7839 lines
299 KiB
EmacsLisp
7839 lines
299 KiB
EmacsLisp
|
;;; magit.el --- control Git from Emacs
|
|||
|
|
|||
|
;; Copyright (C) 2008-2015 The Magit Project Developers
|
|||
|
;;
|
|||
|
;; For a full list of contributors, see the AUTHORS.md file
|
|||
|
;; at the top-level directory of this distribution and at
|
|||
|
;; https://raw.github.com/magit/magit/master/AUTHORS.md
|
|||
|
|
|||
|
;; Author: Marius Vollmer <marius.vollmer@gmail.com>
|
|||
|
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
|
|||
|
;; Former-Maintainers:
|
|||
|
;; Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
|||
|
;; Peter J. Weisberg <pj@irregularexpressions.net>
|
|||
|
;; Phil Jackson <phil@shellarchive.co.uk>
|
|||
|
;; Rémi Vanicat <vanicat@debian.org>
|
|||
|
;; Yann Hodique <yann.hodique@gmail.com>
|
|||
|
|
|||
|
;; Keywords: vc tools
|
|||
|
;; Package: magit
|
|||
|
;; Package-Requires: ((cl-lib "0.5") (git-commit-mode "1.0.0") (git-rebase-mode "1.0.0"))
|
|||
|
|
|||
|
;; Magit requires at least GNU Emacs 23.2 and Git 1.7.2.5.
|
|||
|
;; These are the versions shipped by Debian oldstable (6.0, Squeeze).
|
|||
|
|
|||
|
;; Contains code from GNU Emacs <https://www.gnu.org/software/emacs/>,
|
|||
|
;; released under the GNU General Public License version 3 or later.
|
|||
|
|
|||
|
;; Magit 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, or (at your option)
|
|||
|
;; any later version.
|
|||
|
;;
|
|||
|
;; Magit 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 Magit. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|
|||
|
;;; Commentary:
|
|||
|
|
|||
|
;; Invoking the magit-status function will show a buffer with the
|
|||
|
;; status of the current git repository and its working tree. That
|
|||
|
;; buffer offers key bindings for manipulating the status in simple
|
|||
|
;; ways.
|
|||
|
;;
|
|||
|
;; The status buffer mainly shows the difference between the working
|
|||
|
;; tree and the index, and the difference between the index and the
|
|||
|
;; current HEAD. You can add individual hunks from the working tree
|
|||
|
;; to the index, and you can commit the index.
|
|||
|
;;
|
|||
|
;; See the Magit User Manual for more information.
|
|||
|
|
|||
|
;;; Code:
|
|||
|
|
|||
|
(defvar magit-version 'undefined
|
|||
|
"The version of Magit that you're using.
|
|||
|
Use the function by the same name instead of this variable.")
|
|||
|
;; The value is set at the end of this file, using the
|
|||
|
;; function `magit-version' which is also defined there.
|
|||
|
|
|||
|
;;;; Dependencies
|
|||
|
|
|||
|
(when (version< emacs-version "23.2")
|
|||
|
(error "Magit requires at least GNU Emacs 23.2"))
|
|||
|
|
|||
|
(require 'git-commit-mode)
|
|||
|
(require 'git-rebase-mode)
|
|||
|
|
|||
|
(require 'ansi-color)
|
|||
|
(require 'autorevert)
|
|||
|
(require 'cl-lib)
|
|||
|
(require 'diff-mode)
|
|||
|
(require 'easymenu)
|
|||
|
(require 'epa)
|
|||
|
(require 'format-spec)
|
|||
|
(require 'grep)
|
|||
|
(require 'help-mode)
|
|||
|
(require 'ring)
|
|||
|
(require 'server)
|
|||
|
(require 'tramp)
|
|||
|
(require 'view)
|
|||
|
|
|||
|
(eval-when-compile
|
|||
|
(require 'dired)
|
|||
|
(require 'dired-x)
|
|||
|
(require 'ediff)
|
|||
|
(require 'eshell)
|
|||
|
(require 'ido)
|
|||
|
(require 'package nil t)
|
|||
|
(require 'view))
|
|||
|
|
|||
|
;;;; Declarations
|
|||
|
|
|||
|
(if (featurep 'vc-git)
|
|||
|
(defalias 'magit-grep 'vc-git-grep)
|
|||
|
(defalias 'magit-grep 'lgrep))
|
|||
|
|
|||
|
(declare-function dired-jump 'dired-x)
|
|||
|
(declare-function dired-uncache 'dired)
|
|||
|
(declare-function ediff-cleanup-mess 'ediff)
|
|||
|
(declare-function eshell-parse-arguments 'eshell)
|
|||
|
(declare-function ido-completing-read 'ido)
|
|||
|
(declare-function package-desc-vers 'package)
|
|||
|
(declare-function package-desc-version 'package)
|
|||
|
(declare-function package-version-join 'package)
|
|||
|
(declare-function view-mode 'view)
|
|||
|
|
|||
|
(defvar git-commit-previous-winconf)
|
|||
|
(defvar magit-commit-buffer-name)
|
|||
|
(defvar magit-custom-options)
|
|||
|
(defvar magit-log-buffer-name)
|
|||
|
(defvar magit-marked-commit)
|
|||
|
(defvar magit-process-buffer-name)
|
|||
|
(defvar magit-reflog-buffer-name)
|
|||
|
(defvar magit-refresh-args)
|
|||
|
(defvar magit-stash-buffer-name)
|
|||
|
(defvar magit-status-buffer-name)
|
|||
|
(defvar magit-this-process)
|
|||
|
(defvar package-alist)
|
|||
|
|
|||
|
;;;; Compatibility
|
|||
|
|
|||
|
(eval-and-compile
|
|||
|
|
|||
|
;; Added in Emacs 24.3
|
|||
|
(unless (fboundp 'user-error)
|
|||
|
(defalias 'user-error 'error))
|
|||
|
|
|||
|
;; Added in Emacs 24.3 (mirrors/emacs@b335efc3).
|
|||
|
(unless (fboundp 'setq-local)
|
|||
|
(defmacro setq-local (var val)
|
|||
|
"Set variable VAR to value VAL in current buffer."
|
|||
|
(list 'set (list 'make-local-variable (list 'quote var)) val)))
|
|||
|
|
|||
|
;; Added in Emacs 24.3 (mirrors/emacs@b335efc3).
|
|||
|
(unless (fboundp 'defvar-local)
|
|||
|
(defmacro defvar-local (var val &optional docstring)
|
|||
|
"Define VAR as a buffer-local variable with default value VAL.
|
|||
|
Like `defvar' but additionally marks the variable as being automatically
|
|||
|
buffer-local wherever it is set."
|
|||
|
(declare (debug defvar) (doc-string 3))
|
|||
|
(list 'progn (list 'defvar var val docstring)
|
|||
|
(list 'make-variable-buffer-local (list 'quote var)))))
|
|||
|
|
|||
|
;; Added in Emacs 24.1
|
|||
|
(unless (fboundp 'run-hook-wrapped)
|
|||
|
(defun run-hook-wrapped-1 (hook fns wrap-function &rest args)
|
|||
|
(cl-loop for fn in fns
|
|||
|
if (and (eq fn t)
|
|||
|
(local-variable-p hook)
|
|||
|
(default-boundp hook)
|
|||
|
(apply 'run-hook-wrapped-1 nil
|
|||
|
(default-value hook) wrap-function args))
|
|||
|
return it
|
|||
|
else if (and (functionp fn) (apply wrap-function fn args))
|
|||
|
return it))
|
|||
|
|
|||
|
(defun run-hook-wrapped (hook wrap-function &rest args)
|
|||
|
"Run HOOK, passing each function through WRAP-FUNCTION.
|
|||
|
I.e. instead of calling each function FUN directly with arguments ARGS,
|
|||
|
it calls WRAP-FUNCTION with arguments FUN and ARGS.
|
|||
|
As soon as a call to WRAP-FUNCTION returns non-nil, `run-hook-wrapped'
|
|||
|
aborts and returns that value."
|
|||
|
(when (boundp hook)
|
|||
|
(let ((fns (symbol-value hook)))
|
|||
|
(apply 'run-hook-wrapped-1 hook
|
|||
|
(if (functionp fns) (list fns) fns)
|
|||
|
wrap-function args)))))
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
;;; Settings
|
|||
|
;;;; Custom Groups
|
|||
|
|
|||
|
(defgroup magit nil
|
|||
|
"Controlling Git from Emacs."
|
|||
|
:group 'tools)
|
|||
|
|
|||
|
(defgroup magit-process nil
|
|||
|
"Git and other external processes used by Magit."
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defgroup magit-modes nil
|
|||
|
"Modes provided by Magit."
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defgroup magit-status nil
|
|||
|
"Inspect and manipulate Git repositories."
|
|||
|
:group 'magit-modes)
|
|||
|
|
|||
|
(defgroup magit-diff nil
|
|||
|
"Inspect and manipulate Git diffs."
|
|||
|
:group 'magit-modes)
|
|||
|
|
|||
|
(defgroup magit-commit nil
|
|||
|
"Inspect and manipulate Git commits."
|
|||
|
:group 'magit-modes)
|
|||
|
|
|||
|
(defgroup magit-log nil
|
|||
|
"Inspect and manipulate Git history."
|
|||
|
:group 'magit-modes)
|
|||
|
|
|||
|
(defgroup magit-extensions nil
|
|||
|
"Extensions to Magit."
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defgroup magit-faces nil
|
|||
|
"Faces used by Magit."
|
|||
|
:group 'magit
|
|||
|
:group 'faces)
|
|||
|
|
|||
|
(when (featurep 'gitattributes-mode)
|
|||
|
(custom-add-to-group 'magit-modes 'gitattributes-mode 'custom-group))
|
|||
|
|
|||
|
(when (featurep 'git-commit-mode)
|
|||
|
(custom-add-to-group 'magit-modes 'git-commit 'custom-group)
|
|||
|
(custom-add-to-group 'magit-faces 'git-commit-faces 'custom-group))
|
|||
|
|
|||
|
(when (featurep 'git-rebase-mode)
|
|||
|
(custom-add-to-group 'magit-modes 'git-rebase 'custom-group)
|
|||
|
(custom-add-to-group 'magit-faces 'git-rebase-faces 'custom-group))
|
|||
|
|
|||
|
(custom-add-to-group 'magit 'vc-follow-symlinks 'custom-variable)
|
|||
|
|
|||
|
;;;; Custom Options
|
|||
|
;;;;; Processes
|
|||
|
|
|||
|
(defcustom magit-git-executable
|
|||
|
(or (and (eq system-type 'windows-nt)
|
|||
|
;; On Windows asking for "git" from $PATH might also return
|
|||
|
;; a "git.exe" or "git.cmd". Using "bin/git.exe" directly
|
|||
|
;; is faster than using one of the wrappers "cmd/git.exe"
|
|||
|
;; or "cmd/git.cmd". The wrappers are likely to come
|
|||
|
;; earlier on $PATH, and so we have to exlicitly use
|
|||
|
;; the former.
|
|||
|
(let ((exe (executable-find "git.exe")))
|
|||
|
(when exe
|
|||
|
(let ((alt (directory-file-name (file-name-directory exe))))
|
|||
|
(if (and (equal (file-name-nondirectory alt) "cmd")
|
|||
|
(setq alt (expand-file-name
|
|||
|
(convert-standard-filename "bin/git.exe")
|
|||
|
(file-name-directory alt)))
|
|||
|
(file-executable-p alt))
|
|||
|
alt
|
|||
|
exe)))))
|
|||
|
;; When the only cost is finding the executable, then it it
|
|||
|
;; better not to cache the full path. It might not be installed
|
|||
|
;; in the same location on machines whose repositories are
|
|||
|
;; accessed using Tramp.
|
|||
|
"git")
|
|||
|
"The Git executable used by Magit."
|
|||
|
:group 'magit-process
|
|||
|
:type 'string)
|
|||
|
|
|||
|
(defcustom magit-git-standard-options
|
|||
|
'("--no-pager" "-c" "core.preloadindex=true")
|
|||
|
"Standard options when running Git.
|
|||
|
Be careful what you add here, especially if you are using
|
|||
|
tramp to connect to servers with ancient Git versions."
|
|||
|
:group 'magit-process
|
|||
|
:type '(repeat string))
|
|||
|
|
|||
|
(defcustom magit-gitk-executable
|
|||
|
(or (and (eq system-type 'windows-nt)
|
|||
|
(let ((exe (expand-file-name
|
|||
|
"gitk" (file-name-nondirectory magit-git-executable))))
|
|||
|
(and (file-executable-p exe) exe)))
|
|||
|
(executable-find "gitk") "gitk")
|
|||
|
"The Gitk executable."
|
|||
|
:group 'magit-process
|
|||
|
:set-after '(magit-git-executable)
|
|||
|
:type 'string)
|
|||
|
|
|||
|
(defun magit-locate-emacsclient ()
|
|||
|
"Search for a suitable Emacsclient executable."
|
|||
|
(let ((path (cl-copy-list exec-path)) fixup client)
|
|||
|
(when invocation-directory
|
|||
|
(setq path (cons (directory-file-name invocation-directory) path))
|
|||
|
(when (eq system-type 'darwin)
|
|||
|
(setq fixup (expand-file-name "bin" invocation-directory))
|
|||
|
(when (file-directory-p fixup)
|
|||
|
(push fixup path))
|
|||
|
(when (string-match "^Emacs-\\(powerpc\\|i386\\|x86_64\\)-\\(.+\\)"
|
|||
|
invocation-name)
|
|||
|
(setq fixup (expand-file-name
|
|||
|
(format "bin-%s-%s"
|
|||
|
(match-string 1 invocation-name)
|
|||
|
(match-string 2 invocation-name))
|
|||
|
invocation-directory))
|
|||
|
(when (file-directory-p fixup)
|
|||
|
(push fixup path)))
|
|||
|
(when (string-match-p "Cellar" invocation-directory)
|
|||
|
(setq fixup (expand-file-name "../../../bin" invocation-directory))
|
|||
|
(when (file-directory-p fixup)
|
|||
|
(push fixup path))))
|
|||
|
(setq path (delete-dups path)))
|
|||
|
(setq client (magit-locate-emacsclient-1 path 3))
|
|||
|
(if client
|
|||
|
(shell-quote-argument client)
|
|||
|
(display-warning 'magit (format "\
|
|||
|
Cannot determine a suitable Emacsclient
|
|||
|
|
|||
|
Determining an Emacsclient executable suitable for the
|
|||
|
current Emacs instance failed. For more information
|
|||
|
please see https://github.com/magit/magit/wiki/Emacsclient."))
|
|||
|
nil)))
|
|||
|
|
|||
|
(defun magit-take (n l) ; until we get to use `-take' from dash
|
|||
|
(let (r) (dotimes (_ n) (and l (push (pop l) r))) (nreverse r)))
|
|||
|
|
|||
|
(defun magit-locate-emacsclient-1 (path depth)
|
|||
|
(let* ((version-lst (magit-take depth (split-string emacs-version "\\.")))
|
|||
|
(version-reg (concat "^" (mapconcat #'identity version-lst "\\."))))
|
|||
|
(or (locate-file-internal
|
|||
|
"emacsclient" path
|
|||
|
(cl-mapcan
|
|||
|
(lambda (v) (cl-mapcar (lambda (e) (concat v e)) exec-suffixes))
|
|||
|
(nconc (cl-mapcon (lambda (v)
|
|||
|
(setq v (mapconcat #'identity (reverse v) "."))
|
|||
|
(list v (concat "-" v)))
|
|||
|
(reverse version-lst))
|
|||
|
(list "")))
|
|||
|
(lambda (exec)
|
|||
|
(ignore-errors
|
|||
|
(string-match-p version-reg (magit-emacsclient-version exec)))))
|
|||
|
(and (> depth 1)
|
|||
|
(magit-locate-emacsclient-1 path (1- depth))))))
|
|||
|
|
|||
|
(defun magit-emacsclient-version (exec)
|
|||
|
(cadr (split-string (car (process-lines exec "--version")))))
|
|||
|
|
|||
|
(defcustom magit-emacsclient-executable (magit-locate-emacsclient)
|
|||
|
"The Emacsclient executable.
|
|||
|
If the default is nil, or commiting or rebasing is somehow broken,
|
|||
|
please see https://github.com/magit/magit/wiki/Emacsclient."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:type '(choice (string :tag "Executable")
|
|||
|
(const :tag "Don't use Emacsclient" nil)))
|
|||
|
|
|||
|
(defcustom magit-process-connection-type (not (eq system-type 'cygwin))
|
|||
|
"Connection type used for the git process.
|
|||
|
|
|||
|
If nil, use pipes: this is usually more efficient, and works on Cygwin.
|
|||
|
If t, use ptys: this enables magit to prompt for passphrases when needed."
|
|||
|
:group 'magit-process
|
|||
|
:type '(choice (const :tag "pipe" nil)
|
|||
|
(const :tag "pty" t)))
|
|||
|
|
|||
|
(defcustom magit-process-popup-time -1
|
|||
|
"Popup the process buffer if a command takes longer than this many seconds."
|
|||
|
:group 'magit-process
|
|||
|
:type '(choice (const :tag "Never" -1)
|
|||
|
(const :tag "Immediately" 0)
|
|||
|
(integer :tag "After this many seconds")))
|
|||
|
|
|||
|
(defcustom magit-process-log-max 32
|
|||
|
"Maximum number of sections to keep in a process log buffer.
|
|||
|
When adding a new section would go beyond the limit set here,
|
|||
|
then the older half of the sections are remove. Sections that
|
|||
|
belong to processes that are still running are never removed."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:type 'integer)
|
|||
|
|
|||
|
(defcustom magit-process-quote-curly-braces
|
|||
|
(and (eq system-type 'windows-nt)
|
|||
|
(let ((case-fold-search t))
|
|||
|
(string-match-p "cygwin" magit-git-executable))
|
|||
|
t)
|
|||
|
"Whether curly braces should be quoted when calling git.
|
|||
|
This may be necessary when using Windows. On all other system
|
|||
|
types this must always be nil.
|
|||
|
|
|||
|
We are not certain when quoting is needed, but it appears it is
|
|||
|
needed when using Cygwin Git but not when using stand-alone Git.
|
|||
|
The default value is set based on that assumptions. If this
|
|||
|
turns out to be wrong you can customize this option but please
|
|||
|
also comment on issue #816."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:set-after '(magit-git-executable)
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-process-yes-or-no-prompt-regexp
|
|||
|
" [\[(]\\([Yy]\\(?:es\\)?\\)[/|]\\([Nn]o?\\)[\])] ?[?:] ?$"
|
|||
|
"Regexp matching Yes-or-No prompts of git and its subprocesses."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:type 'regexp)
|
|||
|
|
|||
|
(defcustom magit-process-password-prompt-regexps
|
|||
|
'("^\\(Enter \\)?[Pp]assphrase\\( for \\(RSA \\)?key '.*'\\)?: ?$"
|
|||
|
"^\\(Enter \\)?[Pp]assword\\( for '.*'\\)?: ?$"
|
|||
|
"^.*'s password: ?$"
|
|||
|
"^Yubikey for .*: ?$")
|
|||
|
"List of regexps matching password prompts of git and its subprocesses."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:type '(repeat (regexp)))
|
|||
|
|
|||
|
(defcustom magit-process-username-prompt-regexps
|
|||
|
'("^Username for '.*': ?$")
|
|||
|
"List of regexps matching username prompts of git and its subprocesses."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:type '(repeat (regexp)))
|
|||
|
|
|||
|
(defconst magit-server-window-type
|
|||
|
'(choice
|
|||
|
(const :tag "Use selected window"
|
|||
|
:match (lambda (widget value)
|
|||
|
(not (functionp value)))
|
|||
|
nil)
|
|||
|
(function-item :tag "Display in new frame" switch-to-buffer-other-frame)
|
|||
|
(function-item :tag "Use pop-to-buffer" pop-to-buffer)
|
|||
|
(function :tag "Other function")))
|
|||
|
|
|||
|
(defcustom magit-server-window-for-commit 'pop-to-buffer
|
|||
|
"Function used to select a window for displaying commit message buffers.
|
|||
|
It should take one argument (a buffer) and display and select it.
|
|||
|
A common value is `pop-to-buffer'. It can also be nil in which
|
|||
|
case the selected window is used."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:type magit-server-window-type)
|
|||
|
|
|||
|
(defcustom magit-server-window-for-rebase server-window
|
|||
|
"Function used to select a window for displaying interactive rebase buffers.
|
|||
|
It should take one argument (a buffer) and display and select it.
|
|||
|
A common value is `pop-to-buffer'. It can also be nil in which
|
|||
|
case the selected window is used."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-process
|
|||
|
:set-after '(server-window)
|
|||
|
:type magit-server-window-type)
|
|||
|
|
|||
|
;;;;; Staging
|
|||
|
|
|||
|
(defcustom magit-stage-all-confirm t
|
|||
|
"Whether to require confirmation before staging all changes.
|
|||
|
This reduces the risk of accidentally losing the index. If
|
|||
|
nothing at all is staged yet, then always stage without requiring
|
|||
|
confirmation, because it can be undone without the risk of losing
|
|||
|
a carefully crafted index."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-unstage-all-confirm t
|
|||
|
"Whether to require confirmation before unstaging all changes.
|
|||
|
This reduces the risk of accidentally losing of the index. If
|
|||
|
there are no staged changes at all, then always unstage without
|
|||
|
confirmation, because it can be undone without the risk of losing
|
|||
|
a carefully crafted index."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-revert-item-confirm t
|
|||
|
"Whether to require confirmation before reverting hunks.
|
|||
|
If you disable this, consider enabling `magit-revert-backup'
|
|||
|
instead."
|
|||
|
:group 'magit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-revert-backup nil
|
|||
|
"Whether to backup a hunk before reverting it.
|
|||
|
The hunk is stored in \".git/magit/reverted.diff\" and can be
|
|||
|
applied using `magit-revert-undo'. Older hunks are available
|
|||
|
in the same directory as numbered backup files and have to be
|
|||
|
applied manually. Only individual hunks are backed up; when
|
|||
|
a complete file is reverted (which requires confirmation) no
|
|||
|
backup is created."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-save-some-buffers t
|
|||
|
"Whether certain commands save modified buffers before running.
|
|||
|
|
|||
|
nil don't save buffers.
|
|||
|
t ask which buffers to save.
|
|||
|
`dontask' save all buffers without asking."
|
|||
|
:group 'magit
|
|||
|
:type '(choice (const :tag "Never" nil)
|
|||
|
(const :tag "Ask" t)
|
|||
|
(const :tag "Save without asking" dontask)))
|
|||
|
|
|||
|
(defcustom magit-save-some-buffers-predicate
|
|||
|
'magit-save-buffers-predicate-tree-only
|
|||
|
"A predicate function to decide whether to save a buffer.
|
|||
|
|
|||
|
Used by function `magit-save-some-buffers' when the variable of
|
|||
|
the same name is non-nil."
|
|||
|
:group 'magit
|
|||
|
:type '(radio (function-item magit-save-buffers-predicate-tree-only)
|
|||
|
(function-item magit-save-buffers-predicate-all)
|
|||
|
(function :tag "Other")))
|
|||
|
|
|||
|
(defcustom magit-rewrite-inclusive t
|
|||
|
"Whether magit includes the selected base commit in a rewrite operation.
|
|||
|
|
|||
|
t means both the selected commit as well as any subsequent
|
|||
|
commits will be rewritten. This is magit's default behaviour,
|
|||
|
equivalent to 'git rebase -i ${REV}~1'
|
|||
|
|
|||
|
A'---B'---C'---D'
|
|||
|
^
|
|||
|
|
|||
|
nil means the selected commit will be literally used as 'base',
|
|||
|
so only subsequent commits will be rewritten. This is consistent
|
|||
|
with git-rebase, equivalent to 'git rebase -i ${REV}', yet more
|
|||
|
cumbersome to use from the status buffer.
|
|||
|
|
|||
|
A---B'---C'---D'
|
|||
|
^"
|
|||
|
:group 'magit
|
|||
|
:type '(choice (const :tag "Always" t)
|
|||
|
(const :tag "Never" nil)
|
|||
|
(const :tag "Ask" ask)))
|
|||
|
|
|||
|
;;;;; Highlighting
|
|||
|
|
|||
|
(defun magit-set-variable-and-refresh (symbol value)
|
|||
|
"Set SYMBOL to VALUE and call `magit-refresh-all'."
|
|||
|
(set-default symbol value)
|
|||
|
;; If magit isn't fully loaded yet no buffer that might
|
|||
|
;; need refreshing can exist and we can take a shortcut.
|
|||
|
;; We also don't want everything to repeatedly refresh
|
|||
|
;; when evaluating this file.
|
|||
|
(when (and (featurep 'magit) (not buffer-file-name))
|
|||
|
(magit-refresh-all)))
|
|||
|
|
|||
|
(defcustom magit-highlight-whitespace t
|
|||
|
"Specify where to highlight whitespace errors.
|
|||
|
See `magit-highlight-trailing-whitespace',
|
|||
|
`magit-highlight-indentation'. The symbol t means in all diffs,
|
|||
|
`status' means only in the status buffer, and nil means nowhere."
|
|||
|
:group 'magit
|
|||
|
:set 'magit-set-variable-and-refresh
|
|||
|
:type '(choice (const :tag "Always" t)
|
|||
|
(const :tag "Never" nil)
|
|||
|
(const :tag "In status buffer" status)))
|
|||
|
|
|||
|
(defcustom magit-highlight-trailing-whitespace t
|
|||
|
"Whether to highlight whitespace at the end of a line in diffs.
|
|||
|
Used only when `magit-highlight-whitespace' is non-nil."
|
|||
|
:group 'magit
|
|||
|
:set 'magit-set-variable-and-refresh
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-highlight-indentation nil
|
|||
|
"Highlight the \"wrong\" indentation style.
|
|||
|
Used only when `magit-highlight-whitespace' is non-nil.
|
|||
|
|
|||
|
The value is a list of cons cells. The car is a regular
|
|||
|
expression, and the cdr is the value that applies to repositories
|
|||
|
whose directory matches the regular expression. If more than one
|
|||
|
item matches, then the *last* item in the list applies. So, the
|
|||
|
default value should come first in the list.
|
|||
|
|
|||
|
If the value is `tabs', highlight indentation with tabs. If the
|
|||
|
value is an integer, highlight indentation with at least that
|
|||
|
many spaces. Otherwise, highlight neither."
|
|||
|
:group 'magit
|
|||
|
:set 'magit-set-variable-and-refresh
|
|||
|
:type `(repeat (cons (string :tag "Directory regexp")
|
|||
|
(choice (const :tag "Tabs" tabs)
|
|||
|
(integer :tag "Spaces" :value ,tab-width)
|
|||
|
(const :tag "Neither" nil))))) ;^FIXME
|
|||
|
|
|||
|
(defcustom magit-item-highlight-face 'magit-item-highlight
|
|||
|
"The face used to highlight the current section.
|
|||
|
|
|||
|
By default the highlighting of the current section is done using
|
|||
|
the background color specified by face `magit-item-highlight'.
|
|||
|
|
|||
|
If you don't want to use the background to do the highlighting,
|
|||
|
this *might* by as easy as customizing that face. However if you
|
|||
|
are using a theme, which in turn sets the background color of
|
|||
|
that face then, due to limitations in face inheritance when using
|
|||
|
themes, you might be forced to use another face.
|
|||
|
|
|||
|
Unfortunately it is only possible to override a face attribute,
|
|||
|
set by a theme, but not to drop it entirely. This means that one
|
|||
|
has to explicitly use the `default' background color, to make it
|
|||
|
appear *as if* the background wasn't used.
|
|||
|
|
|||
|
One reason you might want to *not* use the background, is that
|
|||
|
doing so forces the use of overlays for parts of diffs and for
|
|||
|
refnames. Using overlays potentially degrades performance when
|
|||
|
generating large diffs. Also see option `magit-use-overlays'."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit
|
|||
|
:group 'magit-faces
|
|||
|
:type '(choice (const magit-item-highlight)
|
|||
|
(const bold)
|
|||
|
(face :tag "Other face")
|
|||
|
(const :tag "Don't highlight" nil)))
|
|||
|
|
|||
|
(defcustom magit-use-overlays
|
|||
|
(not (eq magit-item-highlight-face 'bold))
|
|||
|
"Whether to use overlays to highlight various diff components.
|
|||
|
|
|||
|
This has to be non-nil if the current section is highlighted by
|
|||
|
changing the background color. Otherwise background colors that
|
|||
|
hold semantic meaning, like that of the added and removed lines
|
|||
|
in diffs, as well as section headings, would be shadowed by the
|
|||
|
highlighting.
|
|||
|
|
|||
|
To select the face used for highlighting customize the option
|
|||
|
`magit-item-highlight-face'. If you set that to `bold' or some
|
|||
|
other face that does not use the background then you can set this
|
|||
|
option to nil. Doing so could potentially improve performance
|
|||
|
when generating large diffs."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit
|
|||
|
:group 'magit-faces
|
|||
|
:set-after '(magit-item-highlight-face)
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(define-obsolete-variable-alias 'magit-diff-use-overlays
|
|||
|
'magit-use-overlays "1.4.0")
|
|||
|
|
|||
|
;;;;; Completion
|
|||
|
|
|||
|
(defcustom magit-completing-read-function 'magit-builtin-completing-read
|
|||
|
"Function to be called when requesting input from the user."
|
|||
|
:group 'magit
|
|||
|
:type '(radio (function-item magit-ido-completing-read)
|
|||
|
(function-item magit-builtin-completing-read)
|
|||
|
(function :tag "Other")))
|
|||
|
|
|||
|
(defcustom magit-remote-ref-format 'remote-slash-branch
|
|||
|
"How to format refs when autocompleting, in particular for remotes.
|
|||
|
|
|||
|
Autocompletion is used by functions like `magit-checkout',
|
|||
|
`magit-interactive-rebase' and others which offer branch name
|
|||
|
completion.
|
|||
|
|
|||
|
`remote-slash-branch' Format refs as \"remote/branch\".
|
|||
|
`branch-then-remote' Format refs as \"branch (remote)\"."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit
|
|||
|
:type '(choice (const :tag "branch (remote)" branch-then-remote)
|
|||
|
(const :tag "remote/branch" remote-slash-branch)))
|
|||
|
|
|||
|
(defcustom magit-repo-dirs nil
|
|||
|
"Directories containing Git repositories.
|
|||
|
Magit will look into these directories for Git repositories and
|
|||
|
offer them as choices for `magit-status'."
|
|||
|
:group 'magit
|
|||
|
:type '(repeat directory))
|
|||
|
|
|||
|
(defcustom magit-repo-dirs-depth 3
|
|||
|
"The maximum depth to look for Git repos.
|
|||
|
When looking for a Git repository below the directories in
|
|||
|
`magit-repo-dirs', Magit will only descend this many levels
|
|||
|
deep."
|
|||
|
:group 'magit
|
|||
|
:type 'integer)
|
|||
|
|
|||
|
;;;;; Modes
|
|||
|
;;;;;; Common
|
|||
|
|
|||
|
(defcustom magit-mode-hook nil
|
|||
|
"Hook run when entering a Magit mode derived mode."
|
|||
|
:options '(magit-load-config-extensions)
|
|||
|
:group 'magit-modes
|
|||
|
:type 'hook)
|
|||
|
|
|||
|
(defcustom magit-show-xref-buttons '(magit-diff-mode magit-commit-mode)
|
|||
|
"List of modes whose buffers should contain history buttons.
|
|||
|
Currently only `magit-diff-mode' and `magit-commit-mode' are
|
|||
|
supported."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type '(repeat (choice (const magit-diff-mode)
|
|||
|
(const magit-commit-mode))))
|
|||
|
|
|||
|
(defcustom magit-show-child-count nil
|
|||
|
"Whether to append the number of childen to section headings."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defvar magit-status-line-align-to 9)
|
|||
|
|
|||
|
(defcustom magit-restore-window-configuration nil
|
|||
|
"Whether quitting a Magit buffer restores previous window configuration.
|
|||
|
|
|||
|
Function `magit-mode-display-buffer' is used to display and
|
|||
|
select Magit buffers. Unless the buffer was already displayed in
|
|||
|
a window of the selected frame it also stores the previous window
|
|||
|
configuration. If this option is non-nil that configuration will
|
|||
|
later be restored by `magit-mode-quit-window', provided the
|
|||
|
buffer has not since been displayed in another frame.
|
|||
|
|
|||
|
This works best when only two windows are usually displayed in a
|
|||
|
frame. If this isn't the case setting this to t might often lead
|
|||
|
to undesirable behaviour. Also quitting a Magit buffer while
|
|||
|
another Magit buffer that was created earlier is still displayed
|
|||
|
will cause that buffer to be hidden, which might or might not be
|
|||
|
what you want."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-refs-namespaces
|
|||
|
'(("^\\(HEAD\\)$" magit-log-head-label-head nil)
|
|||
|
("^refs/tags/\\(.+\\)" magit-log-head-label-tags nil)
|
|||
|
("^refs/heads/\\(.+\\)" magit-log-head-label-local nil)
|
|||
|
("^refs/remotes/\\(.+\\)" magit-log-head-label-remote nil)
|
|||
|
("^refs/bisect/\\(bad\\)" magit-log-head-label-bisect-bad nil)
|
|||
|
("^refs/bisect/\\(skip.*\\)" magit-log-head-label-bisect-skip nil)
|
|||
|
("^refs/bisect/\\(good.*\\)" magit-log-head-label-bisect-good nil)
|
|||
|
("^refs/wip/\\(.+\\)" magit-log-head-label-wip nil)
|
|||
|
("^refs/patches/\\(.+\\)" magit-log-head-label-patches nil)
|
|||
|
("^\\(bad\\):" magit-log-head-label-bisect-bad nil)
|
|||
|
("^\\(skip\\):" magit-log-head-label-bisect-skip nil)
|
|||
|
("^\\(good\\):" magit-log-head-label-bisect-good nil)
|
|||
|
("\\(.+\\)" magit-log-head-label-default nil))
|
|||
|
"How different refs should be formatted for display.
|
|||
|
|
|||
|
Each entry controls how a certain type of ref is displayed, and
|
|||
|
has the form (REGEXP FACE FORMATTER). REGEXP is a regular
|
|||
|
expression used to match full refs. The first entry whose REGEXP
|
|||
|
matches the reference is used. The first regexp submatch becomes
|
|||
|
the \"label\" that represents the ref and is propertized with
|
|||
|
font FONT. If FORMATTER is non-nil it should be a function that
|
|||
|
takes two arguments, the full ref and the face. It is supposed
|
|||
|
to return a propertized label that represents the ref.
|
|||
|
|
|||
|
Currently this variable is only used in logs and the branch
|
|||
|
manager but it will be used in more places in the future."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type '(repeat
|
|||
|
(list regexp
|
|||
|
face
|
|||
|
(choice (const :tag "first submatch is label" nil)
|
|||
|
(function :tag "format using function")))))
|
|||
|
|
|||
|
;;;;;; Status
|
|||
|
|
|||
|
(defcustom magit-status-mode-hook nil
|
|||
|
"Hook run when the `magit-status' buffer is created."
|
|||
|
:group 'magit-status
|
|||
|
:type 'hook)
|
|||
|
|
|||
|
(defcustom magit-status-sections-hook
|
|||
|
'(magit-insert-status-local-line
|
|||
|
magit-insert-status-remote-line
|
|||
|
magit-insert-status-head-line
|
|||
|
magit-insert-status-tags-line
|
|||
|
magit-insert-status-merge-line
|
|||
|
magit-insert-status-rebase-lines
|
|||
|
magit-insert-empty-line
|
|||
|
magit-insert-rebase-sequence
|
|||
|
magit-insert-bisect-output
|
|||
|
magit-insert-bisect-rest
|
|||
|
magit-insert-bisect-log
|
|||
|
magit-insert-stashes
|
|||
|
magit-insert-untracked-files
|
|||
|
magit-insert-pending-commits
|
|||
|
magit-insert-unstaged-changes
|
|||
|
magit-insert-staged-changes
|
|||
|
magit-insert-unpulled-commits
|
|||
|
magit-insert-unpushed-commits)
|
|||
|
"Hook run to insert sections into the status buffer.
|
|||
|
|
|||
|
This option allows reordering the sections and adding sections
|
|||
|
that are by default displayed in other Magit buffers. Doing the
|
|||
|
latter is currently not recommended because not all functions
|
|||
|
that insert sections have been adapted yet. Only inserters that
|
|||
|
take no argument can be used and some functions exist that begin
|
|||
|
with the `magit-insert-' prefix but do not insert a section.
|
|||
|
|
|||
|
Note that there are already plans to improve this and to add
|
|||
|
similar hooks for other Magit modes."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-status
|
|||
|
:type 'hook)
|
|||
|
|
|||
|
(defcustom magit-status-buffer-switch-function 'pop-to-buffer
|
|||
|
"Function for `magit-status' to use for switching to the status buffer.
|
|||
|
|
|||
|
The function is given one argument, the status buffer."
|
|||
|
:group 'magit-status
|
|||
|
:type '(radio (function-item switch-to-buffer)
|
|||
|
(function-item pop-to-buffer)
|
|||
|
(function :tag "Other")))
|
|||
|
|
|||
|
(defcustom magit-status-show-sequence-help t
|
|||
|
"Whether to show instructions on how to proceed a stopped action.
|
|||
|
When this is non-nil and a commit failed to apply during a merge
|
|||
|
or rebase, then show instructions on how to continue."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-status
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-status-tags-line-subject 'head
|
|||
|
"Whether tag or head is the subject on tags line in status buffer.
|
|||
|
|
|||
|
This controls how the words \"ahead\" and \"behind\" are used on
|
|||
|
the tags line in the status buffer. The tags line does not
|
|||
|
actually display complete sentences, but when thinking about when
|
|||
|
to use which term, it helps imagining it did. This option
|
|||
|
controls whether the tag names should be considered the subjects
|
|||
|
or objects in these sentences.
|
|||
|
|
|||
|
`tag' The previous tag is *behind* HEAD by N commits.
|
|||
|
The next tag is *ahead* of HEAD by N commits.
|
|||
|
`head' HEAD is *ahead* of the previous tag by N commits.
|
|||
|
HEAD is *behind* the next tag by N commits.
|
|||
|
|
|||
|
If the value is `tag' the commit counts are fontified; otherwise
|
|||
|
they are not (due to semantic considerations)."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-status
|
|||
|
:type '(choice (const :tag "tags are the subjects" tag)
|
|||
|
(const :tag "head is the subject" head)))
|
|||
|
|
|||
|
;;;;;; Diff
|
|||
|
|
|||
|
(defun magit-set-default-diff-options (symbol value)
|
|||
|
"Set the default for `magit-diff-options' based on popup value.
|
|||
|
Also set the local value in all Magit buffers and refresh them.
|
|||
|
\n(fn)" ; The arguments are an internal implementation detail.
|
|||
|
(interactive (list 'magit-diff-options magit-custom-options))
|
|||
|
(set-default symbol value)
|
|||
|
(when (and (featurep 'magit) (not buffer-file-name))
|
|||
|
(dolist (buffer (buffer-list))
|
|||
|
(when (derived-mode-p 'magit-mode)
|
|||
|
(with-current-buffer buffer
|
|||
|
(with-no-warnings
|
|||
|
(setq-local magit-diff-options value))
|
|||
|
(magit-mode-refresh-buffer))))))
|
|||
|
|
|||
|
(defcustom magit-diff-options nil
|
|||
|
"Git options used to display diffs.
|
|||
|
|
|||
|
For more information about the options see man:git-diff.
|
|||
|
This variable can be conveniently set in Magit buffers
|
|||
|
using `magit-key-mode-popup-diff-options' (bound to \
|
|||
|
\\<magit-mode-map>\\[magit-key-mode-popup-diff-options]).
|
|||
|
|
|||
|
Please note that not all of these options are supported by older
|
|||
|
versions of Git, which could become a problem if you use tramp to
|
|||
|
access repositories on a system with such a version. If you see
|
|||
|
whitespace where you would have expected a diff, this likely is
|
|||
|
the cause, and the only (currently) workaround is to not make the
|
|||
|
problematic option a member of the default value."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-diff
|
|||
|
:set 'magit-set-default-diff-options
|
|||
|
:type '(set :greedy t
|
|||
|
(const :tag
|
|||
|
"--minimal Show smallest possible diff"
|
|||
|
"--minimal")
|
|||
|
(const :tag
|
|||
|
"--patience Use patience diff algorithm"
|
|||
|
"--patience")
|
|||
|
(const :tag
|
|||
|
"--histogram Use histogram diff algorithm"
|
|||
|
"--histogram")
|
|||
|
(const :tag
|
|||
|
"--ignore-space-change Ignore whitespace changes"
|
|||
|
"--ignore-space-change")
|
|||
|
(const :tag
|
|||
|
"--ignore-all-space Ignore all whitespace"
|
|||
|
"--ignore-all-space")
|
|||
|
(const :tag
|
|||
|
"--function-context Show surrounding functions"
|
|||
|
"--function-context")))
|
|||
|
|
|||
|
(put 'magit-diff-options 'permanent-local t)
|
|||
|
|
|||
|
(defcustom magit-show-diffstat t
|
|||
|
"Whether to show diffstat in diff and commit buffers."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-diff
|
|||
|
:group 'magit-commit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-diff-refine-hunk nil
|
|||
|
"Show fine (word-granularity) differences within diff hunks.
|
|||
|
|
|||
|
There are three possible settings:
|
|||
|
|
|||
|
nil never show fine differences
|
|||
|
t show fine differences for the selected diff hunk only
|
|||
|
`all' show fine differences for all displayed diff hunks"
|
|||
|
:group 'magit-diff
|
|||
|
:type '(choice (const :tag "Never" nil)
|
|||
|
(const :tag "Selected only" t)
|
|||
|
(const :tag "All" all))
|
|||
|
:set 'magit-set-variable-and-refresh)
|
|||
|
|
|||
|
;;;;;; Commit
|
|||
|
|
|||
|
(defcustom magit-commit-ask-to-stage t
|
|||
|
"Whether to ask to stage everything when committing and nothing is staged."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-commit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-commit-extend-override-date nil
|
|||
|
"Whether using `magit-commit-extend' changes the committer date."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-commit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-commit-reword-override-date nil
|
|||
|
"Whether using `magit-commit-reword' changes the committer date."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-commit
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-commit-squash-commit nil
|
|||
|
"Whether to target the marked or current commit when squashing.
|
|||
|
|
|||
|
When this is nil then the command `magit-commit-fixup' and
|
|||
|
`magit-commit-squash' always require that the user explicitly
|
|||
|
selects a commit. This is also the case when these commands are
|
|||
|
used with a prefix argument, in which case this option is ignored.
|
|||
|
|
|||
|
Otherwise this controls which commit to target, either the
|
|||
|
current or marked commit. Or if both can be used, which should
|
|||
|
be preferred."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-commit
|
|||
|
:type
|
|||
|
'(choice
|
|||
|
(const :tag "Always prompt" nil)
|
|||
|
(const :tag "Prefer current commit, else use marked" current-or-marked)
|
|||
|
(const :tag "Prefer marked commit, else use current" marked-or-current)
|
|||
|
(const :tag "Use current commit, if any" current)
|
|||
|
(const :tag "Use marked commit, if any" marked)))
|
|||
|
|
|||
|
(defcustom magit-expand-staged-on-commit nil
|
|||
|
"Whether to expand staged changes when creating a commit.
|
|||
|
When this is non-nil and the current buffer is the status buffer
|
|||
|
expand the section containing staged changes. If this is `full'
|
|||
|
always expand all subsections; if it is t subsections that were
|
|||
|
previously hidden remain hidden.
|
|||
|
|
|||
|
In the event that expanding very large patches takes a long time
|
|||
|
\\<global-map>\\[keyboard-quit] can be used to abort that step.
|
|||
|
This is especially useful when you would normally not look at the
|
|||
|
changes, e.g. because you are committing some binary files."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-commit
|
|||
|
:type '(choice (const :tag "Expand all subsections" full)
|
|||
|
(const :tag "Expand top section" t)
|
|||
|
(const :tag "Don't expand" nil)))
|
|||
|
|
|||
|
;;;;;; Log
|
|||
|
|
|||
|
(defcustom magit-log-auto-more nil
|
|||
|
"Insert more log entries automatically when moving past the last entry.
|
|||
|
|
|||
|
Only considered when moving past the last entry with
|
|||
|
`magit-goto-*-section' commands."
|
|||
|
:group 'magit-log
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-log-cutoff-length 100
|
|||
|
"The maximum number of commits to show in the log and whazzup buffers."
|
|||
|
:group 'magit-log
|
|||
|
:type 'integer)
|
|||
|
|
|||
|
(defcustom magit-log-infinite-length 99999
|
|||
|
"Number of log used to show as maximum for `magit-log-cutoff-length'."
|
|||
|
:group 'magit-log
|
|||
|
:type 'integer)
|
|||
|
|
|||
|
(defcustom magit-log-format-graph-function nil
|
|||
|
"Function used to format graphs in log buffers.
|
|||
|
The function is called with one argument, the propertized graph
|
|||
|
of a single line in as a string. It has to return the formatted
|
|||
|
string. This option can also be nil, in which case the graph is
|
|||
|
inserted as is."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type '(choice (const :tag "insert as is" nil)
|
|||
|
(function-item magit-log-format-unicode-graph)
|
|||
|
function))
|
|||
|
|
|||
|
(defcustom magit-log-format-unicode-graph-alist
|
|||
|
'((?/ . ?╱) (?| . ?│) (?\\ . ?╲) (?* . ?◆) (?o . ?◇))
|
|||
|
"Alist used by `magit-log-format-unicode-graph' to translate chars."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type '(repeat (cons :format "%v\n"
|
|||
|
(character :format "replace %v ")
|
|||
|
(character :format "with %v"))))
|
|||
|
|
|||
|
(defcustom magit-log-show-gpg-status nil
|
|||
|
"Display signature verification information as part of the log."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-log-show-margin t
|
|||
|
"Whether to use a margin when showing `oneline' logs.
|
|||
|
When non-nil the author name and date are displayed in the margin
|
|||
|
of the log buffer if that contains a `oneline' log. This can be
|
|||
|
toggled temporarily using the command `magit-log-toggle-margin'."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(put 'magit-log-show-margin 'permanent-local t)
|
|||
|
|
|||
|
(defcustom magit-log-margin-spec '(25 nil magit-duration-spec)
|
|||
|
"How to format the margin for `oneline' logs.
|
|||
|
|
|||
|
When the log buffer contains a `oneline' log, then it optionally
|
|||
|
uses the right margin to display the author name and author date.
|
|||
|
This is also supported in the reflog buffer.
|
|||
|
|
|||
|
Logs that are shown together with other non-log information (e.g.
|
|||
|
in the status buffer) are never accompanied by a margin. The
|
|||
|
same applies to `long' logs, in this case because that would be
|
|||
|
redundant.
|
|||
|
|
|||
|
This option controls how that margin is formatted, the other
|
|||
|
option affecting this is `magit-log-show-margin'; if that is nil
|
|||
|
then no margin is displayed at all. To toggle this temporarily
|
|||
|
use the command `magit-log-show-margin'.
|
|||
|
|
|||
|
The value has the form (WIDTH CHARACTERP DURATION-SPEC). The
|
|||
|
width of the margin is controlled using WIDTH, an integer. When
|
|||
|
CHARACTERP is non-nil time units are shown as single characters,
|
|||
|
otherwise the full name of the unit is displayed. DURATION-SPEC
|
|||
|
has to be a variable, its value controls which time units are
|
|||
|
used, how many seconds they contain, and what their names are."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type '(list (integer :tag "Margin width")
|
|||
|
(choice :tag "Time unit style"
|
|||
|
(const :tag "Character" t)
|
|||
|
(const :tag "Word" nil))
|
|||
|
(variable :tag "Duration spec variable")))
|
|||
|
|
|||
|
(defcustom magit-duration-spec
|
|||
|
`((?Y "year" "years" ,(round (* 60 60 24 365.2425)))
|
|||
|
(?M "month" "months" ,(round (* 60 60 24 30.436875)))
|
|||
|
(?w "week" "weeks" ,(* 60 60 24 7))
|
|||
|
(?d "day" "days" ,(* 60 60 24))
|
|||
|
(?h "hour" "hours" ,(* 60 60))
|
|||
|
(?m "minute" "minutes" 60)
|
|||
|
(?s "second" "seconds" 1))
|
|||
|
"Units used to display durations in a human format.
|
|||
|
The value is a list of time units, beginning with the longest.
|
|||
|
Each element has the form ((CHAR UNIT UNITS SECONDS)..). UNIT
|
|||
|
is the time unit, UNITS is the plural of that unit. CHAR is a
|
|||
|
character that can be used as abbreviation and must be unique
|
|||
|
amoung all elements. SECONDS is the number of seconds in one
|
|||
|
UNIT. Also see option `magit-log-margin-spec'."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type '(repeat (list (character :tag "Unit character")
|
|||
|
(string :tag "Unit singular string")
|
|||
|
(string :tag "Unit plural string")
|
|||
|
(integer :tag "Seconds in unit"))))
|
|||
|
|
|||
|
(defcustom magit-ellipsis #x2026 ; "horizontal ellipsis"
|
|||
|
"Character appended to abreviated text.
|
|||
|
Currently this is used only in the log margin, but might later
|
|||
|
be used elsewhere too. Filenames that were abbreviated by Git
|
|||
|
are left as-is."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-log
|
|||
|
:type 'character)
|
|||
|
|
|||
|
;;;;;; Others
|
|||
|
|
|||
|
(defcustom magit-auto-revert-mode-lighter " MRev"
|
|||
|
"String to display when Magit-Auto-Revert mode is active."
|
|||
|
:group 'magit-modes)
|
|||
|
|
|||
|
(define-minor-mode magit-auto-revert-mode
|
|||
|
"Toggle global Magit-Auto-Revert mode.
|
|||
|
With prefix ARG, enable Magit-Auto-Revert mode if ARG is positive;
|
|||
|
otherwise, disable it. If called from Lisp, enable the mode if
|
|||
|
ARG is omitted or nil.
|
|||
|
|
|||
|
Magit-Auto-Revert mode is a global minor mode that, after Magit
|
|||
|
has run a Git command, reverts buffers associated with files that
|
|||
|
have changed on disk and are tracked in the current Git repository."
|
|||
|
:group 'magit-modes
|
|||
|
:lighter magit-auto-revert-mode-lighter
|
|||
|
:global t
|
|||
|
:init-value t)
|
|||
|
|
|||
|
(defcustom magit-merge-warn-dirty-worktree t
|
|||
|
"Whether to issue a warning when attempting to start a merge in a dirty worktree."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom magit-push-hook '(magit-push-dwim)
|
|||
|
"Hook run by `magit-push' to actually do the work.
|
|||
|
See `magit-push' and `magit-push-dwim' for more information."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type 'hook)
|
|||
|
|
|||
|
(defcustom magit-set-upstream-on-push nil
|
|||
|
"Whether `magit-push' may set upstream when pushing a branch.
|
|||
|
This only applies if the branch does not have an upstream set yet.
|
|||
|
|
|||
|
nil don't use --set-upstream.
|
|||
|
t ask if --set-upstream should be used.
|
|||
|
`dontask' always use --set-upstream.
|
|||
|
`refuse' refuse to push unless a remote branch has already been set."
|
|||
|
:group 'magit-modes
|
|||
|
:type '(choice (const :tag "Never" nil)
|
|||
|
(const :tag "Ask" t)
|
|||
|
(const :tag "Ask if not set" askifnotset)
|
|||
|
(const :tag "Refuse" refuse)
|
|||
|
(const :tag "Always" dontask)))
|
|||
|
|
|||
|
(defcustom magit-wazzup-sections-hook
|
|||
|
'(magit-insert-wazzup-head-line
|
|||
|
magit-insert-empty-line
|
|||
|
magit-insert-wazzup-branches)
|
|||
|
"Hook run to insert sections into the wazzup buffer."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type 'hook)
|
|||
|
|
|||
|
(defcustom magit-cherry-sections-hook
|
|||
|
'(magit-insert-cherry-head-line
|
|||
|
magit-insert-cherry-upstream-line
|
|||
|
magit-insert-cherry-help-lines
|
|||
|
magit-insert-empty-line
|
|||
|
magit-insert-cherry-commits)
|
|||
|
"Hook run to insert sections into the cherry buffer."
|
|||
|
:package-version '(magit . "1.4.0")
|
|||
|
:group 'magit-modes
|
|||
|
:type 'hook)
|
|||
|
|
|||
|
;;;; Custom Faces
|
|||
|
|
|||
|
(defface magit-section-title
|
|||
|
'((t :inherit header-line))
|
|||
|
"Face for section titles."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-branch
|
|||
|
'((((class color) (background light))
|
|||
|
:background "Grey85"
|
|||
|
:foreground "LightSkyBlue4")
|
|||
|
(((class color) (background dark))
|
|||
|
:background "Grey13"
|
|||
|
:foreground "LightSkyBlue1"))
|
|||
|
"Face for branches."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-tag
|
|||
|
'((((class color) (background light))
|
|||
|
:background "LemonChiffon1"
|
|||
|
:foreground "goldenrod4")
|
|||
|
(((class color) (background dark))
|
|||
|
:background "LemonChiffon1"
|
|||
|
:foreground "goldenrod4"))
|
|||
|
"Face for tags."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-file-header
|
|||
|
'((t :inherit diff-file-header))
|
|||
|
"Face for diff file header lines."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-hunk-header
|
|||
|
'((t :inherit diff-hunk-header))
|
|||
|
"Face for diff hunk header lines."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-add
|
|||
|
'((t :inherit diff-added))
|
|||
|
"Face for lines in a diff that have been added."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-del
|
|||
|
'((t :inherit diff-removed))
|
|||
|
"Face for lines in a diff that have been deleted."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-none
|
|||
|
'((t :inherit diff-context))
|
|||
|
"Face for lines in a diff that are unchanged."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-merge-current
|
|||
|
'((t :inherit font-lock-preprocessor-face))
|
|||
|
"Face for merge conflict marker 'current' line."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-merge-separator
|
|||
|
'((t :inherit font-lock-preprocessor-face))
|
|||
|
"Face for merge conflict marker seperator."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-merge-diff3-separator
|
|||
|
'((t :inherit font-lock-preprocessor-face))
|
|||
|
"Face for merge conflict marker seperator."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-diff-merge-proposed
|
|||
|
'((t :inherit font-lock-preprocessor-face))
|
|||
|
"Face for merge conflict marker 'proposed' line."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-graph
|
|||
|
'((((class color) (background light))
|
|||
|
:foreground "grey11")
|
|||
|
(((class color) (background dark))
|
|||
|
:foreground "grey80"))
|
|||
|
"Face for the graph element of the log output."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-sha1
|
|||
|
'((((class color) (background light))
|
|||
|
:foreground "firebrick")
|
|||
|
(((class color) (background dark))
|
|||
|
:foreground "tomato"))
|
|||
|
"Face for the sha1 element of the log output."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-author
|
|||
|
'((((class color) (background light))
|
|||
|
:foreground "firebrick")
|
|||
|
(((class color) (background dark))
|
|||
|
:foreground "tomato"))
|
|||
|
"Face for the author element of the log output."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-date
|
|||
|
'((t))
|
|||
|
"Face for the date element of the log output."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-message
|
|||
|
'((t))
|
|||
|
"Face for the message element of the log output."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-cherry-unmatched
|
|||
|
'((t :foreground "cyan"))
|
|||
|
"Face for unmatched cherry commits.")
|
|||
|
|
|||
|
(defface magit-cherry-equivalent
|
|||
|
'((t :foreground "magenta"))
|
|||
|
"Face for equivalent cherry commits.")
|
|||
|
|
|||
|
(defface magit-item-highlight
|
|||
|
'((t :inherit secondary-selection))
|
|||
|
"Face for highlighting the current item."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-item-mark
|
|||
|
'((t :inherit highlight))
|
|||
|
"Face for highlighting marked item."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-bisect-good
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "light green"
|
|||
|
:foreground "dark olive green")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "light green"
|
|||
|
:foreground "dark olive green"))
|
|||
|
"Face for good bisect refs."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-bisect-skip
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "light goldenrod"
|
|||
|
:foreground "dark goldenrod")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "light goldenrod"
|
|||
|
:foreground "dark goldenrod"))
|
|||
|
"Face for skipped bisect refs."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-bisect-bad
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "IndianRed1"
|
|||
|
:foreground "IndianRed4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "IndianRed1"
|
|||
|
:foreground "IndianRed4"))
|
|||
|
"Face for bad bisect refs."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-remote
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey85"
|
|||
|
:foreground "OliveDrab4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey11"
|
|||
|
:foreground "DarkSeaGreen2"))
|
|||
|
"Face for remote branch head labels shown in log buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-tags
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "LemonChiffon1"
|
|||
|
:foreground "goldenrod4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "LemonChiffon1"
|
|||
|
:foreground "goldenrod4"))
|
|||
|
"Face for tag labels shown in log buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-patches
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "IndianRed1"
|
|||
|
:foreground "IndianRed4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "IndianRed1"
|
|||
|
:foreground "IndianRed4"))
|
|||
|
"Face for Stacked Git patches."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-whitespace-warning-face
|
|||
|
'((t :inherit trailing-whitespace))
|
|||
|
"Face for highlighting whitespace errors in Magit diffs."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-local
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey85"
|
|||
|
:foreground "LightSkyBlue4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey13"
|
|||
|
:foreground "LightSkyBlue1"))
|
|||
|
"Face for local branch head labels shown in log buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-head
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey70"
|
|||
|
:foreground "Black")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey20"
|
|||
|
:foreground "White"))
|
|||
|
"Face for working branch head labels shown in log buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-default
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey50")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey50"))
|
|||
|
"Face for unknown ref labels shown in log buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-head-label-wip
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey95"
|
|||
|
:foreground "LightSkyBlue3")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey07"
|
|||
|
:foreground "LightSkyBlue4"))
|
|||
|
"Face for git-wip labels shown in log buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-signature-good
|
|||
|
'((t :foreground "green"))
|
|||
|
"Face for good signatures."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-signature-bad
|
|||
|
'((t :foreground "red"))
|
|||
|
"Face for bad signatures."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-signature-untrusted
|
|||
|
'((t :foreground "cyan"))
|
|||
|
"Face for good untrusted signatures."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-signature-none
|
|||
|
'((t :inherit magit-log-message))
|
|||
|
"Face for unsigned commits."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
|
|||
|
(defface magit-log-reflog-label-commit
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "LemonChiffon1"
|
|||
|
:foreground "goldenrod4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "LemonChiffon1"
|
|||
|
:foreground "goldenrod4"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-amend
|
|||
|
'((t :inherit magit-log-reflog-label-commit))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-merge
|
|||
|
'((t :inherit magit-log-reflog-label-commit))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-checkout
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey85"
|
|||
|
:foreground "LightSkyBlue4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey13"
|
|||
|
:foreground "LightSkyBlue1"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-reset
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "IndianRed1"
|
|||
|
:foreground "IndianRed4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "IndianRed1"
|
|||
|
:foreground "IndianRed4"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-rebase
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey85"
|
|||
|
:foreground "OliveDrab4")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey11"
|
|||
|
:foreground "DarkSeaGreen2"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-cherry-pick
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "light green"
|
|||
|
:foreground "dark olive green")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "light green"
|
|||
|
:foreground "dark olive green"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-remote
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey50")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey50"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-log-reflog-label-other
|
|||
|
'((((class color) (background light))
|
|||
|
:box t
|
|||
|
:background "Grey50")
|
|||
|
(((class color) (background dark))
|
|||
|
:box t
|
|||
|
:background "Grey50"))
|
|||
|
"Face for reflog subject labels shown in reflog buffer."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-process-ok
|
|||
|
'((t :inherit magit-section-title
|
|||
|
:foreground "green"))
|
|||
|
"Face for zero exit-status."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
(defface magit-process-ng
|
|||
|
'((t :inherit magit-section-title
|
|||
|
:foreground "red"))
|
|||
|
"Face for non-zero exit-status."
|
|||
|
:group 'magit-faces)
|
|||
|
|
|||
|
;;;; Keymaps
|
|||
|
|
|||
|
;; Not an option to avoid advertising it.
|
|||
|
(defvar magit-rigid-key-bindings nil
|
|||
|
"Use rigid key bindings instead of thematic key popups.
|
|||
|
If you enable this a lot of functionality is lost. You most
|
|||
|
likely don't want that. This variable only has an effect if
|
|||
|
set before loading libary `magit'.")
|
|||
|
|
|||
|
(when (boundp 'git-commit-mode-map)
|
|||
|
(define-key git-commit-mode-map (kbd "C-c C-d") 'magit-diff-staged))
|
|||
|
|
|||
|
(defvar magit-mode-map
|
|||
|
(let ((map (make-keymap)))
|
|||
|
(suppress-keymap map t)
|
|||
|
(define-key map (kbd "n") 'magit-goto-next-section)
|
|||
|
(define-key map (kbd "p") 'magit-goto-previous-section)
|
|||
|
(define-key map (kbd "^") 'magit-goto-parent-section)
|
|||
|
(define-key map (kbd "M-n") 'magit-goto-next-sibling-section)
|
|||
|
(define-key map (kbd "M-p") 'magit-goto-previous-sibling-section)
|
|||
|
(define-key map (kbd "TAB") 'magit-toggle-section)
|
|||
|
(define-key map (kbd "<backtab>") 'magit-expand-collapse-section)
|
|||
|
(define-key map (kbd "1") 'magit-show-level-1)
|
|||
|
(define-key map (kbd "2") 'magit-show-level-2)
|
|||
|
(define-key map (kbd "3") 'magit-show-level-3)
|
|||
|
(define-key map (kbd "4") 'magit-show-level-4)
|
|||
|
(define-key map (kbd "M-1") 'magit-show-level-1-all)
|
|||
|
(define-key map (kbd "M-2") 'magit-show-level-2-all)
|
|||
|
(define-key map (kbd "M-3") 'magit-show-level-3-all)
|
|||
|
(define-key map (kbd "M-4") 'magit-show-level-4-all)
|
|||
|
(define-key map (kbd "M-h") 'magit-show-only-files)
|
|||
|
(define-key map (kbd "M-H") 'magit-show-only-files-all)
|
|||
|
(define-key map (kbd "M-s") 'magit-show-level-4)
|
|||
|
(define-key map (kbd "M-S") 'magit-show-level-4-all)
|
|||
|
(define-key map (kbd "g") 'magit-refresh)
|
|||
|
(define-key map (kbd "G") 'magit-refresh-all)
|
|||
|
(define-key map (kbd "?") 'magit-key-mode-popup-dispatch)
|
|||
|
(define-key map (kbd ":") 'magit-git-command)
|
|||
|
(define-key map (kbd "C-x 4 a") 'magit-add-change-log-entry-other-window)
|
|||
|
(define-key map (kbd "L") 'magit-add-change-log-entry)
|
|||
|
(define-key map (kbd "RET") 'magit-visit-item)
|
|||
|
(define-key map (kbd "C-<return>") 'magit-dired-jump)
|
|||
|
(define-key map (kbd "SPC") 'magit-show-item-or-scroll-up)
|
|||
|
(define-key map (kbd "DEL") 'magit-show-item-or-scroll-down)
|
|||
|
(define-key map (kbd "C-w") 'magit-copy-item-as-kill)
|
|||
|
(cond (magit-rigid-key-bindings
|
|||
|
(define-key map (kbd "c") 'magit-commit)
|
|||
|
(define-key map (kbd "m") 'magit-merge)
|
|||
|
(define-key map (kbd "b") 'magit-checkout)
|
|||
|
(define-key map (kbd "M") 'magit-branch-manager)
|
|||
|
(define-key map (kbd "r") 'undefined)
|
|||
|
(define-key map (kbd "f") 'magit-fetch-current)
|
|||
|
(define-key map (kbd "F") 'magit-pull)
|
|||
|
(define-key map (kbd "J") 'magit-apply-mailbox)
|
|||
|
(define-key map (kbd "!") 'magit-git-command-topdir)
|
|||
|
(define-key map (kbd "P") 'magit-push)
|
|||
|
(define-key map (kbd "t") 'magit-tag)
|
|||
|
(define-key map (kbd "l") 'magit-log)
|
|||
|
(define-key map (kbd "o") 'magit-submodule-update)
|
|||
|
(define-key map (kbd "B") 'undefined)
|
|||
|
(define-key map (kbd "z") 'magit-stash))
|
|||
|
(t
|
|||
|
(define-key map (kbd "c") 'magit-key-mode-popup-committing)
|
|||
|
(define-key map (kbd "m") 'magit-key-mode-popup-merging)
|
|||
|
(define-key map (kbd "b") 'magit-key-mode-popup-branching)
|
|||
|
(define-key map (kbd "M") 'magit-key-mode-popup-remoting)
|
|||
|
(define-key map (kbd "r") 'magit-key-mode-popup-rewriting)
|
|||
|
(define-key map (kbd "f") 'magit-key-mode-popup-fetching)
|
|||
|
(define-key map (kbd "F") 'magit-key-mode-popup-pulling)
|
|||
|
(define-key map (kbd "J") 'magit-key-mode-popup-apply-mailbox)
|
|||
|
(define-key map (kbd "!") 'magit-key-mode-popup-running)
|
|||
|
(define-key map (kbd "P") 'magit-key-mode-popup-pushing)
|
|||
|
(define-key map (kbd "t") 'magit-key-mode-popup-tagging)
|
|||
|
(define-key map (kbd "l") 'magit-key-mode-popup-logging)
|
|||
|
(define-key map (kbd "o") 'magit-key-mode-popup-submodule)
|
|||
|
(define-key map (kbd "B") 'magit-key-mode-popup-bisecting)
|
|||
|
(define-key map (kbd "z") 'magit-key-mode-popup-stashing)))
|
|||
|
(define-key map (kbd "$") 'magit-process)
|
|||
|
(define-key map (kbd "E") 'magit-interactive-rebase)
|
|||
|
(define-key map (kbd "R") 'magit-rebase-step)
|
|||
|
(define-key map (kbd "e") 'magit-ediff)
|
|||
|
(define-key map (kbd "w") 'magit-wazzup)
|
|||
|
(define-key map (kbd "y") 'magit-cherry)
|
|||
|
(define-key map (kbd "q") 'magit-mode-quit-window)
|
|||
|
(define-key map (kbd "x") 'magit-reset-head)
|
|||
|
(define-key map (kbd "v") 'magit-revert-item)
|
|||
|
(define-key map (kbd "a") 'magit-apply-item)
|
|||
|
(define-key map (kbd "A") 'magit-cherry-pick-item)
|
|||
|
(define-key map (kbd "d") 'magit-diff-working-tree)
|
|||
|
(define-key map (kbd "D") 'magit-diff)
|
|||
|
(define-key map (kbd "-") 'magit-diff-smaller-hunks)
|
|||
|
(define-key map (kbd "+") 'magit-diff-larger-hunks)
|
|||
|
(define-key map (kbd "0") 'magit-diff-default-hunks)
|
|||
|
(define-key map (kbd "h") 'magit-key-mode-popup-diff-options)
|
|||
|
(define-key map (kbd "H") 'magit-diff-toggle-refine-hunk)
|
|||
|
(define-key map (kbd "S") 'magit-stage-all)
|
|||
|
(define-key map (kbd "U") 'magit-unstage-all)
|
|||
|
(define-key map (kbd "X") 'magit-reset-working-tree)
|
|||
|
(define-key map (kbd "C-c C-c") 'magit-key-mode-popup-dispatch)
|
|||
|
(define-key map (kbd "C-c C-e") 'magit-key-mode-popup-dispatch)
|
|||
|
map)
|
|||
|
"Parent keymap for all keymaps of modes derived from `magit-mode'.")
|
|||
|
|
|||
|
(defvar magit-commit-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
(define-key map (kbd "C-c C-b") 'magit-go-backward)
|
|||
|
(define-key map (kbd "C-c C-f") 'magit-go-forward)
|
|||
|
(define-key map (kbd "SPC") 'scroll-up)
|
|||
|
(define-key map (kbd "DEL") 'scroll-down)
|
|||
|
(define-key map (kbd "j") 'magit-jump-to-diffstats)
|
|||
|
map)
|
|||
|
"Keymap for `magit-commit-mode'.")
|
|||
|
|
|||
|
(defvar magit-status-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
(define-key map (kbd "s") 'magit-stage-item)
|
|||
|
(define-key map (kbd "u") 'magit-unstage-item)
|
|||
|
(define-key map (kbd "i") 'magit-ignore-item)
|
|||
|
(define-key map (kbd "I") 'magit-ignore-item-locally)
|
|||
|
(define-key map (kbd "j") 'magit-section-jump-map)
|
|||
|
(define-key map (kbd ".") 'magit-mark-item)
|
|||
|
(define-key map (kbd "=") 'magit-diff-with-mark)
|
|||
|
(define-key map (kbd "k") 'magit-discard-item)
|
|||
|
(define-key map (kbd "C") 'magit-commit-add-log)
|
|||
|
map)
|
|||
|
"Keymap for `magit-status-mode'.")
|
|||
|
|
|||
|
(eval-after-load 'dired-x
|
|||
|
'(define-key magit-status-mode-map [remap dired-jump] 'magit-dired-jump))
|
|||
|
|
|||
|
(defvar magit-log-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
(define-key map (kbd ".") 'magit-mark-item)
|
|||
|
(define-key map (kbd "=") 'magit-diff-with-mark)
|
|||
|
(define-key map (kbd "e") 'magit-log-show-more-entries)
|
|||
|
(define-key map (kbd "h") 'magit-log-toggle-margin)
|
|||
|
map)
|
|||
|
"Keymap for `magit-log-mode'.")
|
|||
|
|
|||
|
(defvar magit-cherry-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
map)
|
|||
|
"Keymap for `magit-cherry-mode'.")
|
|||
|
|
|||
|
(defvar magit-reflog-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-log-mode-map)
|
|||
|
map)
|
|||
|
"Keymap for `magit-reflog-mode'.")
|
|||
|
|
|||
|
(defvar magit-diff-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
(define-key map (kbd "C-c C-b") 'magit-go-backward)
|
|||
|
(define-key map (kbd "C-c C-f") 'magit-go-forward)
|
|||
|
(define-key map (kbd "SPC") 'scroll-up)
|
|||
|
(define-key map (kbd "DEL") 'scroll-down)
|
|||
|
(define-key map (kbd "j") 'magit-jump-to-diffstats)
|
|||
|
map)
|
|||
|
"Keymap for `magit-diff-mode'.")
|
|||
|
|
|||
|
(defvar magit-wazzup-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
(define-key map (kbd ".") 'magit-mark-item)
|
|||
|
(define-key map (kbd "=") 'magit-diff-with-mark)
|
|||
|
(define-key map (kbd "i") 'magit-ignore-item)
|
|||
|
map)
|
|||
|
"Keymap for `magit-wazzup-mode'.")
|
|||
|
|
|||
|
(defvar magit-branch-manager-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
(define-key map (kbd "c") 'magit-create-branch)
|
|||
|
(define-key map (kbd "a") 'magit-add-remote)
|
|||
|
(define-key map (kbd "r") 'magit-rename-item)
|
|||
|
(define-key map (kbd "k") 'magit-discard-item)
|
|||
|
(define-key map (kbd "T") 'magit-change-what-branch-tracks)
|
|||
|
map)
|
|||
|
"Keymap for `magit-branch-manager-mode'.")
|
|||
|
|
|||
|
(defvar magit-process-mode-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(set-keymap-parent map magit-mode-map)
|
|||
|
map)
|
|||
|
"Keymap for `magit-process-mode'.")
|
|||
|
|
|||
|
(defvar magit-section-jump-map
|
|||
|
(let ((map (make-sparse-keymap)))
|
|||
|
(define-key map (kbd "z") 'magit-jump-to-stashes)
|
|||
|
(define-key map (kbd "n") 'magit-jump-to-untracked)
|
|||
|
(define-key map (kbd "u") 'magit-jump-to-unstaged)
|
|||
|
(define-key map (kbd "s") 'magit-jump-to-staged)
|
|||
|
(define-key map (kbd "f") 'magit-jump-to-unpulled)
|
|||
|
(define-key map (kbd "p") 'magit-jump-to-unpushed)
|
|||
|
(define-key map (kbd "r") 'magit-jump-to-pending)
|
|||
|
map)
|
|||
|
"Submap for jumping to sections in `magit-status-mode'.")
|
|||
|
(fset 'magit-section-jump-map magit-section-jump-map)
|
|||
|
|
|||
|
(easy-menu-define magit-mode-menu magit-mode-map
|
|||
|
"Magit menu"
|
|||
|
'("Magit"
|
|||
|
["Refresh" magit-refresh t]
|
|||
|
["Refresh all" magit-refresh-all t]
|
|||
|
"---"
|
|||
|
["Stage" magit-stage-item t]
|
|||
|
["Stage all" magit-stage-all t]
|
|||
|
["Unstage" magit-unstage-item t]
|
|||
|
["Unstage all" magit-unstage-all t]
|
|||
|
["Commit" magit-key-mode-popup-committing t]
|
|||
|
["Add log entry" magit-commit-add-log t]
|
|||
|
["Tag" magit-tag t]
|
|||
|
"---"
|
|||
|
["Diff working tree" magit-diff-working-tree t]
|
|||
|
["Diff" magit-diff t]
|
|||
|
("Log"
|
|||
|
["Short Log" magit-log t]
|
|||
|
["Long Log" magit-log-long t]
|
|||
|
["Reflog" magit-reflog t]
|
|||
|
["Extended..." magit-key-mode-popup-logging t])
|
|||
|
"---"
|
|||
|
["Cherry pick" magit-cherry-pick-item t]
|
|||
|
["Apply" magit-apply-item t]
|
|||
|
["Revert" magit-revert-item t]
|
|||
|
"---"
|
|||
|
["Ignore" magit-ignore-item t]
|
|||
|
["Ignore locally" magit-ignore-item-locally t]
|
|||
|
["Discard" magit-discard-item t]
|
|||
|
["Reset head" magit-reset-head t]
|
|||
|
["Reset working tree" magit-reset-working-tree t]
|
|||
|
["Stash" magit-stash t]
|
|||
|
["Snapshot" magit-stash-snapshot t]
|
|||
|
"---"
|
|||
|
["Branch..." magit-checkout t]
|
|||
|
["Merge" magit-merge t]
|
|||
|
["Interactive resolve" magit-interactive-resolve t]
|
|||
|
["Rebase" magit-rebase-step t]
|
|||
|
("Rewrite"
|
|||
|
["Start" magit-rewrite-start t]
|
|||
|
["Stop" magit-rewrite-stop t]
|
|||
|
["Finish" magit-rewrite-finish t]
|
|||
|
["Abort" magit-rewrite-abort t]
|
|||
|
["Set used" magit-rewrite-set-used t]
|
|||
|
["Set unused" magit-rewrite-set-unused t])
|
|||
|
"---"
|
|||
|
["Push" magit-push t]
|
|||
|
["Pull" magit-pull t]
|
|||
|
["Remote update" magit-remote-update t]
|
|||
|
("Submodule"
|
|||
|
["Submodule update" magit-submodule-update t]
|
|||
|
["Submodule update and init" magit-submodule-update-init t]
|
|||
|
["Submodule init" magit-submodule-init t]
|
|||
|
["Submodule sync" magit-submodule-sync t])
|
|||
|
"---"
|
|||
|
("Extensions")
|
|||
|
"---"
|
|||
|
["Display Git output" magit-process t]
|
|||
|
["Quit Magit" magit-mode-quit-window t]))
|
|||
|
|
|||
|
|
|||
|
;;; Utilities (1)
|
|||
|
;;;; Minibuffer Input
|
|||
|
|
|||
|
(defun magit-ido-completing-read
|
|||
|
(prompt choices &optional predicate require-match initial-input hist def)
|
|||
|
"ido-based completing-read almost-replacement."
|
|||
|
(require 'ido)
|
|||
|
(let ((reply (ido-completing-read
|
|||
|
prompt
|
|||
|
(if (consp (car choices))
|
|||
|
(mapcar #'car choices)
|
|||
|
choices)
|
|||
|
predicate require-match initial-input hist def)))
|
|||
|
(or (and (consp (car choices))
|
|||
|
(cdr (assoc reply choices)))
|
|||
|
reply)))
|
|||
|
|
|||
|
(defun magit-builtin-completing-read
|
|||
|
(prompt choices &optional predicate require-match initial-input hist def)
|
|||
|
"Magit wrapper for standard `completing-read' function."
|
|||
|
(let ((reply (completing-read
|
|||
|
(if (and def (> (length prompt) 2)
|
|||
|
(string-equal ": " (substring prompt -2)))
|
|||
|
(format "%s (default %s): " (substring prompt 0 -2) def)
|
|||
|
prompt)
|
|||
|
choices predicate require-match initial-input hist def)))
|
|||
|
(if (string= reply "")
|
|||
|
(if require-match
|
|||
|
(user-error "Nothing selected")
|
|||
|
nil)
|
|||
|
reply)))
|
|||
|
|
|||
|
(defun magit-completing-read
|
|||
|
(prompt collection &optional predicate require-match initial-input hist def)
|
|||
|
"Call function in `magit-completing-read-function' to read user input.
|
|||
|
|
|||
|
Read `completing-read' documentation for the meaning of the argument."
|
|||
|
(funcall magit-completing-read-function
|
|||
|
(concat prompt ": ") collection predicate
|
|||
|
require-match initial-input hist def))
|
|||
|
|
|||
|
(defvar magit-gpg-secret-key-hist nil)
|
|||
|
|
|||
|
(defun magit-read-gpg-secret-key (prompt)
|
|||
|
(let ((keys (mapcar
|
|||
|
(lambda (key)
|
|||
|
(list (epg-sub-key-id (car (epg-key-sub-key-list key)))
|
|||
|
(let ((id-obj (car (epg-key-user-id-list key)))
|
|||
|
(id-str nil))
|
|||
|
(when id-obj
|
|||
|
(setq id-str (epg-user-id-string id-obj))
|
|||
|
(if (stringp id-str)
|
|||
|
id-str
|
|||
|
(epg-decode-dn id-obj))))))
|
|||
|
(epg-list-keys (epg-make-context epa-protocol) nil t))))
|
|||
|
(magit-completing-read prompt keys nil nil nil 'magit-gpg-secret-key-hist
|
|||
|
(car (or magit-gpg-secret-key-hist keys)))))
|
|||
|
|
|||
|
;;;; Various Utilities
|
|||
|
|
|||
|
(defmacro magit-bind-match-strings (varlist &rest body)
|
|||
|
(declare (indent 1))
|
|||
|
(let ((i 0))
|
|||
|
`(let ,(mapcar (lambda (var)
|
|||
|
(list var (list 'match-string (cl-incf i))))
|
|||
|
varlist)
|
|||
|
,@body)))
|
|||
|
|
|||
|
(defun magit-file-line (file)
|
|||
|
"Return the first line of FILE as a string."
|
|||
|
(when (file-regular-p file)
|
|||
|
(with-temp-buffer
|
|||
|
(insert-file-contents file)
|
|||
|
(buffer-substring-no-properties (point-min)
|
|||
|
(line-end-position)))))
|
|||
|
|
|||
|
(defun magit-file-lines (file &optional keep-empty-lines)
|
|||
|
"Return a list of strings containing one element per line in FILE.
|
|||
|
Unless optional argument KEEP-EMPTY-LINES is t, trim all empty lines."
|
|||
|
(when (file-regular-p file)
|
|||
|
(with-temp-buffer
|
|||
|
(insert-file-contents file)
|
|||
|
(split-string (buffer-string) "\n" (not keep-empty-lines)))))
|
|||
|
|
|||
|
(defvar-local magit-file-name ()
|
|||
|
"Name of file the buffer shows a different version of.")
|
|||
|
|
|||
|
(defun magit-buffer-file-name (&optional relative)
|
|||
|
(let* ((topdir (magit-get-top-dir))
|
|||
|
(filename (or buffer-file-name
|
|||
|
(when (buffer-base-buffer)
|
|||
|
(with-current-buffer (buffer-base-buffer)
|
|||
|
buffer-file-name))
|
|||
|
(when magit-file-name
|
|||
|
(expand-file-name magit-file-name topdir)))))
|
|||
|
(when filename
|
|||
|
(setq filename (file-truename filename))
|
|||
|
(if relative
|
|||
|
(file-relative-name filename topdir)
|
|||
|
filename))))
|
|||
|
|
|||
|
(defun magit-format-duration (duration spec width)
|
|||
|
(cl-destructuring-bind (char unit units weight)
|
|||
|
(car spec)
|
|||
|
(let ((cnt (round (/ duration weight 1.0))))
|
|||
|
(if (or (not (cdr spec))
|
|||
|
(>= (/ duration weight) 1))
|
|||
|
(if (= width 1)
|
|||
|
(format "%3i%c" cnt char)
|
|||
|
(format (format "%%3i %%-%is" width) cnt
|
|||
|
(if (= cnt 1) unit units)))
|
|||
|
(magit-format-duration duration (cdr spec) width)))))
|
|||
|
|
|||
|
(defun magit-flatten-onelevel (list)
|
|||
|
(cl-mapcan (lambda (elt)
|
|||
|
(cond ((consp elt) (copy-sequence elt))
|
|||
|
(elt (list elt))))
|
|||
|
list))
|
|||
|
|
|||
|
(defun magit-insert (string face &rest args)
|
|||
|
(if magit-use-overlays
|
|||
|
(let ((start (point)))
|
|||
|
(insert string)
|
|||
|
(let ((ov (make-overlay start (point) nil t)))
|
|||
|
(overlay-put ov 'face face)
|
|||
|
;; (overlay-put ov 'priority 10)
|
|||
|
(overlay-put ov 'evaporate t)))
|
|||
|
(insert (propertize string 'face face)))
|
|||
|
(apply #'insert args))
|
|||
|
|
|||
|
(defun magit-put-face-property (start end face)
|
|||
|
(if magit-use-overlays
|
|||
|
(let ((ov (make-overlay start end nil t)))
|
|||
|
(overlay-put ov 'face face)
|
|||
|
;; (overlay-put ov 'priority 10)
|
|||
|
(overlay-put ov 'evaporate t))
|
|||
|
(put-text-property start end 'face face)))
|
|||
|
|
|||
|
;;;; Buffer Margins
|
|||
|
|
|||
|
(defun magit-set-buffer-margin (width enable)
|
|||
|
(let ((window (get-buffer-window)))
|
|||
|
(when window
|
|||
|
(with-selected-window window
|
|||
|
(set-window-margins nil (car (window-margins)) (if enable width 0))
|
|||
|
(let ((fn (apply-partially
|
|||
|
(lambda (width)
|
|||
|
(let ((window (get-buffer-window)))
|
|||
|
(when window
|
|||
|
(with-selected-window window
|
|||
|
(set-window-margins nil (car (window-margins))
|
|||
|
width)))))
|
|||
|
width)))
|
|||
|
(if enable
|
|||
|
(add-hook 'window-configuration-change-hook fn nil t)
|
|||
|
(remove-hook 'window-configuration-change-hook fn t)))))))
|
|||
|
|
|||
|
(defun magit-make-margin-overlay (&rest strings)
|
|||
|
(let ((o (make-overlay (point) (line-end-position) nil t)))
|
|||
|
(overlay-put o 'evaporate t)
|
|||
|
(overlay-put o 'before-string
|
|||
|
(propertize "o" 'display
|
|||
|
(list '(margin right-margin)
|
|||
|
(apply #'concat strings))))))
|
|||
|
|
|||
|
(defvar-local magit-log-margin-timeunit-width nil)
|
|||
|
|
|||
|
(defun magit-log-margin-set-timeunit-width ()
|
|||
|
(cl-destructuring-bind (width characterp duration-spec)
|
|||
|
magit-log-margin-spec
|
|||
|
(setq magit-log-margin-timeunit-width
|
|||
|
(if characterp
|
|||
|
1
|
|||
|
(apply 'max (mapcar (lambda (e)
|
|||
|
(max (length (nth 1 e))
|
|||
|
(length (nth 2 e))))
|
|||
|
(symbol-value duration-spec)))))))
|
|||
|
|
|||
|
;;;; Emacsclient Support
|
|||
|
|
|||
|
(defmacro magit-with-emacsclient (server-window &rest body)
|
|||
|
"Arrange for Git to use Emacsclient as \"the git editor\".
|
|||
|
|
|||
|
Git processes that use \"the editor\" have to be asynchronous.
|
|||
|
The use of this macro ensures that such processes inside BODY use
|
|||
|
Emacsclient as \"the editor\" by setting the environment variable
|
|||
|
$GIT_EDITOR accordingly around calls to Git and starting the
|
|||
|
server if necessary."
|
|||
|
(declare (indent 1))
|
|||
|
`(let* ((process-environment process-environment)
|
|||
|
(magit-process-popup-time -1))
|
|||
|
;; Make sure the client is usable.
|
|||
|
(magit-assert-emacsclient "use `magit-with-emacsclient'")
|
|||
|
;; Make sure server-use-tcp's value is valid.
|
|||
|
(unless (featurep 'make-network-process '(:family local))
|
|||
|
(setq server-use-tcp t))
|
|||
|
;; Make sure the server is running.
|
|||
|
(unless server-process
|
|||
|
(when (server-running-p server-name)
|
|||
|
(setq server-name (format "server%s" (emacs-pid)))
|
|||
|
(when (server-running-p server-name)
|
|||
|
(server-force-delete server-name)))
|
|||
|
(server-start))
|
|||
|
;; Tell Git to use the client.
|
|||
|
(setenv "GIT_EDITOR"
|
|||
|
(concat magit-emacsclient-executable
|
|||
|
;; Tell the client where the server file is.
|
|||
|
(and (not server-use-tcp)
|
|||
|
(concat " --socket-name="
|
|||
|
(expand-file-name server-name
|
|||
|
server-socket-dir)))))
|
|||
|
(when server-use-tcp
|
|||
|
(setenv "EMACS_SERVER_FILE"
|
|||
|
(expand-file-name server-name server-auth-dir)))
|
|||
|
;; As last resort fallback to a new Emacs instance.
|
|||
|
(setenv "ALTERNATE_EDITOR"
|
|||
|
(expand-file-name invocation-name invocation-directory))
|
|||
|
;; Git has to be called asynchronously in BODY or we create a
|
|||
|
;; dead lock. By the time Emacsclient is called the dynamic
|
|||
|
;; binding is no longer in effect and our primitives don't
|
|||
|
;; support callbacks. Temporarily set the default value and
|
|||
|
;; restore the old value using a timer.
|
|||
|
(let ((window ,server-window))
|
|||
|
(unless (equal window server-window)
|
|||
|
(run-at-time "1 sec" nil
|
|||
|
(apply-partially (lambda (value)
|
|||
|
(setq server-window value))
|
|||
|
server-window))
|
|||
|
(setq-default server-window window)))
|
|||
|
,@body))
|
|||
|
|
|||
|
(defun magit-use-emacsclient-p ()
|
|||
|
(and magit-emacsclient-executable
|
|||
|
(not (tramp-tramp-file-p default-directory))))
|
|||
|
|
|||
|
(defun magit-assert-emacsclient (action)
|
|||
|
(unless magit-emacsclient-executable
|
|||
|
(user-error "Cannot %s when `magit-emacsclient-executable' is nil" action))
|
|||
|
(when (tramp-tramp-file-p default-directory)
|
|||
|
(user-error "Cannot %s when accessing repository using tramp" action)))
|
|||
|
|
|||
|
;;;; Git Config
|
|||
|
|
|||
|
(defun magit-get (&rest keys)
|
|||
|
"Return the value of Git config entry specified by KEYS."
|
|||
|
(magit-git-string "config" (mapconcat 'identity keys ".")))
|
|||
|
|
|||
|
(defun magit-get-all (&rest keys)
|
|||
|
"Return all values of the Git config entry specified by KEYS."
|
|||
|
(magit-git-lines "config" "--get-all" (mapconcat 'identity keys ".")))
|
|||
|
|
|||
|
(defun magit-get-boolean (&rest keys)
|
|||
|
"Return the boolean value of Git config entry specified by KEYS."
|
|||
|
(magit-git-true "config" "--bool" (mapconcat 'identity keys ".")))
|
|||
|
|
|||
|
(defun magit-set (val &rest keys)
|
|||
|
"Set Git config settings specified by KEYS to VAL."
|
|||
|
(if val
|
|||
|
(magit-git-string "config" (mapconcat 'identity keys ".") val)
|
|||
|
(magit-git-string "config" "--unset" (mapconcat 'identity keys "."))))
|
|||
|
|
|||
|
;;;; Git Low-Level
|
|||
|
|
|||
|
(defun magit-git-repo-p (dir)
|
|||
|
(file-exists-p (expand-file-name ".git" dir)))
|
|||
|
|
|||
|
(defun magit-git-dir (&optional path)
|
|||
|
"Return absolute path to the GIT_DIR for the current repository.
|
|||
|
If optional PATH is non-nil it has to be a path relative to the
|
|||
|
GIT_DIR and its absolute path is returned"
|
|||
|
(let ((gitdir (magit-git-string "rev-parse" "--git-dir")))
|
|||
|
(when gitdir
|
|||
|
(setq gitdir (file-name-as-directory
|
|||
|
(magit-expand-git-file-name gitdir)))
|
|||
|
(if path
|
|||
|
(expand-file-name (convert-standard-filename path) gitdir)
|
|||
|
gitdir))))
|
|||
|
|
|||
|
(defun magit-no-commit-p ()
|
|||
|
"Return non-nil if there is no commit in the current git repository."
|
|||
|
(not (magit-git-string "rev-list" "-1" "HEAD")))
|
|||
|
|
|||
|
(defun magit-get-top-dir (&optional directory)
|
|||
|
"Return the top directory for the current repository.
|
|||
|
|
|||
|
Determine the repository which contains `default-directory' in
|
|||
|
either its work tree or git control directory and return its top
|
|||
|
directory. If there is no top directory, because the repository
|
|||
|
is bare, return the control directory instead.
|
|||
|
|
|||
|
If optional DIRECTORY is non-nil then return the top directory of
|
|||
|
the repository that contains that instead. DIRECTORY has to be
|
|||
|
an existing directory."
|
|||
|
(setq directory (if directory
|
|||
|
(file-name-as-directory
|
|||
|
(expand-file-name directory))
|
|||
|
default-directory))
|
|||
|
(unless (file-directory-p directory)
|
|||
|
(error "%s isn't an existing directory" directory))
|
|||
|
(let* ((default-directory directory)
|
|||
|
(top (magit-git-string "rev-parse" "--show-toplevel")))
|
|||
|
(if top
|
|||
|
(file-name-as-directory (magit-expand-git-file-name top))
|
|||
|
(let ((gitdir (magit-git-dir)))
|
|||
|
(when gitdir
|
|||
|
(if (magit-bare-repo-p)
|
|||
|
gitdir
|
|||
|
(file-name-directory (directory-file-name gitdir))))))))
|
|||
|
|
|||
|
(defun magit-expand-git-file-name (filename)
|
|||
|
(when (tramp-tramp-file-p default-directory)
|
|||
|
(setq filename (file-relative-name filename
|
|||
|
(with-parsed-tramp-file-name
|
|||
|
default-directory nil
|
|||
|
localname))))
|
|||
|
(expand-file-name filename))
|
|||
|
|
|||
|
(defun magit-file-relative-name (file)
|
|||
|
"Return the path of FILE relative to the repository root.
|
|||
|
If FILE isn't inside a Git repository then return nil."
|
|||
|
(setq file (file-truename file))
|
|||
|
(let ((topdir (magit-get-top-dir (file-name-directory file))))
|
|||
|
(and topdir (substring file (length topdir)))))
|
|||
|
|
|||
|
(defun magit-bare-repo-p ()
|
|||
|
"Return t if the current repository is bare."
|
|||
|
(magit-git-true "rev-parse" "--is-bare-repository"))
|
|||
|
|
|||
|
(defun magit-get-ref (ref)
|
|||
|
(magit-git-string "symbolic-ref" "-q" ref))
|
|||
|
|
|||
|
(defun magit-get-current-branch ()
|
|||
|
(let ((head (magit-get-ref "HEAD")))
|
|||
|
(when (and head (string-match "^refs/heads/" head))
|
|||
|
(substring head 11))))
|
|||
|
|
|||
|
(defun magit-get-remote/branch (&optional branch verify)
|
|||
|
"Return the remote-tracking branch of BRANCH used for pulling.
|
|||
|
Return a string of the form \"REMOTE/BRANCH\".
|
|||
|
|
|||
|
If optional BRANCH is nil return the remote-tracking branch of
|
|||
|
the current branch. If optional VERIFY is non-nil verify that
|
|||
|
the remote branch exists; else return nil."
|
|||
|
(save-match-data
|
|||
|
(let (remote remote-branch remote/branch)
|
|||
|
(and (or branch (setq branch (magit-get-current-branch)))
|
|||
|
(setq remote (magit-get "branch" branch "remote"))
|
|||
|
(setq remote-branch (magit-get "branch" branch "merge"))
|
|||
|
(string-match "^refs/heads/\\(.+\\)" remote-branch)
|
|||
|
(setq remote/branch
|
|||
|
(concat remote "/" (match-string 1 remote-branch)))
|
|||
|
(or (not verify)
|
|||
|
(magit-git-success "rev-parse" "--verify" remote/branch))
|
|||
|
remote/branch))))
|
|||
|
|
|||
|
(defun magit-get-tracked-branch (&optional branch qualified pretty)
|
|||
|
"Return the name of the tracking branch the local branch name BRANCH.
|
|||
|
|
|||
|
If optional QUALIFIED is non-nil return the full branch path,
|
|||
|
otherwise try to shorten it to a name (which may fail). If
|
|||
|
optional PRETTY is non-nil additionally format the branch name
|
|||
|
according to option `magit-remote-ref-format'."
|
|||
|
(unless branch
|
|||
|
(setq branch (magit-get-current-branch)))
|
|||
|
(when branch
|
|||
|
(let ((remote (magit-get "branch" branch "remote"))
|
|||
|
(merge (magit-get "branch" branch "merge")))
|
|||
|
(when (and (not merge)
|
|||
|
(not (equal remote ".")))
|
|||
|
(setq merge branch))
|
|||
|
(when (and remote merge)
|
|||
|
(if (string= remote ".")
|
|||
|
(cond (qualified merge)
|
|||
|
((string-match "^refs/heads/" merge)
|
|||
|
(substring merge 11))
|
|||
|
((string-match "^refs/" merge)
|
|||
|
merge))
|
|||
|
(let* ((fetch (mapcar (lambda (f) (split-string f "[+:]" t))
|
|||
|
(magit-get-all "remote" remote "fetch")))
|
|||
|
(match (cadr (assoc merge fetch))))
|
|||
|
(unless match
|
|||
|
(let* ((prefix (nreverse (split-string merge "/")))
|
|||
|
(unique (list (car prefix))))
|
|||
|
(setq prefix (cdr prefix))
|
|||
|
(setq fetch
|
|||
|
(cl-mapcan
|
|||
|
(lambda (f)
|
|||
|
(cl-destructuring-bind (from to) f
|
|||
|
(setq from (nreverse (split-string from "/")))
|
|||
|
(when (equal (car from) "*")
|
|||
|
(list (list (cdr from) to)))))
|
|||
|
fetch))
|
|||
|
(while (and prefix (not match))
|
|||
|
(if (setq match (cadr (assoc prefix fetch)))
|
|||
|
(setq match (concat (substring match 0 -1)
|
|||
|
(mapconcat 'identity unique "/")))
|
|||
|
(push (car prefix) unique)
|
|||
|
(setq prefix (cdr prefix))))))
|
|||
|
(cond ((not match) nil)
|
|||
|
(qualified match)
|
|||
|
((string-match "^refs/remotes/" match)
|
|||
|
(if pretty
|
|||
|
(magit-format-ref match)
|
|||
|
(substring match 13)))
|
|||
|
(t match))))))))
|
|||
|
|
|||
|
(defun magit-get-previous-branch ()
|
|||
|
"Return the refname of the previously checked out branch.
|
|||
|
Return nil if the previously checked out branch no longer exists."
|
|||
|
(magit-name-rev (magit-git-string "rev-parse" "--verify" "@{-1}")))
|
|||
|
|
|||
|
(defun magit-get-current-tag (&optional with-distance-p)
|
|||
|
"Return the closest tag reachable from \"HEAD\".
|
|||
|
|
|||
|
If optional WITH-DISTANCE-P is non-nil then return (TAG COMMITS),
|
|||
|
if it is `dirty' return (TAG COMMIT DIRTY). COMMITS is the number
|
|||
|
of commits in \"HEAD\" but not in TAG and DIRTY is t if there are
|
|||
|
uncommitted changes, nil otherwise."
|
|||
|
(let ((tag (magit-git-string "describe" "--long" "--tags"
|
|||
|
(and (eq with-distance-p 'dirty) "--dirty"))))
|
|||
|
(save-match-data
|
|||
|
(when tag
|
|||
|
(string-match
|
|||
|
"\\(.+\\)-\\(?:0[0-9]*\\|\\([0-9]+\\)\\)-g[0-9a-z]+\\(-dirty\\)?$" tag)
|
|||
|
(if with-distance-p
|
|||
|
(list (match-string 1 tag)
|
|||
|
(string-to-number (or (match-string 2 tag) "0"))
|
|||
|
(and (match-string 3 tag) t))
|
|||
|
(match-string 1 tag))))))
|
|||
|
|
|||
|
(defun magit-get-next-tag (&optional with-distance-p)
|
|||
|
"Return the closest tag from which \"HEAD\" is reachable.
|
|||
|
|
|||
|
If no such tag can be found or if the distance is 0 (in which
|
|||
|
case it is the current tag, not the next) return nil instead.
|
|||
|
|
|||
|
If optional WITH-DISTANCE-P is non-nil then return (TAG COMMITS)
|
|||
|
where COMMITS is the number of commits in TAG but not in \"HEAD\"."
|
|||
|
(let ((rev (magit-git-string "describe" "--contains" "HEAD")))
|
|||
|
(save-match-data
|
|||
|
(when (and rev (string-match "^[^^~]+" rev))
|
|||
|
(let ((tag (match-string 0 rev)))
|
|||
|
(unless (equal tag (magit-get-current-tag))
|
|||
|
(if with-distance-p
|
|||
|
(list tag (car (magit-rev-diff-count tag "HEAD")))
|
|||
|
tag)))))))
|
|||
|
|
|||
|
(defun magit-get-remote (branch)
|
|||
|
"Return the name of the remote for BRANCH.
|
|||
|
If branch is nil or it has no remote, but a remote named
|
|||
|
\"origin\" exists, return that. Otherwise, return nil."
|
|||
|
(let ((remote (or (and branch (magit-get "branch" branch "remote"))
|
|||
|
(and (magit-get "remote" "origin" "url") "origin"))))
|
|||
|
(unless (string= remote "")
|
|||
|
remote)))
|
|||
|
|
|||
|
(defun magit-get-current-remote ()
|
|||
|
"Return the name of the remote for the current branch.
|
|||
|
If there is no current branch, or no remote for that branch,
|
|||
|
but a remote named \"origin\" is configured, return that.
|
|||
|
Otherwise, return nil."
|
|||
|
(magit-get-remote (magit-get-current-branch)))
|
|||
|
|
|||
|
(defun magit-ref-exists-p (ref)
|
|||
|
(magit-git-success "show-ref" "--verify" ref))
|
|||
|
|
|||
|
(defun magit-rev-parse (&rest args)
|
|||
|
"Execute `git rev-parse ARGS', returning first line of output.
|
|||
|
If there is no output return nil."
|
|||
|
(apply #'magit-git-string "rev-parse" args))
|
|||
|
|
|||
|
(defun magit-ref-ambiguous-p (ref)
|
|||
|
"Return whether or not REF is ambiguous."
|
|||
|
;; An ambiguous ref does not cause `git rev-parse --abbrev-ref'
|
|||
|
;; to exits with a non-zero status. But there is nothing on
|
|||
|
;; stdout in that case.
|
|||
|
(not (magit-git-string "rev-parse" "--abbrev-ref" ref)))
|
|||
|
|
|||
|
(defun magit-rev-diff-count (a b)
|
|||
|
"Return the commits in A but not B and vice versa.
|
|||
|
Return a list of two integers: (A>B B>A)."
|
|||
|
(mapcar 'string-to-number
|
|||
|
(split-string (magit-git-string "rev-list"
|
|||
|
"--count" "--left-right"
|
|||
|
(concat a "..." b))
|
|||
|
"\t")))
|
|||
|
|
|||
|
(defun magit-name-rev (rev &optional no-trim)
|
|||
|
"Return a human-readable name for REV.
|
|||
|
Unlike `git name-rev', this will remove \"tags/\" and \"remotes/\"
|
|||
|
prefixes if that can be done unambiguously (unless optional arg
|
|||
|
NO-TRIM is non-nil). In addition, it will filter out revs
|
|||
|
involving HEAD."
|
|||
|
(when rev
|
|||
|
(let ((name (magit-git-string "name-rev" "--no-undefined" "--name-only" rev)))
|
|||
|
;; There doesn't seem to be a way of filtering HEAD out from name-rev,
|
|||
|
;; so we have to do it manually.
|
|||
|
;; HEAD-based names are too transient to allow.
|
|||
|
(when (and (stringp name)
|
|||
|
(string-match "^\\(.*\\<HEAD\\)\\([~^].*\\|$\\)" name))
|
|||
|
(let ((head-ref (match-string 1 name))
|
|||
|
(modifier (match-string 2 name)))
|
|||
|
;; Sometimes when name-rev gives a HEAD-based name,
|
|||
|
;; rev-parse will give an actual branch or remote name.
|
|||
|
(setq name (concat (magit-git-string "rev-parse" "--abbrev-ref" head-ref)
|
|||
|
modifier))
|
|||
|
;; If rev-parse doesn't give us what we want, just use the SHA.
|
|||
|
(when (or (null name) (string-match-p "\\<HEAD\\>" name))
|
|||
|
(setq name (magit-rev-parse rev)))))
|
|||
|
(setq rev (or name rev))
|
|||
|
(when (string-match "^\\(?:tags\\|remotes\\)/\\(.*\\)" rev)
|
|||
|
(let ((plain-name (match-string 1 rev)))
|
|||
|
(unless (or no-trim (magit-ref-ambiguous-p plain-name))
|
|||
|
(setq rev plain-name))))
|
|||
|
rev)))
|
|||
|
|
|||
|
(defun magit-file-uptodate-p (file)
|
|||
|
(magit-git-success "diff" "--quiet" "--" file))
|
|||
|
|
|||
|
(defun magit-anything-staged-p ()
|
|||
|
(magit-git-failure "diff" "--quiet" "--cached"))
|
|||
|
|
|||
|
(defun magit-anything-unstaged-p ()
|
|||
|
(magit-git-failure "diff" "--quiet"))
|
|||
|
|
|||
|
(defun magit-anything-modified-p ()
|
|||
|
(or (magit-anything-staged-p)
|
|||
|
(magit-anything-unstaged-p)))
|
|||
|
|
|||
|
(defun magit-commit-parents (commit)
|
|||
|
(cdr (split-string (magit-git-string "rev-list" "-1" "--parents" commit))))
|
|||
|
|
|||
|
(defun magit-assert-one-parent (commit command)
|
|||
|
(when (> (length (magit-commit-parents commit)) 1)
|
|||
|
(user-error "Cannot %s a merge commit" command)))
|
|||
|
|
|||
|
(defun magit-decode-git-path (path)
|
|||
|
(if (eq (aref path 0) ?\")
|
|||
|
(string-as-multibyte (read path))
|
|||
|
path))
|
|||
|
|
|||
|
(defun magit-abbrev-length ()
|
|||
|
(string-to-number (or (magit-get "core.abbrev") "7")))
|
|||
|
|
|||
|
(defun magit-abbrev-arg ()
|
|||
|
(format "--abbrev=%d" (magit-abbrev-length)))
|
|||
|
|
|||
|
;;;; Git Revisions
|
|||
|
|
|||
|
(defvar magit-uninteresting-refs
|
|||
|
'("^refs/stash$"
|
|||
|
"^refs/remotes/[^/]+/HEAD$"
|
|||
|
"^refs/remotes/[^/]+/top-bases$"
|
|||
|
"^refs/top-bases$"))
|
|||
|
|
|||
|
(cl-defun magit-list-interesting-refs (&optional uninteresting
|
|||
|
(refs nil srefs))
|
|||
|
(cl-loop for ref in (if srefs
|
|||
|
refs
|
|||
|
(mapcar (lambda (l)
|
|||
|
(cadr (split-string l " ")))
|
|||
|
(magit-git-lines "show-ref")))
|
|||
|
with label
|
|||
|
unless (or (cl-loop for i in
|
|||
|
(cl-typecase uninteresting
|
|||
|
(null magit-uninteresting-refs)
|
|||
|
(list uninteresting)
|
|||
|
(string (cons (format "^refs/heads/%s$"
|
|||
|
uninteresting)
|
|||
|
magit-uninteresting-refs)))
|
|||
|
thereis (string-match i ref))
|
|||
|
(not (setq label (magit-format-ref ref))))
|
|||
|
collect (cons label ref)))
|
|||
|
|
|||
|
(defun magit-format-ref (ref)
|
|||
|
(cond ((string-match "refs/heads/\\(.*\\)" ref)
|
|||
|
(match-string 1 ref))
|
|||
|
((string-match "refs/tags/\\(.*\\)" ref)
|
|||
|
(format (if (eq magit-remote-ref-format 'branch-then-remote)
|
|||
|
"%s (tag)"
|
|||
|
"%s")
|
|||
|
(match-string 1 ref)))
|
|||
|
((string-match "refs/remotes/\\([^/]+\\)/\\(.+\\)" ref)
|
|||
|
(if (eq magit-remote-ref-format 'branch-then-remote)
|
|||
|
(format "%s (%s)"
|
|||
|
(match-string 2 ref)
|
|||
|
(match-string 1 ref))
|
|||
|
(substring ref 13)))
|
|||
|
(t ref)))
|
|||
|
|
|||
|
(defvar magit-read-file-hist nil)
|
|||
|
|
|||
|
(defun magit-read-file-from-rev (revision &optional default)
|
|||
|
(unless revision
|
|||
|
(setq revision "HEAD"))
|
|||
|
(let ((default-directory (magit-get-top-dir)))
|
|||
|
(magit-completing-read
|
|||
|
(format "Retrieve file from %s" revision)
|
|||
|
(magit-git-lines "ls-tree" "-r" "-t" "--name-only" revision)
|
|||
|
nil 'require-match
|
|||
|
nil 'magit-read-file-hist
|
|||
|
(or default (magit-buffer-file-name t)))))
|
|||
|
|
|||
|
(defun magit-read-file-trace (ignored)
|
|||
|
(let ((file (magit-read-file-from-rev "HEAD"))
|
|||
|
(trace (read-string "Trace: ")))
|
|||
|
(if (string-match
|
|||
|
"^\\(/.+/\\|:[^:]+\\|[0-9]+,[-+]?[0-9]+\\)\\(:\\)?$" trace)
|
|||
|
(concat trace (or (match-string 2 trace) ":") file)
|
|||
|
(user-error "Trace is invalid, see man git-log"))))
|
|||
|
|
|||
|
(defvar magit-read-rev-history nil
|
|||
|
"The history of inputs to `magit-read-rev' and `magit-read-tag'.")
|
|||
|
|
|||
|
(defun magit-read-tag (prompt &optional require-match)
|
|||
|
(magit-completing-read prompt (magit-git-lines "tag") nil
|
|||
|
require-match nil 'magit-read-rev-history))
|
|||
|
|
|||
|
(defun magit-read-rev (prompt &optional default uninteresting noselection)
|
|||
|
(let* ((interesting-refs
|
|||
|
(mapcar (lambda (elt)
|
|||
|
(setcdr elt (replace-regexp-in-string
|
|||
|
"^refs/heads/" "" (cdr elt)))
|
|||
|
elt)
|
|||
|
(magit-list-interesting-refs uninteresting)))
|
|||
|
(reply (magit-completing-read prompt interesting-refs nil nil nil
|
|||
|
'magit-read-rev-history default))
|
|||
|
(rev (or (cdr (assoc reply interesting-refs)) reply)))
|
|||
|
(when (equal rev ".")
|
|||
|
(setq rev magit-marked-commit))
|
|||
|
(unless (or rev noselection)
|
|||
|
(user-error "No rev selected"))
|
|||
|
rev))
|
|||
|
|
|||
|
(defun magit-read-rev-with-default (prompt)
|
|||
|
(magit-read-rev prompt
|
|||
|
(let ((branch (or (magit-guess-branch) "HEAD")))
|
|||
|
(when branch
|
|||
|
(if (string-match "^refs/\\(.*\\)" branch)
|
|||
|
(match-string 1 branch)
|
|||
|
branch)))))
|
|||
|
|
|||
|
(defun magit-read-rev-range (op &optional def-beg def-end)
|
|||
|
(let ((beg (magit-read-rev (format "%s range or start" op) def-beg)))
|
|||
|
(save-match-data
|
|||
|
(if (string-match "^\\(.+\\)\\.\\.\\(.+\\)$" beg)
|
|||
|
(match-string 0 beg)
|
|||
|
(let ((end (magit-read-rev (format "%s end" op) def-end nil t)))
|
|||
|
(if end (concat beg ".." end) beg))))))
|
|||
|
|
|||
|
(defun magit-read-stash (prompt)
|
|||
|
(let ((n (read-number prompt 0))
|
|||
|
(l (1- (length (magit-git-lines "stash" "list")))))
|
|||
|
(if (> n l)
|
|||
|
(user-error "No stash older than stash@{%i}" l)
|
|||
|
(format "stash@{%i}" n))))
|
|||
|
|
|||
|
(defun magit-read-remote (prompt &optional default require-match)
|
|||
|
(magit-completing-read prompt (magit-git-lines "remote")
|
|||
|
nil require-match nil nil
|
|||
|
(or default (magit-guess-remote))))
|
|||
|
|
|||
|
(defun magit-read-remote-branch (prompt remote &optional default)
|
|||
|
(let ((branch (magit-completing-read
|
|||
|
prompt
|
|||
|
(cl-mapcan
|
|||
|
(lambda (b)
|
|||
|
(and (not (string-match " -> " b))
|
|||
|
(string-match (format "^ *%s/\\(.*\\)$"
|
|||
|
(regexp-quote remote)) b)
|
|||
|
(list (match-string 1 b))))
|
|||
|
(magit-git-lines "branch" "-r"))
|
|||
|
nil nil nil nil default)))
|
|||
|
(unless (string= branch "")
|
|||
|
branch)))
|
|||
|
|
|||
|
(defun magit-format-ref-label (ref)
|
|||
|
(cl-destructuring-bind (re face fn)
|
|||
|
(cl-find-if (lambda (ns)
|
|||
|
(string-match (car ns) ref))
|
|||
|
magit-refs-namespaces)
|
|||
|
(if fn
|
|||
|
(funcall fn ref face)
|
|||
|
(propertize (or (match-string 1 ref) ref) 'face face))))
|
|||
|
|
|||
|
(defun magit-format-ref-labels (string)
|
|||
|
(save-match-data
|
|||
|
(mapconcat 'magit-format-ref-label
|
|||
|
(mapcar 'cdr
|
|||
|
(magit-list-interesting-refs
|
|||
|
nil (split-string string "\\(tag: \\|[(), ]\\)" t)))
|
|||
|
" ")))
|
|||
|
|
|||
|
(defun magit-insert-ref-labels (string)
|
|||
|
(save-match-data
|
|||
|
(dolist (ref (split-string string "\\(tag: \\|[(), ]\\)" t) " ")
|
|||
|
(cl-destructuring-bind (re face fn)
|
|||
|
(cl-find-if (lambda (elt) (string-match (car elt) ref))
|
|||
|
magit-refs-namespaces)
|
|||
|
(if fn
|
|||
|
(let ((text (funcall fn ref face)))
|
|||
|
(magit-insert text (get-text-property 1 'face text) ?\s))
|
|||
|
(magit-insert (or (match-string 1 ref) ref) face ?\s))))))
|
|||
|
|
|||
|
(defun magit-format-rev-summary (rev)
|
|||
|
(let ((s (magit-git-string "log" "-1"
|
|||
|
(concat "--pretty=format:%h %s") rev)))
|
|||
|
(when s
|
|||
|
(string-match " " s)
|
|||
|
(put-text-property 0 (match-beginning 0) 'face 'magit-log-sha1 s)
|
|||
|
s)))
|
|||
|
|
|||
|
;;; Magit Api
|
|||
|
;;;; Section Api
|
|||
|
;;;;; Section Core
|
|||
|
|
|||
|
(cl-defstruct magit-section
|
|||
|
type info
|
|||
|
beginning content-beginning end
|
|||
|
hidden needs-refresh-on-show highlight
|
|||
|
diff-status diff-file2 diff-range
|
|||
|
process
|
|||
|
parent children)
|
|||
|
|
|||
|
(defvar-local magit-root-section nil
|
|||
|
"The root section in the current buffer.
|
|||
|
All other sections are descendants of this section. The value
|
|||
|
of this variable is set by `magit-with-section' and you should
|
|||
|
never modify it.")
|
|||
|
(put 'magit-root-section 'permanent-local t)
|
|||
|
|
|||
|
;;;;; Section Creation
|
|||
|
|
|||
|
(defvar magit-with-section--parent nil
|
|||
|
"For use by `magit-with-section' only.")
|
|||
|
|
|||
|
(defvar magit-with-section--oldroot nil
|
|||
|
"For use by `magit-with-section' only.")
|
|||
|
|
|||
|
(defmacro magit-with-section (arglist &rest body)
|
|||
|
"\n\n(fn (NAME TYPE &optional INFO HEADING NOHIGHLIGHT COLLAPSE) &rest ARGS)"
|
|||
|
(declare (indent 1) (debug ((form form &optional form form form) body)))
|
|||
|
(let ((s (car arglist)))
|
|||
|
`(let ((,s (make-magit-section
|
|||
|
:type ',(nth 1 arglist)
|
|||
|
:info ,(nth 2 arglist)
|
|||
|
:highlight (not ,(nth 4 arglist))
|
|||
|
:beginning (point-marker)
|
|||
|
:content-beginning (point-marker)
|
|||
|
:parent magit-with-section--parent)))
|
|||
|
(setf (magit-section-hidden ,s)
|
|||
|
(let ((old (and magit-with-section--oldroot
|
|||
|
(magit-find-section (magit-section-path ,s)
|
|||
|
magit-with-section--oldroot))))
|
|||
|
(if old
|
|||
|
(magit-section-hidden old)
|
|||
|
,(nth 5 arglist))))
|
|||
|
(let ((magit-with-section--parent ,s)
|
|||
|
(magit-with-section--oldroot
|
|||
|
(or magit-with-section--oldroot
|
|||
|
(unless magit-with-section--parent
|
|||
|
(prog1 magit-root-section
|
|||
|
(setq magit-root-section ,s))))))
|
|||
|
,@body)
|
|||
|
(when ,s
|
|||
|
(set-marker-insertion-type (magit-section-content-beginning ,s) t)
|
|||
|
(let ((heading ,(nth 3 arglist)))
|
|||
|
(when heading
|
|||
|
(save-excursion
|
|||
|
(goto-char (magit-section-beginning ,s))
|
|||
|
(insert
|
|||
|
(if (string-match-p "\n$" heading)
|
|||
|
(substring heading 0 -1)
|
|||
|
(propertize
|
|||
|
(let (c)
|
|||
|
(if (and magit-show-child-count
|
|||
|
(string-match-p ":$" heading)
|
|||
|
(> (setq c (length (magit-section-children ,s))) 0))
|
|||
|
(format "%s (%s):" (substring heading 0 -1) c)
|
|||
|
heading))
|
|||
|
'face 'magit-section-title)))
|
|||
|
(insert "\n"))))
|
|||
|
(set-marker-insertion-type (magit-section-beginning ,s) t)
|
|||
|
(goto-char (max (point) ; smaller if there is no content
|
|||
|
(magit-section-content-beginning ,s)))
|
|||
|
(setf (magit-section-end ,s) (point-marker))
|
|||
|
(save-excursion
|
|||
|
(goto-char (magit-section-beginning ,s))
|
|||
|
(let ((end (magit-section-end ,s)))
|
|||
|
(while (< (point) end)
|
|||
|
(let ((next (or (next-single-property-change
|
|||
|
(point) 'magit-section)
|
|||
|
end)))
|
|||
|
(unless (get-text-property (point) 'magit-section)
|
|||
|
(put-text-property (point) next 'magit-section ,s))
|
|||
|
(goto-char next)))))
|
|||
|
(if (eq ,s magit-root-section)
|
|||
|
(magit-section-set-hidden magit-root-section nil)
|
|||
|
(setf (magit-section-children (magit-section-parent ,s))
|
|||
|
(nconc (magit-section-children (magit-section-parent ,s))
|
|||
|
(list ,s)))))
|
|||
|
,s)))
|
|||
|
|
|||
|
(defmacro magit-cmd-insert-section (arglist washer program &rest args)
|
|||
|
"\n\n(fn (TYPE &optional HEADING) WASHER PROGRAM &rest ARGS)"
|
|||
|
(declare (indent 2))
|
|||
|
`(magit-with-section (section ,(car arglist)
|
|||
|
',(car arglist)
|
|||
|
,(cadr arglist) t)
|
|||
|
(apply #'process-file ,program nil (list t nil) nil
|
|||
|
(magit-flatten-onelevel (list ,@args)))
|
|||
|
(unless (eq (char-before) ?\n)
|
|||
|
(insert "\n"))
|
|||
|
(save-restriction
|
|||
|
(narrow-to-region (magit-section-content-beginning section) (point))
|
|||
|
(goto-char (point-min))
|
|||
|
(funcall ,washer)
|
|||
|
(goto-char (point-max)))
|
|||
|
(let ((parent (magit-section-parent section))
|
|||
|
(head-beg (magit-section-beginning section))
|
|||
|
(body-beg (magit-section-content-beginning section)))
|
|||
|
(if (= (point) body-beg)
|
|||
|
(if (not parent)
|
|||
|
(insert "(empty)\n")
|
|||
|
(delete-region head-beg body-beg)
|
|||
|
(setq section nil))
|
|||
|
(insert "\n")))))
|
|||
|
|
|||
|
(defmacro magit-git-insert-section (arglist washer &rest args)
|
|||
|
"\n\n(fn (TYPE &optional HEADING) WASHER &rest ARGS)"
|
|||
|
(declare (indent 2))
|
|||
|
`(magit-cmd-insert-section ,arglist
|
|||
|
,washer
|
|||
|
magit-git-executable
|
|||
|
magit-git-standard-options ,@args))
|
|||
|
|
|||
|
(defmacro magit-insert-line-section (arglist line)
|
|||
|
"\n\n(fn (TYPE &optional INFO) line)"
|
|||
|
(declare (indent 1))
|
|||
|
(let ((l (cl-gensym "line")))
|
|||
|
`(let ((,l (concat ,line "\n")))
|
|||
|
(when (string-match "^\\([^:]+\\):\\( \\)" ,l)
|
|||
|
(setq ,l (replace-match
|
|||
|
(make-string (max 1 (- magit-status-line-align-to
|
|||
|
(length (match-string 1 ,l))))
|
|||
|
?\s)
|
|||
|
t t ,l 2)))
|
|||
|
(magit-with-section (section ,(car arglist) ',(car arglist) ,l t)
|
|||
|
(setf (magit-section-info section) ,(cadr arglist))))))
|
|||
|
|
|||
|
;;;;; Section Searching
|
|||
|
|
|||
|
(defun magit-find-section (path top)
|
|||
|
"Find the section at the path PATH in subsection of section TOP."
|
|||
|
(if (null path)
|
|||
|
top
|
|||
|
(let ((secs (magit-section-children top)))
|
|||
|
(while (and secs (not (equal (car path)
|
|||
|
(magit-section-info (car secs)))))
|
|||
|
(setq secs (cdr secs)))
|
|||
|
(when (car secs)
|
|||
|
(magit-find-section (cdr path) (car secs))))))
|
|||
|
|
|||
|
(defun magit-section-path (section)
|
|||
|
"Return the path of SECTION."
|
|||
|
(let ((parent (magit-section-parent section)))
|
|||
|
(when parent
|
|||
|
(append (magit-section-path parent)
|
|||
|
(list (magit-section-info section))))))
|
|||
|
|
|||
|
(defun magit-find-section-after (pos)
|
|||
|
"Find the first section that begins after POS."
|
|||
|
(magit-find-section-after* pos (list magit-root-section)))
|
|||
|
|
|||
|
(defun magit-find-section-after* (pos secs)
|
|||
|
"Find the first section that begins after POS in the list SECS
|
|||
|
\(including children of sections in SECS)."
|
|||
|
(while (and secs
|
|||
|
(<= (magit-section-beginning (car secs)) pos))
|
|||
|
(setq secs (if (magit-section-hidden (car secs))
|
|||
|
(cdr secs)
|
|||
|
(append (magit-section-children (car secs))
|
|||
|
(cdr secs)))))
|
|||
|
(car secs))
|
|||
|
|
|||
|
(defun magit-find-section-before (pos)
|
|||
|
"Return the last section that begins before POS."
|
|||
|
(let ((section (magit-find-section-at pos)))
|
|||
|
(cl-do* ((current (or (magit-section-parent section)
|
|||
|
section)
|
|||
|
next)
|
|||
|
(next (unless (magit-section-hidden current)
|
|||
|
(magit-find-section-before*
|
|||
|
pos (magit-section-children current)))
|
|||
|
(unless (magit-section-hidden current)
|
|||
|
(magit-find-section-before*
|
|||
|
pos (magit-section-children current)))))
|
|||
|
((null next) current))))
|
|||
|
|
|||
|
(defun magit-find-section-before* (pos secs)
|
|||
|
"Find the last section that begins before POS in the list SECS."
|
|||
|
(let ((prev nil))
|
|||
|
(while (and secs
|
|||
|
(< (magit-section-beginning (car secs)) pos))
|
|||
|
(setq prev (car secs))
|
|||
|
(setq secs (cdr secs)))
|
|||
|
prev))
|
|||
|
|
|||
|
(defun magit-current-section ()
|
|||
|
"Return the Magit section at point."
|
|||
|
(magit-find-section-at (point)))
|
|||
|
|
|||
|
(defun magit-find-section-at (pos)
|
|||
|
"Return the Magit section at POS."
|
|||
|
(or (get-text-property pos 'magit-section)
|
|||
|
magit-root-section))
|
|||
|
|
|||
|
;;;;; Section Jumping
|
|||
|
|
|||
|
(defun magit-goto-next-section ()
|
|||
|
"Go to the next section."
|
|||
|
(interactive)
|
|||
|
(let ((next (magit-find-section-after (point))))
|
|||
|
(if next
|
|||
|
(magit-goto-section next)
|
|||
|
(message "No next section"))))
|
|||
|
|
|||
|
(defun magit-goto-previous-section ()
|
|||
|
"Go to the previous section."
|
|||
|
(interactive)
|
|||
|
(if (eq (point) 1)
|
|||
|
(message "No previous section")
|
|||
|
(magit-goto-section (magit-find-section-before (point)))))
|
|||
|
|
|||
|
(defun magit-goto-parent-section ()
|
|||
|
"Go to the parent section."
|
|||
|
(interactive)
|
|||
|
(let ((parent (magit-section-parent (magit-current-section))))
|
|||
|
(when parent
|
|||
|
(goto-char (magit-section-beginning parent)))))
|
|||
|
|
|||
|
(defun magit-goto-next-sibling-section ()
|
|||
|
"Go to the next sibling section."
|
|||
|
(interactive)
|
|||
|
(let* ((section (magit-current-section))
|
|||
|
(parent (magit-section-parent section))
|
|||
|
(next (and parent (magit-find-section-after*
|
|||
|
(1- (magit-section-end section))
|
|||
|
(magit-section-children parent)))))
|
|||
|
(if next
|
|||
|
(magit-goto-section next)
|
|||
|
(magit-goto-next-section))))
|
|||
|
|
|||
|
(defun magit-goto-previous-sibling-section ()
|
|||
|
"Go to the previous sibling section."
|
|||
|
(interactive)
|
|||
|
(let* ((section (magit-current-section))
|
|||
|
(parent (magit-section-parent section))
|
|||
|
(prev (and parent (magit-find-section-before*
|
|||
|
(magit-section-beginning section)
|
|||
|
(magit-section-children parent)))))
|
|||
|
(if prev
|
|||
|
(magit-goto-section prev)
|
|||
|
(magit-goto-previous-section))))
|
|||
|
|
|||
|
(defun magit-goto-section (section)
|
|||
|
(goto-char (magit-section-beginning section))
|
|||
|
(cond
|
|||
|
((and magit-log-auto-more
|
|||
|
(eq (magit-section-type section) 'longer))
|
|||
|
(magit-log-show-more-entries)
|
|||
|
(forward-line -1)
|
|||
|
(magit-goto-next-section))
|
|||
|
((and (eq (magit-section-type section) 'commit)
|
|||
|
(derived-mode-p 'magit-log-mode)
|
|||
|
(or (eq (car magit-refresh-args) 'oneline)
|
|||
|
(get-buffer-window magit-commit-buffer-name)))
|
|||
|
(magit-show-commit (magit-section-info section) t))))
|
|||
|
|
|||
|
(defun magit-goto-section-at-path (path)
|
|||
|
"Go to the section described by PATH."
|
|||
|
(let ((sec (magit-find-section path magit-root-section)))
|
|||
|
(if sec
|
|||
|
(goto-char (magit-section-beginning sec))
|
|||
|
(message "No such section"))))
|
|||
|
|
|||
|
(defmacro magit-define-section-jumper (sym title)
|
|||
|
"Define an interactive function to go to section SYM.
|
|||
|
TITLE is the displayed title of the section."
|
|||
|
(let ((fun (intern (format "magit-jump-to-%s" sym))))
|
|||
|
`(progn
|
|||
|
(defun ,fun (&optional expand) ,(format "\
|
|||
|
Jump to section '%s'.
|
|||
|
With a prefix argument also expand it." title)
|
|||
|
(interactive "P")
|
|||
|
(if (magit-goto-section-at-path '(,sym))
|
|||
|
(when expand
|
|||
|
(with-local-quit
|
|||
|
(if (eq magit-expand-staged-on-commit 'full)
|
|||
|
(magit-show-level 4 nil)
|
|||
|
(magit-expand-section)))
|
|||
|
(recenter 0))
|
|||
|
(message ,(format "Section '%s' wasn't found" title))))
|
|||
|
(put ',fun 'definition-name ',sym))))
|
|||
|
|
|||
|
(magit-define-section-jumper stashes "Stashes")
|
|||
|
(magit-define-section-jumper untracked "Untracked files")
|
|||
|
(magit-define-section-jumper unstaged "Unstaged changes")
|
|||
|
(magit-define-section-jumper staged "Staged changes")
|
|||
|
(magit-define-section-jumper unpulled "Unpulled commits")
|
|||
|
(magit-define-section-jumper unpushed "Unpushed commits")
|
|||
|
(magit-define-section-jumper diffstats "Diffstats")
|
|||
|
|
|||
|
;;;;; Section Hooks
|
|||
|
|
|||
|
(defun magit-add-section-hook (hook function &optional at append local)
|
|||
|
"Add to the value of section hook HOOK the function FUNCTION.
|
|||
|
|
|||
|
Add FUNCTION at the beginning of the hook list unless optional
|
|||
|
APPEND is non-nil, in which case FUNCTION is added at the end.
|
|||
|
If FUNCTION already is a member then move it to the new location.
|
|||
|
|
|||
|
If optional AT is non-nil and a member of the hook list, then add
|
|||
|
FUNCTION next to that instead. Add before or after AT depending
|
|||
|
on APPEND. If only FUNCTION is a member of the list, then leave
|
|||
|
it where ever it already is.
|
|||
|
|
|||
|
If optional LOCAL is non-nil, then modify the hook's buffer-local
|
|||
|
value rather than its global value. This makes the hook local by
|
|||
|
copying the default value. That copy is then modified.
|
|||
|
|
|||
|
HOOK should be a symbol. If HOOK is void, it is first set to nil.
|
|||
|
HOOK's value must not be a single hook function. FUNCTION should
|
|||
|
be a function that takes no arguments and inserts one or multiple
|
|||
|
sections at point, moving point forward. FUNCTION may choose not
|
|||
|
to insert its section(s), when doing so would not make sense. It
|
|||
|
should not be abused for other side-effects. To remove FUNCTION
|
|||
|
again use `remove-hook'."
|
|||
|
(or (boundp hook) (set hook nil))
|
|||
|
(or (default-boundp hook) (set-default hook nil))
|
|||
|
(let ((value (if local
|
|||
|
(if (local-variable-p hook)
|
|||
|
(symbol-value hook)
|
|||
|
(unless (local-variable-if-set-p hook)
|
|||
|
(make-local-variable hook))
|
|||
|
(copy-sequence (default-value hook)))
|
|||
|
(default-value hook))))
|
|||
|
(if at
|
|||
|
(when (setq at (member at value))
|
|||
|
(setq value (delq function value))
|
|||
|
(if append
|
|||
|
(push function (cdr at))
|
|||
|
(push (car at) (cdr at))
|
|||
|
(setcar at function)))
|
|||
|
(setq value (delq function value)))
|
|||
|
(unless (member function value)
|
|||
|
(setq value (if append
|
|||
|
(append value (list function))
|
|||
|
(cons function value))))
|
|||
|
(if local
|
|||
|
(set hook value)
|
|||
|
(set-default hook value))))
|
|||
|
|
|||
|
;;;;; Section Utilities
|
|||
|
|
|||
|
(defun magit-map-sections (function section)
|
|||
|
"Apply FUNCTION to SECTION and recursively its subsections."
|
|||
|
(funcall function section)
|
|||
|
(mapc (apply-partially 'magit-map-sections function)
|
|||
|
(magit-section-children section)))
|
|||
|
|
|||
|
(defun magit-wash-sequence (function)
|
|||
|
"Repeatedly call FUNCTION until it returns nil or eob is reached.
|
|||
|
FUNCTION has to move point forward or return nil."
|
|||
|
(while (and (not (eobp)) (funcall function))))
|
|||
|
|
|||
|
(defun magit-section-parent-info (section)
|
|||
|
(setq section (magit-section-parent section))
|
|||
|
(when section (magit-section-info section)))
|
|||
|
|
|||
|
(defun magit-section-siblings (section &optional direction)
|
|||
|
(let ((parent (magit-section-parent section)))
|
|||
|
(when parent
|
|||
|
(let ((siblings (magit-section-children parent)))
|
|||
|
(cl-ecase direction
|
|||
|
(prev (member section (reverse siblings)))
|
|||
|
(next (member section siblings))
|
|||
|
(nil siblings))))))
|
|||
|
|
|||
|
(defun magit-section-region-siblings (&optional key)
|
|||
|
(let ((beg (magit-find-section-at (region-beginning)))
|
|||
|
(end (magit-find-section-at (region-end))))
|
|||
|
(if (eq beg end)
|
|||
|
(list (if key (funcall key beg) beg))
|
|||
|
(goto-char (region-end))
|
|||
|
(when (bolp)
|
|||
|
(setq end (magit-find-section-at (1- (point)))))
|
|||
|
(while (> (length (magit-section-path beg))
|
|||
|
(length (magit-section-path end)))
|
|||
|
(setq beg (magit-section-parent beg)))
|
|||
|
(while (> (length (magit-section-path end))
|
|||
|
(length (magit-section-path beg)))
|
|||
|
(setq end (magit-section-parent end)))
|
|||
|
(let* ((parent (magit-section-parent beg))
|
|||
|
(siblings (magit-section-children parent)))
|
|||
|
(if (eq parent (magit-section-parent end))
|
|||
|
(mapcar (or key #'identity)
|
|||
|
(cl-intersection (memq beg siblings)
|
|||
|
(memq end (reverse siblings))))
|
|||
|
(user-error "Ambitious cross-section region"))))))
|
|||
|
|
|||
|
(defun magit-diff-section-for-diffstat (section)
|
|||
|
(let ((file (magit-section-info section)))
|
|||
|
(cl-find-if (lambda (s)
|
|||
|
(and (eq (magit-section-type s) 'diff)
|
|||
|
(string-equal (magit-section-info s) file)))
|
|||
|
(magit-section-children magit-root-section))))
|
|||
|
|
|||
|
;;;;; Section Visibility
|
|||
|
|
|||
|
(defun magit-section-set-hidden (section hidden)
|
|||
|
"Hide SECTION if HIDDEN is not nil, show it otherwise."
|
|||
|
(setf (magit-section-hidden section) hidden)
|
|||
|
(if (and (not hidden)
|
|||
|
(magit-section-needs-refresh-on-show section))
|
|||
|
(magit-refresh)
|
|||
|
(let ((inhibit-read-only t)
|
|||
|
(beg (save-excursion
|
|||
|
(goto-char (magit-section-beginning section))
|
|||
|
(forward-line)
|
|||
|
(point)))
|
|||
|
(end (magit-section-end section)))
|
|||
|
(when (< beg end)
|
|||
|
(put-text-property beg end 'invisible hidden)))
|
|||
|
(unless hidden
|
|||
|
(dolist (c (magit-section-children section))
|
|||
|
(magit-section-set-hidden c (magit-section-hidden c))))))
|
|||
|
|
|||
|
(defun magit-section-any-hidden (section)
|
|||
|
"Return true if SECTION or any of its children is hidden."
|
|||
|
(or (magit-section-hidden section)
|
|||
|
(let ((kids (magit-section-children section)))
|
|||
|
(while (and kids (not (magit-section-any-hidden (car kids))))
|
|||
|
(setq kids (cdr kids)))
|
|||
|
kids)))
|
|||
|
|
|||
|
(defun magit-section-collapse (section)
|
|||
|
"Show SECTION and hide all its children."
|
|||
|
(dolist (c (magit-section-children section))
|
|||
|
(setf (magit-section-hidden c) t))
|
|||
|
(magit-section-set-hidden section nil))
|
|||
|
|
|||
|
(defun magit-section-expand (section)
|
|||
|
"Show SECTION and all its children."
|
|||
|
(dolist (c (magit-section-children section))
|
|||
|
(setf (magit-section-hidden c) nil))
|
|||
|
(magit-section-set-hidden section nil))
|
|||
|
|
|||
|
(defun magit-section-expand-all-aux (section)
|
|||
|
"Show recursively all SECTION's children."
|
|||
|
(dolist (c (magit-section-children section))
|
|||
|
(setf (magit-section-hidden c) nil)
|
|||
|
(magit-section-expand-all-aux c)))
|
|||
|
|
|||
|
(defun magit-section-expand-all (section)
|
|||
|
"Show SECTION and all its children."
|
|||
|
(magit-section-expand-all-aux section)
|
|||
|
(magit-section-set-hidden section nil))
|
|||
|
|
|||
|
(defun magit-section-hideshow (flag-or-func)
|
|||
|
"Show or hide current section depending on FLAG-OR-FUNC.
|
|||
|
|
|||
|
If FLAG-OR-FUNC is a function, it will be ran on current section.
|
|||
|
IF FLAG-OR-FUNC is a boolean, the section will be hidden if it is
|
|||
|
true, shown otherwise."
|
|||
|
(let ((section (magit-current-section)))
|
|||
|
(when (magit-section-parent section)
|
|||
|
(goto-char (magit-section-beginning section))
|
|||
|
(if (functionp flag-or-func)
|
|||
|
(funcall flag-or-func section)
|
|||
|
(magit-section-set-hidden section flag-or-func)))))
|
|||
|
|
|||
|
(defun magit-show-section ()
|
|||
|
"Show current section."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow nil))
|
|||
|
|
|||
|
(defun magit-hide-section ()
|
|||
|
"Hide current section."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow t))
|
|||
|
|
|||
|
(defun magit-collapse-section ()
|
|||
|
"Hide all subsection of current section."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow #'magit-section-collapse))
|
|||
|
|
|||
|
(defun magit-expand-section ()
|
|||
|
"Show all subsection of current section."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow #'magit-section-expand))
|
|||
|
|
|||
|
(defun magit-toggle-file-section ()
|
|||
|
"Like `magit-toggle-section' but toggle at file granularity."
|
|||
|
(interactive)
|
|||
|
(when (eq (magit-section-type (magit-current-section)) 'hunk)
|
|||
|
(magit-goto-parent-section))
|
|||
|
(magit-toggle-section))
|
|||
|
|
|||
|
(defun magit-toggle-section ()
|
|||
|
"Toggle hidden status of current section."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow
|
|||
|
(lambda (s)
|
|||
|
(magit-section-set-hidden s (not (magit-section-hidden s))))))
|
|||
|
|
|||
|
(defun magit-expand-collapse-section ()
|
|||
|
"Toggle hidden status of subsections of current section."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow
|
|||
|
(lambda (s)
|
|||
|
(cond ((magit-section-any-hidden s)
|
|||
|
(magit-section-expand-all s))
|
|||
|
(t
|
|||
|
(magit-section-collapse s))))))
|
|||
|
|
|||
|
(defun magit-cycle-section ()
|
|||
|
"Cycle between expanded, hidden and collapsed state for current section.
|
|||
|
|
|||
|
Hidden: only the first line of the section is shown
|
|||
|
Collapsed: only the first line of the subsection is shown
|
|||
|
Expanded: everything is shown."
|
|||
|
(interactive)
|
|||
|
(magit-section-hideshow
|
|||
|
(lambda (s)
|
|||
|
(cond ((magit-section-hidden s)
|
|||
|
(magit-section-collapse s))
|
|||
|
((with-no-warnings
|
|||
|
(cl-notany #'magit-section-hidden (magit-section-children s)))
|
|||
|
(magit-section-set-hidden s t))
|
|||
|
(t
|
|||
|
(magit-section-expand s))))))
|
|||
|
|
|||
|
(defun magit-section-lineage (section)
|
|||
|
"Return list of parent, grand-parents... for SECTION."
|
|||
|
(when section
|
|||
|
(cons section (magit-section-lineage (magit-section-parent section)))))
|
|||
|
|
|||
|
(defun magit-section-show-level (section level threshold path)
|
|||
|
(magit-section-set-hidden section (>= level threshold))
|
|||
|
(when (and (< level threshold)
|
|||
|
(not (magit-no-commit-p)))
|
|||
|
(if path
|
|||
|
(magit-section-show-level (car path) (1+ level) threshold (cdr path))
|
|||
|
(dolist (c (magit-section-children section))
|
|||
|
(magit-section-show-level c (1+ level) threshold nil)))))
|
|||
|
|
|||
|
(defun magit-show-level (level all)
|
|||
|
"Show section whose level is less than LEVEL, hide the others.
|
|||
|
If ALL is non nil, do this in all sections, otherwise do it only
|
|||
|
on ancestors and descendants of current section."
|
|||
|
(if all
|
|||
|
(magit-section-show-level magit-root-section 0 level nil)
|
|||
|
(let ((path (reverse (magit-section-lineage (magit-current-section)))))
|
|||
|
(magit-section-show-level (car path) 0 level (cdr path)))))
|
|||
|
|
|||
|
(defun magit-show-only-files ()
|
|||
|
"Show section that are files, but not their subsection.
|
|||
|
|
|||
|
Do this in on ancestors and descendants of current section."
|
|||
|
(interactive)
|
|||
|
(if (derived-mode-p 'magit-status-mode)
|
|||
|
(call-interactively 'magit-show-level-2)
|
|||
|
(call-interactively 'magit-show-level-1)))
|
|||
|
|
|||
|
(defun magit-show-only-files-all ()
|
|||
|
"Show section that are files, but not their subsection.
|
|||
|
Do this for all sections"
|
|||
|
(interactive)
|
|||
|
(if (derived-mode-p 'magit-status-mode)
|
|||
|
(call-interactively 'magit-show-level-2-all)
|
|||
|
(call-interactively 'magit-show-level-1-all)))
|
|||
|
|
|||
|
(defmacro magit-define-level-shower-1 (level all)
|
|||
|
"Define an interactive function to show function of level LEVEL.
|
|||
|
|
|||
|
If ALL is non nil, this function will affect all section,
|
|||
|
otherwise it will affect only ancestors and descendants of
|
|||
|
current section."
|
|||
|
(let ((fun (intern (format "magit-show-level-%s%s"
|
|||
|
level (if all "-all" ""))))
|
|||
|
(doc (format "Show sections on level %s." level)))
|
|||
|
`(defun ,fun ()
|
|||
|
,doc
|
|||
|
(interactive)
|
|||
|
(magit-show-level ,level ,all))))
|
|||
|
|
|||
|
(defmacro magit-define-level-shower (level)
|
|||
|
"Define two interactive function to show function of level LEVEL.
|
|||
|
One for all, one for current lineage."
|
|||
|
`(progn
|
|||
|
(magit-define-level-shower-1 ,level nil)
|
|||
|
(magit-define-level-shower-1 ,level t)))
|
|||
|
|
|||
|
(magit-define-level-shower 1)
|
|||
|
(magit-define-level-shower 2)
|
|||
|
(magit-define-level-shower 3)
|
|||
|
(magit-define-level-shower 4)
|
|||
|
|
|||
|
;;;;; Section Highlighting
|
|||
|
|
|||
|
(defvar-local magit-highlighted-section nil)
|
|||
|
(defvar-local magit-highlight-overlay nil)
|
|||
|
|
|||
|
(defun magit-highlight-section ()
|
|||
|
"Highlight current section.
|
|||
|
If its HIGHLIGHT slot is nil, then don't highlight it."
|
|||
|
(let ((section (magit-current-section))
|
|||
|
(refinep (lambda ()
|
|||
|
(and magit-highlighted-section
|
|||
|
(eq magit-diff-refine-hunk t)
|
|||
|
(eq (magit-section-type magit-highlighted-section)
|
|||
|
'hunk)))))
|
|||
|
(unless (eq section magit-highlighted-section)
|
|||
|
(when (funcall refinep)
|
|||
|
(magit-diff-unrefine-hunk magit-highlighted-section))
|
|||
|
(setq magit-highlighted-section section)
|
|||
|
(unless magit-highlight-overlay
|
|||
|
(overlay-put (setq magit-highlight-overlay (make-overlay 1 1))
|
|||
|
'face magit-item-highlight-face))
|
|||
|
(cond ((and section (magit-section-highlight section))
|
|||
|
(when (funcall refinep)
|
|||
|
(magit-diff-refine-hunk section))
|
|||
|
(move-overlay magit-highlight-overlay
|
|||
|
(magit-section-beginning section)
|
|||
|
(magit-section-end section)
|
|||
|
(current-buffer)))
|
|||
|
(t
|
|||
|
(delete-overlay magit-highlight-overlay))))))
|
|||
|
|
|||
|
;;;;; Section Actions
|
|||
|
|
|||
|
(defun magit-section-context-type (section)
|
|||
|
(cons (magit-section-type section)
|
|||
|
(let ((parent (magit-section-parent section)))
|
|||
|
(when parent
|
|||
|
(magit-section-context-type parent)))))
|
|||
|
|
|||
|
(defun magit-section-match-1 (l1 l2)
|
|||
|
(or (null l1)
|
|||
|
(if (eq (car l1) '*)
|
|||
|
(or (magit-section-match-1 (cdr l1) l2)
|
|||
|
(and l2
|
|||
|
(magit-section-match-1 l1 (cdr l2))))
|
|||
|
(and l2
|
|||
|
(equal (car l1) (car l2))
|
|||
|
(magit-section-match-1 (cdr l1) (cdr l2))))))
|
|||
|
|
|||
|
(defun magit-section-match (condition &optional section)
|
|||
|
(unless section
|
|||
|
(setq section (magit-current-section)))
|
|||
|
(cond ((eq condition t) t)
|
|||
|
((not section) nil)
|
|||
|
((listp condition)
|
|||
|
(cl-find t condition :test
|
|||
|
(lambda (_ condition)
|
|||
|
(magit-section-match condition section))))
|
|||
|
(t
|
|||
|
(magit-section-match-1 (if (symbolp condition)
|
|||
|
(list condition)
|
|||
|
(append condition nil))
|
|||
|
(magit-section-context-type section)))))
|
|||
|
|
|||
|
(defmacro magit-section-case (slots &rest clauses)
|
|||
|
(declare (indent 1))
|
|||
|
`(let* ((it (magit-current-section))
|
|||
|
,@(mapcar
|
|||
|
(lambda (slot)
|
|||
|
`(,slot
|
|||
|
(and it (,(intern (format "magit-section-%s" slot)) it))))
|
|||
|
slots))
|
|||
|
(cond ,@(mapcar (lambda (clause)
|
|||
|
`((magit-section-match ',(car clause) it)
|
|||
|
,@(cdr clause)))
|
|||
|
clauses))))
|
|||
|
|
|||
|
(defconst magit-section-action-success
|
|||
|
(make-symbol "magit-section-action-success"))
|
|||
|
|
|||
|
(defmacro magit-section-action (opname slots &rest clauses)
|
|||
|
(declare (indent 2) (debug (sexp &rest (sexp body))))
|
|||
|
(let ((value (cl-gensym "value")))
|
|||
|
`(let ((,value
|
|||
|
(or (run-hook-wrapped
|
|||
|
',(intern (format "magit-%s-hook" opname))
|
|||
|
(lambda (fn section)
|
|||
|
(when (magit-section-match
|
|||
|
(or (get fn 'magit-section-action-context)
|
|||
|
(error "%s undefined for %s"
|
|||
|
'magit-section-action-context fn))
|
|||
|
section)
|
|||
|
(funcall fn (magit-section-info section))))
|
|||
|
(magit-current-section))
|
|||
|
(magit-section-case ,slots
|
|||
|
,@clauses
|
|||
|
(t (user-error
|
|||
|
(if (magit-current-section)
|
|||
|
,(format "Cannot %s this section" opname)
|
|||
|
,(format "Nothing to %s here" opname))))))))
|
|||
|
(unless (eq ,value magit-section-action-success)
|
|||
|
,value))))
|
|||
|
|
|||
|
;;;; Process Api
|
|||
|
;;;;; Process Commands
|
|||
|
|
|||
|
(defun magit-process ()
|
|||
|
"Display Magit process buffer."
|
|||
|
(interactive)
|
|||
|
(let ((buf (magit-process-buffer)))
|
|||
|
(if (buffer-live-p buf)
|
|||
|
(pop-to-buffer buf)
|
|||
|
(user-error "Process buffer doesn't exist"))))
|
|||
|
|
|||
|
(defun magit-process-kill ()
|
|||
|
"Kill the process at point."
|
|||
|
(interactive)
|
|||
|
(magit-section-case (info)
|
|||
|
(process (if (eq (process-status info) 'run)
|
|||
|
(when (yes-or-no-p "Kill this process? ")
|
|||
|
(kill-process info))
|
|||
|
(user-error "Process isn't running")))))
|
|||
|
|
|||
|
(defvar magit-git-command-history nil)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-git-command (args directory)
|
|||
|
"Execute a Git subcommand asynchronously, displaying the output.
|
|||
|
With a prefix argument run Git in the root of the current
|
|||
|
repository. Non-interactively run Git in DIRECTORY with ARGS."
|
|||
|
(interactive (magit-git-command-read-args))
|
|||
|
(require 'eshell)
|
|||
|
(magit-mode-display-buffer (magit-process-buffer nil t)
|
|||
|
'magit-process-mode 'pop-to-buffer)
|
|||
|
(goto-char (point-max))
|
|||
|
(let ((default-directory directory))
|
|||
|
(magit-run-git-async
|
|||
|
(with-temp-buffer
|
|||
|
(insert args)
|
|||
|
(mapcar 'eval (eshell-parse-arguments (point-min)
|
|||
|
(point-max)))))))
|
|||
|
|
|||
|
(defun magit-git-command-topdir (args directory)
|
|||
|
"Execute a Git subcommand asynchronously, displaying the output.
|
|||
|
Run Git in the root of the current repository.
|
|||
|
\n(fn)" ; arguments are for internal use
|
|||
|
(interactive (magit-git-command-read-args t))
|
|||
|
(magit-git-command args directory))
|
|||
|
|
|||
|
(defun magit-git-command-read-args (&optional root)
|
|||
|
(let ((dir (if (or root current-prefix-arg)
|
|||
|
(or (magit-get-top-dir)
|
|||
|
(user-error "Not inside a Git repository"))
|
|||
|
default-directory)))
|
|||
|
(list (read-string (format "Git subcommand (in %s): "
|
|||
|
(abbreviate-file-name dir))
|
|||
|
nil 'magit-git-command-history)
|
|||
|
dir)))
|
|||
|
|
|||
|
;;;;; Process Mode
|
|||
|
|
|||
|
(define-derived-mode magit-process-mode magit-mode "Magit Process"
|
|||
|
"Mode for looking at git process output.")
|
|||
|
|
|||
|
(defvar magit-process-buffer-name "*magit-process*"
|
|||
|
"Name of buffer where output of processes is put.")
|
|||
|
|
|||
|
(defun magit-process-buffer (&optional topdir create)
|
|||
|
(or (magit-mode-get-buffer magit-process-buffer-name
|
|||
|
'magit-process-mode topdir)
|
|||
|
(with-current-buffer (magit-mode-get-buffer-create
|
|||
|
magit-process-buffer-name
|
|||
|
'magit-process-mode topdir)
|
|||
|
(magit-process-mode)
|
|||
|
(let* ((inhibit-read-only t)
|
|||
|
(s (magit-with-section (section processbuf nil nil t)
|
|||
|
(insert "\n"))))
|
|||
|
(set-marker-insertion-type (magit-section-beginning s) nil)
|
|||
|
(set-marker-insertion-type (magit-section-content-beginning s) nil)
|
|||
|
(current-buffer)))))
|
|||
|
|
|||
|
;;;;; Synchronous Processes
|
|||
|
|
|||
|
(defun magit-git-exit-code (&rest args)
|
|||
|
"Execute Git with ARGS, returning its exit code."
|
|||
|
(apply #'process-file magit-git-executable nil nil nil
|
|||
|
(append magit-git-standard-options
|
|||
|
(magit-flatten-onelevel args))))
|
|||
|
|
|||
|
(defun magit-git-success (&rest args)
|
|||
|
"Execute Git with ARGS, returning t if its exit code is 0."
|
|||
|
(= (apply #'magit-git-exit-code args) 0))
|
|||
|
|
|||
|
(defun magit-git-failure (&rest args)
|
|||
|
"Execute Git with ARGS, returning t if its exit code is 1."
|
|||
|
(= (apply #'magit-git-exit-code args) 1))
|
|||
|
|
|||
|
(defun magit-git-string (&rest args)
|
|||
|
"Execute Git with ARGS, returning the first line of its output.
|
|||
|
If there is no output return nil. If the output begins with a
|
|||
|
newline return an empty string."
|
|||
|
(with-temp-buffer
|
|||
|
(apply #'process-file magit-git-executable nil (list t nil) nil
|
|||
|
(append magit-git-standard-options
|
|||
|
(magit-flatten-onelevel args)))
|
|||
|
(unless (= (point-min) (point-max))
|
|||
|
(goto-char (point-min))
|
|||
|
(buffer-substring-no-properties
|
|||
|
(line-beginning-position)
|
|||
|
(line-end-position)))))
|
|||
|
|
|||
|
(defun magit-git-true (&rest args)
|
|||
|
"Execute Git with ARGS, returning t if it prints \"true\".
|
|||
|
Return t if the first (and usually only) output line is the
|
|||
|
string \"true\", otherwise return nil."
|
|||
|
(equal (apply #'magit-git-string args) "true"))
|
|||
|
|
|||
|
(defun magit-git-false (&rest args)
|
|||
|
"Execute Git with ARGS, returning t if it prints \"false\".
|
|||
|
Return t if the first (and usually only) output line is the
|
|||
|
string \"false\", otherwise return nil."
|
|||
|
(equal (apply #'magit-git-string args) "false"))
|
|||
|
|
|||
|
(defun magit-git-insert (&rest args)
|
|||
|
"Execute Git with ARGS, inserting its output at point."
|
|||
|
(apply #'process-file magit-git-executable nil (list t nil) nil
|
|||
|
(append magit-git-standard-options
|
|||
|
(magit-flatten-onelevel args))))
|
|||
|
|
|||
|
(defun magit-git-lines (&rest args)
|
|||
|
"Execute Git with ARGS, returning its output as a list of lines.
|
|||
|
Empty lines anywhere in the output are omitted."
|
|||
|
(with-temp-buffer
|
|||
|
(apply #'process-file magit-git-executable nil (list t nil) nil
|
|||
|
(append magit-git-standard-options
|
|||
|
(magit-flatten-onelevel args)))
|
|||
|
(split-string (buffer-string) "\n" 'omit-nulls)))
|
|||
|
|
|||
|
(defun magit-run-git (&rest args)
|
|||
|
"Call Git synchronously in a separate process, and refresh.
|
|||
|
|
|||
|
The arguments ARGS specify command line arguments. The first
|
|||
|
level of ARGS is flattened, so each member of ARGS has to be a
|
|||
|
string or a list of strings.
|
|||
|
|
|||
|
Option `magit-git-executable' specifies which Git executable is
|
|||
|
used. The arguments in option `magit-git-standard-options' are
|
|||
|
prepended to ARGS.
|
|||
|
|
|||
|
After Git returns, the current buffer (if it is a Magit buffer)
|
|||
|
as well as the current repository's status buffer are refreshed.
|
|||
|
Unmodified buffers visiting files that are tracked in the current
|
|||
|
repository are reverted if `magit-auto-revert-mode' is active.
|
|||
|
|
|||
|
Process output goes into a new section in a buffer specified by
|
|||
|
variable `magit-process-buffer-name'."
|
|||
|
(apply #'magit-call-git (magit-process-quote-arguments args))
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
(defun magit-call-git (&rest args)
|
|||
|
"Call Git synchronously in a separate process.
|
|||
|
|
|||
|
The arguments ARGS specify command line arguments. The first
|
|||
|
level of ARGS is flattened, so each member of ARGS has to be a
|
|||
|
string or a list of strings.
|
|||
|
|
|||
|
Option `magit-git-executable' specifies which Git executable is
|
|||
|
used. The arguments in option `magit-git-standard-options' are
|
|||
|
prepended to ARGS.
|
|||
|
|
|||
|
Process output goes into a new section in a buffer specified by
|
|||
|
variable `magit-process-buffer-name'."
|
|||
|
(apply #'magit-call-process magit-git-executable
|
|||
|
(append magit-git-standard-options args)))
|
|||
|
|
|||
|
(defun magit-call-process (program &rest args)
|
|||
|
"Call PROGRAM synchronously in a separate process.
|
|||
|
|
|||
|
The arguments ARGS specify command line arguments. The first
|
|||
|
level of ARGS is flattened, so each member of ARGS has to be a
|
|||
|
string or a list of strings.
|
|||
|
|
|||
|
Process output goes into a new section in a buffer specified by
|
|||
|
variable `magit-process-buffer-name'."
|
|||
|
(setq args (magit-flatten-onelevel args))
|
|||
|
(cl-destructuring-bind (process-buf . section)
|
|||
|
(magit-process-setup program args)
|
|||
|
(magit-process-finish
|
|||
|
(let ((inhibit-read-only t))
|
|||
|
(apply #'process-file program nil process-buf nil args))
|
|||
|
process-buf (current-buffer) default-directory section)))
|
|||
|
|
|||
|
(defun magit-run-git-with-input (input &rest args)
|
|||
|
"Call Git in a separate process.
|
|||
|
|
|||
|
The first argument, INPUT, has to be a buffer or the name of an
|
|||
|
existing buffer. The content of that buffer is used as the
|
|||
|
process' standard input.
|
|||
|
|
|||
|
The remaining arguments, ARGS, specify command line arguments.
|
|||
|
The first level of ARGS is flattened, so each member of ARGS has
|
|||
|
to be a string or a list of strings.
|
|||
|
|
|||
|
Option `magit-git-executable' specifies which Git executable is
|
|||
|
used. The arguments in option `magit-git-standard-options' are
|
|||
|
prepended to ARGS.
|
|||
|
|
|||
|
After Git returns, the current buffer (if it is a Magit buffer)
|
|||
|
as well as the current repository's status buffer are refreshed.
|
|||
|
Unmodified buffers visiting files that are tracked in the current
|
|||
|
repository are reverted if `magit-auto-revert-mode' is active.
|
|||
|
|
|||
|
This function actually starts a asynchronous process, but it then
|
|||
|
waits for that process to return."
|
|||
|
(apply #'magit-start-git input args)
|
|||
|
(magit-process-wait)
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
(defun magit-run-git-with-logfile (file &rest args)
|
|||
|
"Call Git in a separate process and log its output.
|
|||
|
And log the output to FILE. This function might have a
|
|||
|
short halflive. See `magit-run-git' for more information."
|
|||
|
(apply #'magit-start-git nil args)
|
|||
|
(process-put magit-this-process 'logfile file)
|
|||
|
(set-process-filter magit-this-process 'magit-process-logfile-filter)
|
|||
|
(magit-process-wait)
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
;;;;; Asynchronous Processes
|
|||
|
|
|||
|
(defvar magit-this-process nil)
|
|||
|
|
|||
|
(defun magit-run-git-async (&rest args)
|
|||
|
"Start Git, prepare for refresh, and return the process object.
|
|||
|
|
|||
|
If optional argument INPUT is non-nil, it has to be a buffer or
|
|||
|
the name of an existing buffer. The content of that buffer is
|
|||
|
used as the process' standard input.
|
|||
|
|
|||
|
The remaining arguments, ARGS, specify command line arguments.
|
|||
|
The first level of ARGS is flattened, so each member of ARGS has
|
|||
|
to be a string or a list of strings.
|
|||
|
|
|||
|
Display the command line arguments in the echo area.
|
|||
|
|
|||
|
After Git returns some buffers are refreshed: the buffer that was
|
|||
|
current when `magit-start-process' was called (if it is a Magit
|
|||
|
buffer and still alive), as well as the respective Magit status
|
|||
|
buffer. Unmodified buffers visiting files that are tracked in
|
|||
|
the current repository are reverted if `magit-auto-revert-mode'
|
|||
|
is active.
|
|||
|
|
|||
|
See `magit-start-process' for more information."
|
|||
|
(message "Running %s %s" magit-git-executable
|
|||
|
(mapconcat 'identity (magit-flatten-onelevel args) " "))
|
|||
|
(apply #'magit-start-git nil args))
|
|||
|
|
|||
|
(defun magit-start-git (&optional input &rest args)
|
|||
|
"Start Git, prepare for refresh, and return the process object.
|
|||
|
|
|||
|
If optional argument INPUT is non-nil, it has to be a buffer or
|
|||
|
the name of an existing buffer. The buffer content becomes the
|
|||
|
processes standard input.
|
|||
|
|
|||
|
The remaining arguments, ARGS, specify command line arguments.
|
|||
|
The first level of ARGS is flattened, so each member of ARGS has
|
|||
|
to be a string or a list of strings.
|
|||
|
|
|||
|
After Git returns some buffers are refreshed: the buffer that was
|
|||
|
current when `magit-start-process' was called (if it is a Magit
|
|||
|
buffer and still alive), as well as the respective Magit status
|
|||
|
buffer. Unmodified buffers visiting files that are tracked in
|
|||
|
the current repository are reverted if `magit-auto-revert-mode'
|
|||
|
is active.
|
|||
|
|
|||
|
See `magit-start-process' for more information."
|
|||
|
(apply #'magit-start-process magit-git-executable input
|
|||
|
(append magit-git-standard-options
|
|||
|
(magit-process-quote-arguments args))))
|
|||
|
|
|||
|
(defun magit-start-process (program &optional input &rest args)
|
|||
|
"Start PROGRAM, prepare for refresh, and return the process object.
|
|||
|
|
|||
|
If optional argument INPUT is non-nil, it has to be a buffer or
|
|||
|
the name of an existing buffer. The buffer content becomes the
|
|||
|
processes standard input.
|
|||
|
|
|||
|
The remaining arguments, ARGS, specify command line arguments.
|
|||
|
The first level of ARGS is flattened, so each member of ARGS has
|
|||
|
to be a string or a list of strings.
|
|||
|
|
|||
|
The process is started using `start-file-process' and then setup
|
|||
|
to use the sentinel `magit-process-sentinel' and the filter
|
|||
|
`magit-process-filter'. Information required by these functions
|
|||
|
is stored in the process object. When this function returns the
|
|||
|
process has not started to run yet so it is possible to override
|
|||
|
the sentinel and filter.
|
|||
|
|
|||
|
After the process returns, `magit-process-sentinel' refreshes the
|
|||
|
buffer that was current when `magit-start-process' was called (if
|
|||
|
it is a Magit buffer and still alive), as well as the respective
|
|||
|
Magit status buffer. Unmodified buffers visiting files that are
|
|||
|
tracked in the current repository are reverted if
|
|||
|
`magit-auto-revert-mode' is active."
|
|||
|
(setq args (magit-flatten-onelevel args))
|
|||
|
(cl-destructuring-bind (process-buf . section)
|
|||
|
(magit-process-setup program args)
|
|||
|
(let* ((process-connection-type
|
|||
|
;; Don't use a pty, because it would set icrnl
|
|||
|
;; which would modify the input (issue #20).
|
|||
|
(and (not input) magit-process-connection-type))
|
|||
|
(process (apply 'start-file-process
|
|||
|
(file-name-nondirectory program)
|
|||
|
process-buf program args)))
|
|||
|
(set-process-sentinel process #'magit-process-sentinel)
|
|||
|
(set-process-filter process #'magit-process-filter)
|
|||
|
(set-process-buffer process process-buf)
|
|||
|
(process-put process 'section section)
|
|||
|
(process-put process 'command-buf (current-buffer))
|
|||
|
(process-put process 'default-dir default-directory)
|
|||
|
(setf (magit-section-process section) process)
|
|||
|
(with-current-buffer process-buf
|
|||
|
(set-marker (process-mark process) (point)))
|
|||
|
(when input
|
|||
|
(with-current-buffer input
|
|||
|
(process-send-region process (point-min) (point-max))
|
|||
|
(process-send-eof process)))
|
|||
|
(setq magit-this-process process)
|
|||
|
(setf (magit-section-info section) process)
|
|||
|
(magit-process-display-buffer process)
|
|||
|
process)))
|
|||
|
|
|||
|
;;;;; Process Internals
|
|||
|
|
|||
|
(defun magit-process-setup (program args)
|
|||
|
(magit-process-set-mode-line program args)
|
|||
|
(let ((buf (magit-process-buffer)))
|
|||
|
(if buf
|
|||
|
(magit-process-truncate-log buf)
|
|||
|
(setq buf (magit-process-buffer nil t)))
|
|||
|
(with-current-buffer buf
|
|||
|
(goto-char (1- (point-max)))
|
|||
|
(let* ((inhibit-read-only t)
|
|||
|
(magit-with-section--parent magit-root-section)
|
|||
|
;; Kids, don't do this ^^^^ at home.
|
|||
|
(s (magit-with-section
|
|||
|
(section process nil
|
|||
|
(mapconcat 'identity (cons program args) " "))
|
|||
|
(insert "\n"))))
|
|||
|
(set-marker-insertion-type (magit-section-content-beginning s) nil)
|
|||
|
(insert "\n")
|
|||
|
(backward-char 2)
|
|||
|
(cons (current-buffer) s)))))
|
|||
|
|
|||
|
(defun magit-process-truncate-log (buffer)
|
|||
|
(with-current-buffer buffer
|
|||
|
(let* ((head nil)
|
|||
|
(tail (magit-section-children magit-root-section))
|
|||
|
(count (length tail)))
|
|||
|
(when (> (1+ count) magit-process-log-max)
|
|||
|
(while (and (cdr tail)
|
|||
|
(> count (/ magit-process-log-max 2)))
|
|||
|
(let* ((inhibit-read-only t)
|
|||
|
(section (car tail))
|
|||
|
(process (magit-section-process section)))
|
|||
|
(cond ((not process))
|
|||
|
((memq (process-status process) '(exit signal))
|
|||
|
(delete-region (magit-section-beginning section)
|
|||
|
(1+ (magit-section-end section)))
|
|||
|
(cl-decf count))
|
|||
|
(t
|
|||
|
(push section head))))
|
|||
|
(pop tail))
|
|||
|
(setf (magit-section-children magit-root-section)
|
|||
|
(nconc (reverse head) tail))))))
|
|||
|
|
|||
|
(defun magit-process-sentinel (process event)
|
|||
|
"Default sentinel used by `magit-start-process'."
|
|||
|
(when (memq (process-status process) '(exit signal))
|
|||
|
(setq event (substring event 0 -1))
|
|||
|
(magit-process-unset-mode-line)
|
|||
|
(when (string-match "^finished" event)
|
|||
|
(message (concat (capitalize (process-name process)) " finished")))
|
|||
|
(magit-process-finish process)
|
|||
|
(when (eq process magit-this-process)
|
|||
|
(setq magit-this-process nil))
|
|||
|
(magit-refresh (and (buffer-live-p (process-get process 'command-buf))
|
|||
|
(process-get process 'command-buf)))))
|
|||
|
|
|||
|
(defun magit-process-filter (proc string)
|
|||
|
"Default filter used by `magit-start-process'."
|
|||
|
(with-current-buffer (process-buffer proc)
|
|||
|
(let ((inhibit-read-only t))
|
|||
|
(magit-process-yes-or-no-prompt proc string)
|
|||
|
(magit-process-username-prompt proc string)
|
|||
|
(magit-process-password-prompt proc string)
|
|||
|
(goto-char (process-mark proc))
|
|||
|
(setq string (propertize string 'invisible
|
|||
|
(magit-section-hidden
|
|||
|
(process-get proc 'section))))
|
|||
|
;; Find last ^M in string. If one was found, ignore everything
|
|||
|
;; before it and delete the current line.
|
|||
|
(let ((ret-pos (length string)))
|
|||
|
(while (and (>= (setq ret-pos (1- ret-pos)) 0)
|
|||
|
(/= ?\r (aref string ret-pos))))
|
|||
|
(cond ((>= ret-pos 0)
|
|||
|
(goto-char (line-beginning-position))
|
|||
|
(delete-region (point) (line-end-position))
|
|||
|
(insert-and-inherit (substring string (+ ret-pos 1))))
|
|||
|
(t
|
|||
|
(insert-and-inherit string))))
|
|||
|
(set-marker (process-mark proc) (point)))))
|
|||
|
|
|||
|
(defun magit-process-logfile-filter (process string)
|
|||
|
"Special filter used by `magit-run-git-with-logfile'."
|
|||
|
(magit-process-filter process string)
|
|||
|
(let ((file (process-get process 'logfile)))
|
|||
|
(with-temp-file file
|
|||
|
(when (file-exists-p file)
|
|||
|
(insert-file-contents file)
|
|||
|
(goto-char (point-max)))
|
|||
|
(insert string)
|
|||
|
(write-region (point-min) (point-max) file))))
|
|||
|
|
|||
|
(defun magit-process-yes-or-no-prompt (proc string)
|
|||
|
"Forward yes-or-no prompts to the user."
|
|||
|
(let ((beg (string-match magit-process-yes-or-no-prompt-regexp string))
|
|||
|
(max-mini-window-height 30))
|
|||
|
(when beg
|
|||
|
(process-send-string
|
|||
|
proc
|
|||
|
(downcase
|
|||
|
(concat (match-string (if (yes-or-no-p (substring string 0 beg)) 1 2)
|
|||
|
string)
|
|||
|
"\n"))))))
|
|||
|
|
|||
|
(defun magit-process-password-prompt (proc string)
|
|||
|
"Forward password prompts to the user."
|
|||
|
(let ((prompt (magit-process-match-prompt
|
|||
|
magit-process-password-prompt-regexps string)))
|
|||
|
(when prompt
|
|||
|
(process-send-string proc (concat (read-passwd prompt) "\n")))))
|
|||
|
|
|||
|
(defun magit-process-username-prompt (proc string)
|
|||
|
"Forward username prompts to the user."
|
|||
|
(let ((prompt (magit-process-match-prompt
|
|||
|
magit-process-username-prompt-regexps string)))
|
|||
|
(when prompt
|
|||
|
(process-send-string proc
|
|||
|
(concat (read-string prompt nil nil
|
|||
|
(user-login-name))
|
|||
|
"\n")))))
|
|||
|
|
|||
|
(defun magit-process-match-prompt (prompts string)
|
|||
|
(when (cl-find-if (lambda (regex)
|
|||
|
(string-match regex string))
|
|||
|
prompts)
|
|||
|
(let ((prompt (match-string 0 string)))
|
|||
|
(cond ((string-match ": $" prompt) prompt)
|
|||
|
((string-match ":$" prompt) (concat prompt " "))
|
|||
|
(t (concat prompt ": "))))))
|
|||
|
|
|||
|
(defun magit-process-wait ()
|
|||
|
(while (and magit-this-process
|
|||
|
(eq (process-status magit-this-process) 'run))
|
|||
|
(sit-for 0.1 t)))
|
|||
|
|
|||
|
(defun magit-process-set-mode-line (program args)
|
|||
|
(when (equal program magit-git-executable)
|
|||
|
(setq args (nthcdr (1+ (length magit-git-standard-options)) args)))
|
|||
|
(magit-map-magit-buffers
|
|||
|
(apply-partially (lambda (s)
|
|||
|
(setq mode-line-process s))
|
|||
|
(concat " " program
|
|||
|
(and args (concat " " (car args)))))))
|
|||
|
|
|||
|
(defun magit-process-unset-mode-line ()
|
|||
|
(magit-map-magit-buffers (lambda () (setq mode-line-process nil))))
|
|||
|
|
|||
|
(defvar magit-process-error-message-re
|
|||
|
(concat "^\\(?:error\\|fatal\\|git\\): \\(.*\\)" paragraph-separate))
|
|||
|
|
|||
|
(defun magit-process-finish (arg &optional process-buf command-buf
|
|||
|
default-dir section)
|
|||
|
(unless (integerp arg)
|
|||
|
(setq process-buf (process-buffer arg)
|
|||
|
command-buf (process-get arg 'command-buf)
|
|||
|
default-dir (process-get arg 'default-dir)
|
|||
|
section (process-get arg 'section)
|
|||
|
arg (process-exit-status arg)))
|
|||
|
(when (featurep 'dired)
|
|||
|
(dired-uncache default-dir))
|
|||
|
(when (buffer-live-p process-buf)
|
|||
|
(with-current-buffer process-buf
|
|||
|
(let ((inhibit-read-only t)
|
|||
|
(mark (magit-section-beginning section)))
|
|||
|
(set-marker-insertion-type mark nil)
|
|||
|
(goto-char mark)
|
|||
|
(insert (propertize (format "%3s " arg)
|
|||
|
'magit-section section
|
|||
|
'face (if (= arg 0)
|
|||
|
'magit-process-ok
|
|||
|
'magit-process-ng))))))
|
|||
|
(unless (= arg 0)
|
|||
|
(message ; `error' would prevent refresh
|
|||
|
"%s ... [%s buffer %s for details]"
|
|||
|
(or (and (buffer-live-p process-buf)
|
|||
|
(with-current-buffer process-buf
|
|||
|
(save-excursion
|
|||
|
(goto-char (magit-section-end section))
|
|||
|
(when (re-search-backward
|
|||
|
magit-process-error-message-re
|
|||
|
(magit-section-content-beginning section)
|
|||
|
t)
|
|||
|
(match-string 1)))))
|
|||
|
"Git failed")
|
|||
|
(let ((key (and (buffer-live-p command-buf)
|
|||
|
(with-current-buffer command-buf
|
|||
|
(car (where-is-internal
|
|||
|
'magit-process-display-buffer))))))
|
|||
|
(if key (format "Hit %s to see" (key-description key)) "See"))
|
|||
|
(buffer-name process-buf)))
|
|||
|
arg)
|
|||
|
|
|||
|
(defun magit-process-display-buffer (process)
|
|||
|
(when (process-live-p process)
|
|||
|
(let ((buf (process-buffer process)))
|
|||
|
(cond ((not (buffer-live-p buf)))
|
|||
|
((= magit-process-popup-time 0)
|
|||
|
(pop-to-buffer buf))
|
|||
|
((> magit-process-popup-time 0)
|
|||
|
(run-with-timer magit-process-popup-time nil
|
|||
|
(lambda (p)
|
|||
|
(when (eq (process-status p) 'run)
|
|||
|
(let ((buf (process-buffer p)))
|
|||
|
(when (buffer-live-p buf)
|
|||
|
(pop-to-buffer buf)))))
|
|||
|
process))))))
|
|||
|
|
|||
|
(defun magit-process-quote-arguments (args)
|
|||
|
"Quote each argument in list ARGS as an argument to Git.
|
|||
|
Except when `magit-process-quote-curly-braces' is non-nil ARGS is
|
|||
|
returned unchanged. This is required to works around strangeness
|
|||
|
of the Windows \"Powershell\"."
|
|||
|
(if magit-process-quote-curly-braces
|
|||
|
(mapcar (apply-partially 'replace-regexp-in-string
|
|||
|
"{\\([0-9]+\\)}" "\\\\{\\1\\\\}")
|
|||
|
(magit-flatten-onelevel args))
|
|||
|
args))
|
|||
|
|
|||
|
;;;; Mode Api
|
|||
|
;;;;; Mode Foundation
|
|||
|
|
|||
|
(define-derived-mode magit-mode special-mode "Magit"
|
|||
|
"Parent major mode from which Magit major modes inherit.
|
|||
|
|
|||
|
Please see the manual for a complete description of Magit.
|
|||
|
|
|||
|
\\{magit-mode-map}"
|
|||
|
(buffer-disable-undo)
|
|||
|
(setq truncate-lines t)
|
|||
|
(add-hook 'pre-command-hook #'magit-remember-point nil t)
|
|||
|
(add-hook 'post-command-hook #'magit-correct-point-after-command t t)
|
|||
|
(add-hook 'post-command-hook #'magit-highlight-section t t)
|
|||
|
;; Emacs' normal method of showing trailing whitespace gives weird
|
|||
|
;; results when `magit-whitespace-warning-face' is different from
|
|||
|
;; `trailing-whitespace'.
|
|||
|
(when (and magit-highlight-whitespace
|
|||
|
magit-highlight-trailing-whitespace)
|
|||
|
(setq show-trailing-whitespace nil)))
|
|||
|
|
|||
|
(defvar-local magit-refresh-function nil)
|
|||
|
(put 'magit-refresh-function 'permanent-local t)
|
|||
|
|
|||
|
(defvar-local magit-refresh-args nil)
|
|||
|
(put 'magit-refresh-args 'permanent-local t)
|
|||
|
|
|||
|
(defmacro magit-mode-setup
|
|||
|
(buffer switch-func mode refresh-func &rest refresh-args)
|
|||
|
"Display and select BUFFER, turn on MODE, and refresh a first time.
|
|||
|
Display BUFFER using `magit-mode-display-buffer', then turn on
|
|||
|
MODE in BUFFER, set the local value of `magit-refresh-function'
|
|||
|
to REFRESH-FUNC and that of `magit-refresh-args' to REFRESH-ARGS
|
|||
|
and finally \"refresh\" a first time. All arguments are
|
|||
|
evaluated before switching to BUFFER."
|
|||
|
(let ((mode-symb (cl-gensym "mode-symb"))
|
|||
|
(toplevel (cl-gensym "toplevel"))
|
|||
|
(init-args (cl-gensym "init-args"))
|
|||
|
(buf-symb (cl-gensym "buf-symb")))
|
|||
|
`(let* ((,mode-symb ,mode)
|
|||
|
(,toplevel (magit-get-top-dir))
|
|||
|
(,init-args (list ,mode-symb ,refresh-func ,@refresh-args))
|
|||
|
(,buf-symb (magit-mode-display-buffer
|
|||
|
,buffer ,mode-symb ,switch-func)))
|
|||
|
(if ,toplevel
|
|||
|
(with-current-buffer ,buf-symb
|
|||
|
(apply #'magit-mode-init ,toplevel ,init-args))
|
|||
|
(user-error "Not inside a Git repository")))))
|
|||
|
|
|||
|
(defun magit-mode-init (dir mode refresh-func &rest refresh-args)
|
|||
|
"Turn on MODE and refresh in the current buffer.
|
|||
|
Turn on MODE, set the local value of `magit-refresh-function' to
|
|||
|
REFRESH-FUNC and that of `magit-refresh-args' to REFRESH-ARGS and
|
|||
|
finally \"refresh\" a first time.
|
|||
|
|
|||
|
Also see `magit-mode-setup', a more convenient variant."
|
|||
|
(cl-case mode
|
|||
|
(magit-commit-mode
|
|||
|
(magit-setup-xref (cons #'magit-show-commit refresh-args))
|
|||
|
(goto-char (point-min)))
|
|||
|
(magit-diff-mode
|
|||
|
(magit-setup-xref (cons #'magit-diff refresh-args))
|
|||
|
(goto-char (point-min))))
|
|||
|
(setq default-directory dir
|
|||
|
magit-refresh-function refresh-func
|
|||
|
magit-refresh-args refresh-args)
|
|||
|
(funcall mode)
|
|||
|
(magit-mode-refresh-buffer))
|
|||
|
|
|||
|
(defvar-local magit-previous-window-configuration nil)
|
|||
|
(put 'magit-previous-window-configuration 'permanent-local t)
|
|||
|
|
|||
|
(defun magit-mode-display-buffer (buffer mode &optional switch-function)
|
|||
|
"Display BUFFER in some window and select it.
|
|||
|
BUFFER may be a buffer or a string, the name of a buffer. Return
|
|||
|
the buffer.
|
|||
|
|
|||
|
Unless BUFFER is already displayed in the selected frame store the
|
|||
|
previous window configuration as a buffer local value, so that it
|
|||
|
can later be restored by `magit-mode-quit-window'.
|
|||
|
|
|||
|
Then display and select BUFFER using SWITCH-FUNCTION. If that is
|
|||
|
nil either use `pop-to-buffer' if the current buffer's major mode
|
|||
|
derives from Magit mode; or else use `switch-to-buffer'.
|
|||
|
|
|||
|
This is only intended for buffers whose major modes derive from
|
|||
|
Magit mode."
|
|||
|
(cond ((stringp buffer)
|
|||
|
(setq buffer (magit-mode-get-buffer-create buffer mode)))
|
|||
|
((not (bufferp buffer))
|
|||
|
(signal 'wrong-type-argument (list 'bufferp nil))))
|
|||
|
(unless (get-buffer-window buffer (selected-frame))
|
|||
|
(with-current-buffer (get-buffer-create buffer)
|
|||
|
(setq magit-previous-window-configuration
|
|||
|
(current-window-configuration))))
|
|||
|
(funcall (or switch-function
|
|||
|
(if (derived-mode-p 'magit-mode)
|
|||
|
'switch-to-buffer
|
|||
|
'pop-to-buffer))
|
|||
|
buffer)
|
|||
|
buffer)
|
|||
|
|
|||
|
(defun magit-mode-get-buffer (format mode &optional topdir create)
|
|||
|
(if (not (string-match-p "%[Tt]" format))
|
|||
|
(funcall (if create #'get-buffer-create #'get-buffer) format)
|
|||
|
(unless topdir
|
|||
|
(setq topdir (magit-get-top-dir)))
|
|||
|
(let ((name (format-spec
|
|||
|
format `((?T . ,topdir)
|
|||
|
(?t . ,(file-name-nondirectory
|
|||
|
(directory-file-name topdir)))))))
|
|||
|
(or (cl-find-if
|
|||
|
(lambda (buf)
|
|||
|
(with-current-buffer buf
|
|||
|
(and (or (not mode) (eq major-mode mode))
|
|||
|
(equal (expand-file-name default-directory) topdir)
|
|||
|
(string-match-p (format "^%s\\(?:<[0-9]+>\\)?$"
|
|||
|
(regexp-quote name))
|
|||
|
(buffer-name)))))
|
|||
|
(buffer-list))
|
|||
|
(and create (generate-new-buffer name))))))
|
|||
|
|
|||
|
(defun magit-mode-get-buffer-create (format mode &optional topdir)
|
|||
|
(magit-mode-get-buffer format mode topdir t))
|
|||
|
|
|||
|
(cl-defun magit-mode-refresh-buffer (&optional (buffer (current-buffer)))
|
|||
|
(with-current-buffer buffer
|
|||
|
(when magit-refresh-function
|
|||
|
(let* ((old-line (line-number-at-pos))
|
|||
|
(old-point (point))
|
|||
|
(old-section (magit-current-section))
|
|||
|
(old-path (and old-section
|
|||
|
(magit-section-path (magit-current-section)))))
|
|||
|
(beginning-of-line)
|
|||
|
(let ((inhibit-read-only t)
|
|||
|
(section-line (and old-section
|
|||
|
(count-lines
|
|||
|
(magit-section-beginning old-section)
|
|||
|
(point))))
|
|||
|
(line-char (- old-point (point))))
|
|||
|
(erase-buffer)
|
|||
|
(apply magit-refresh-function
|
|||
|
magit-refresh-args)
|
|||
|
(let ((s (and old-path (magit-find-section old-path magit-root-section))))
|
|||
|
(cond (s
|
|||
|
(goto-char (magit-section-beginning s))
|
|||
|
(forward-line section-line)
|
|||
|
(forward-char line-char))
|
|||
|
(t
|
|||
|
(save-restriction
|
|||
|
(widen)
|
|||
|
(goto-char (point-min))
|
|||
|
(forward-line (1- old-line)))))))
|
|||
|
(magit-highlight-section)
|
|||
|
(magit-refresh-marked-commits-in-buffer)))))
|
|||
|
|
|||
|
(defun magit-mode-quit-window (&optional kill-buffer)
|
|||
|
"Bury the current buffer and delete its window.
|
|||
|
With a prefix argument, kill the buffer instead.
|
|||
|
|
|||
|
If `magit-restore-window-configuration' is non-nil and the last
|
|||
|
configuration stored by `magit-mode-display-buffer' originates
|
|||
|
from the selected frame then restore it after burrying/killing
|
|||
|
the buffer. Finally reset the window configuration to nil."
|
|||
|
(interactive "P")
|
|||
|
(let ((winconf magit-previous-window-configuration)
|
|||
|
(buffer (current-buffer))
|
|||
|
(frame (selected-frame)))
|
|||
|
(quit-window kill-buffer (selected-window))
|
|||
|
(when winconf
|
|||
|
(when (and magit-restore-window-configuration
|
|||
|
(equal frame (window-configuration-frame winconf)))
|
|||
|
(set-window-configuration winconf)
|
|||
|
(when (buffer-live-p buffer)
|
|||
|
(with-current-buffer buffer
|
|||
|
(setq magit-previous-window-configuration nil)))))
|
|||
|
(run-hook-with-args 'magit-mode-quit-window-hook buffer)))
|
|||
|
|
|||
|
;;;;; Mode Utilities
|
|||
|
|
|||
|
(defun magit-map-magit-buffers (func &optional dir)
|
|||
|
(dolist (buf (buffer-list))
|
|||
|
(with-current-buffer buf
|
|||
|
(when (and (derived-mode-p 'magit-mode)
|
|||
|
(or (null dir)
|
|||
|
(equal default-directory dir)))
|
|||
|
(funcall func)))))
|
|||
|
|
|||
|
;;;;; (section kludges)
|
|||
|
|
|||
|
(defvar-local magit-last-point nil)
|
|||
|
(put 'magit-last-point 'permanent-local t)
|
|||
|
|
|||
|
(defun magit-remember-point ()
|
|||
|
(setq magit-last-point (point)))
|
|||
|
|
|||
|
(defun magit-invisible-region-end (pos)
|
|||
|
(while (and (not (= pos (point-max))) (invisible-p pos))
|
|||
|
(setq pos (next-char-property-change pos)))
|
|||
|
pos)
|
|||
|
|
|||
|
(defun magit-invisible-region-start (pos)
|
|||
|
(while (and (not (= pos (point-min))) (invisible-p pos))
|
|||
|
(setq pos (1- (previous-char-property-change pos))))
|
|||
|
pos)
|
|||
|
|
|||
|
(defun magit-correct-point-after-command ()
|
|||
|
"Move point outside of invisible regions.
|
|||
|
|
|||
|
Emacs often leaves point in invisible regions, it seems. To fix
|
|||
|
this, we move point ourselves and never let Emacs do its own
|
|||
|
adjustments.
|
|||
|
|
|||
|
When point has to be moved out of an invisible region, it can be
|
|||
|
moved to its end or its beginning. We usually move it to its
|
|||
|
end, except when that would move point back to where it was
|
|||
|
before the last command."
|
|||
|
(when (invisible-p (point))
|
|||
|
(let ((end (magit-invisible-region-end (point))))
|
|||
|
(goto-char (if (= end magit-last-point)
|
|||
|
(magit-invisible-region-start (point))
|
|||
|
end))))
|
|||
|
(setq disable-point-adjustment t))
|
|||
|
|
|||
|
;;;;; Buffer History
|
|||
|
|
|||
|
(defun magit-go-backward ()
|
|||
|
"Move backward in current buffer's history."
|
|||
|
(interactive)
|
|||
|
(if help-xref-stack
|
|||
|
(help-xref-go-back (current-buffer))
|
|||
|
(user-error "No previous entry in buffer's history")))
|
|||
|
|
|||
|
(defun magit-go-forward ()
|
|||
|
"Move forward in current buffer's history."
|
|||
|
(interactive)
|
|||
|
(if help-xref-forward-stack
|
|||
|
(help-xref-go-forward (current-buffer))
|
|||
|
(user-error "No next entry in buffer's history")))
|
|||
|
|
|||
|
(defun magit-xref-insert-buttons ()
|
|||
|
(when (and (or (eq magit-show-xref-buttons t)
|
|||
|
(apply 'derived-mode-p magit-show-xref-buttons))
|
|||
|
(or help-xref-stack help-xref-forward-stack))
|
|||
|
(insert "\n")
|
|||
|
(when help-xref-stack
|
|||
|
(magit-xref-insert-button help-back-label
|
|||
|
'magit-xref-backward))
|
|||
|
(when help-xref-forward-stack
|
|||
|
(when help-xref-stack
|
|||
|
(insert " "))
|
|||
|
(magit-xref-insert-button help-forward-label
|
|||
|
'magit-xref-forward))))
|
|||
|
|
|||
|
(defun magit-xref-insert-button (label type)
|
|||
|
(magit-with-section (section button label)
|
|||
|
(insert-text-button label 'type type
|
|||
|
'help-args (list (current-buffer)))))
|
|||
|
|
|||
|
(define-button-type 'magit-xref-backward
|
|||
|
:supertype 'help-back
|
|||
|
'mouse-face magit-item-highlight-face
|
|||
|
'help-echo (purecopy "mouse-2, RET: go back to previous history entry"))
|
|||
|
|
|||
|
(define-button-type 'magit-xref-forward
|
|||
|
:supertype 'help-forward
|
|||
|
'mouse-face magit-item-highlight-face
|
|||
|
'help-echo (purecopy "mouse-2, RET: go back to next history entry"))
|
|||
|
|
|||
|
(defun magit-setup-xref (item)
|
|||
|
(when help-xref-stack-item
|
|||
|
(push (cons (point) help-xref-stack-item) help-xref-stack)
|
|||
|
(setq help-xref-forward-stack nil))
|
|||
|
(when (called-interactively-p 'interactive)
|
|||
|
(let ((tail (nthcdr 10 help-xref-stack)))
|
|||
|
(if tail (setcdr tail nil))))
|
|||
|
(setq help-xref-stack-item item))
|
|||
|
|
|||
|
;;;;; Refresh Machinery
|
|||
|
|
|||
|
(defun magit-refresh (&optional buffer)
|
|||
|
"Refresh some buffers belonging to the current repository.
|
|||
|
|
|||
|
Refresh the current buffer if its major mode derives from
|
|||
|
`magit-mode', and refresh the corresponding status buffer.
|
|||
|
If the global `magit-auto-revert-mode' is turned on, then
|
|||
|
also revert all unmodified buffers that visit files being
|
|||
|
tracked in the current repository."
|
|||
|
(interactive (list (current-buffer)))
|
|||
|
(unless buffer
|
|||
|
(setq buffer (current-buffer)))
|
|||
|
(with-current-buffer buffer
|
|||
|
(when (derived-mode-p 'magit-mode)
|
|||
|
(magit-mode-refresh-buffer buffer))
|
|||
|
(let (status)
|
|||
|
(when (and (not (eq major-mode 'magit-status-mode))
|
|||
|
(setq status (magit-mode-get-buffer
|
|||
|
magit-status-buffer-name
|
|||
|
'magit-status-mode)))
|
|||
|
(magit-mode-refresh-buffer status))))
|
|||
|
(when magit-auto-revert-mode
|
|||
|
(magit-revert-buffers)))
|
|||
|
|
|||
|
(defun magit-refresh-all ()
|
|||
|
"Refresh all buffers belonging to the current repository.
|
|||
|
|
|||
|
Refresh all Magit buffers belonging to the current repository.
|
|||
|
If the global `magit-auto-revert-mode' is turned on, then also
|
|||
|
revert all unmodified buffers that visit files being tracked in
|
|||
|
the current repository."
|
|||
|
(interactive)
|
|||
|
(magit-map-magit-buffers #'magit-mode-refresh-buffer default-directory)
|
|||
|
(magit-revert-buffers))
|
|||
|
|
|||
|
(defun magit-revert-buffers ()
|
|||
|
(let ((topdir (magit-get-top-dir)))
|
|||
|
(when topdir
|
|||
|
(let ((gitdir (magit-git-dir))
|
|||
|
(tracked (magit-git-lines "ls-tree" "-r" "--name-only" "HEAD")))
|
|||
|
(dolist (buf (buffer-list))
|
|||
|
(with-current-buffer buf
|
|||
|
(let ((file (buffer-file-name)))
|
|||
|
(and file (string-prefix-p topdir file)
|
|||
|
(not (string-prefix-p gitdir file))
|
|||
|
(member (file-relative-name file topdir) tracked)
|
|||
|
(let ((auto-revert-mode t))
|
|||
|
(auto-revert-handler)
|
|||
|
(run-hooks 'magit-revert-buffer-hook))))))))))
|
|||
|
|
|||
|
;;; (misplaced)
|
|||
|
;;;; Diff Options
|
|||
|
|
|||
|
(defvar magit-diff-context-lines 3)
|
|||
|
|
|||
|
(defun magit-diff-U-arg ()
|
|||
|
(format "-U%d" magit-diff-context-lines))
|
|||
|
|
|||
|
(defun magit-diff-smaller-hunks (&optional count)
|
|||
|
"Decrease the context for diff hunks by COUNT."
|
|||
|
(interactive "p")
|
|||
|
(setq magit-diff-context-lines (max 0 (- magit-diff-context-lines count)))
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
(defun magit-diff-larger-hunks (&optional count)
|
|||
|
"Increase the context for diff hunks by COUNT."
|
|||
|
(interactive "p")
|
|||
|
(setq magit-diff-context-lines (+ magit-diff-context-lines count))
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
(defun magit-diff-default-hunks ()
|
|||
|
"Reset context for diff hunks to the default size."
|
|||
|
(interactive)
|
|||
|
(setq magit-diff-context-lines 3)
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
(defun magit-set-diff-options ()
|
|||
|
"Set local `magit-diff-options' based on popup state.
|
|||
|
And refresh the current Magit buffer."
|
|||
|
(interactive)
|
|||
|
(setq-local magit-diff-options magit-custom-options)
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
;; `magit-set-default-diff-options' is defined in "Options"/"Setters".
|
|||
|
|
|||
|
(defun magit-save-default-diff-options ()
|
|||
|
"Set and save the default for `magit-diff-options' based on popup value.
|
|||
|
Also set the local value in all Magit buffers and refresh them."
|
|||
|
(interactive)
|
|||
|
(customize-save-variable 'magit-diff-options magit-custom-options))
|
|||
|
|
|||
|
(defun magit-reset-diff-options ()
|
|||
|
"Reset local `magit-diff-options' to default value.
|
|||
|
And refresh the current Magit buffer."
|
|||
|
(interactive)
|
|||
|
(setq-local magit-diff-options (default-value 'magit-diff-options))
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
;;;; Raw Diff Washing
|
|||
|
|
|||
|
(defun magit-insert-diff (section file status)
|
|||
|
(let ((beg (point)))
|
|||
|
(apply 'magit-git-insert "-c" "diff.submodule=short" "diff"
|
|||
|
`(,(magit-diff-U-arg) ,@magit-diff-options "--" ,file))
|
|||
|
(unless (eq (char-before) ?\n)
|
|||
|
(insert "\n"))
|
|||
|
(save-restriction
|
|||
|
(narrow-to-region beg (point))
|
|||
|
(goto-char beg)
|
|||
|
(magit-wash-diff-section section)
|
|||
|
(goto-char (point-max)))))
|
|||
|
|
|||
|
(defun magit-wash-raw-diffs (&optional staged)
|
|||
|
(let (previous)
|
|||
|
(magit-wash-sequence
|
|||
|
(lambda ()
|
|||
|
(setq previous (magit-wash-raw-diff previous staged))))))
|
|||
|
|
|||
|
(defun magit-wash-raw-diff (previous staged)
|
|||
|
(when (looking-at
|
|||
|
":\\([0-7]+\\) \\([0-7]+\\) [0-9a-f]+ [0-9a-f]+ \\(.\\)[0-9]*\t\\([^\t\n]+\\)$")
|
|||
|
(let ((file (magit-decode-git-path (match-string-no-properties 4)))
|
|||
|
(status (cl-ecase (string-to-char (match-string-no-properties 3))
|
|||
|
(?A 'new)
|
|||
|
(?C 'copy)
|
|||
|
(?D 'deleted)
|
|||
|
(?M 'modified)
|
|||
|
(?T 'typechange)
|
|||
|
(?U 'unmerged)
|
|||
|
(?X 'unknown))))
|
|||
|
(delete-region (point) (1+ (line-end-position)))
|
|||
|
(unless (or ;; Unmerged files get two entries; we ignore the second.
|
|||
|
(equal file previous)
|
|||
|
;; Ignore staged, unmerged files.
|
|||
|
(and staged (eq status 'unmerged)))
|
|||
|
(magit-with-section (section diff file nil nil
|
|||
|
(not (derived-mode-p
|
|||
|
'magit-diff-mode
|
|||
|
'magit-commit-mode)))
|
|||
|
(if (not (magit-section-hidden section))
|
|||
|
(magit-insert-diff section file status)
|
|||
|
(setf (magit-section-diff-status section) status)
|
|||
|
(setf (magit-section-needs-refresh-on-show section) t)
|
|||
|
(magit-insert-diff-title status file nil))))
|
|||
|
file)))
|
|||
|
|
|||
|
;;; Modes (1)
|
|||
|
;;;; Commit Mode
|
|||
|
;;;;; Commit Core
|
|||
|
|
|||
|
(define-derived-mode magit-commit-mode magit-mode "Magit"
|
|||
|
"Mode for looking at a git commit.
|
|||
|
|
|||
|
\\<magit-commit-mode-map>Type `\\[magit-visit-item]` to visit the changed file, \
|
|||
|
`\\[magit-toggle-section]` to hide or show a hunk,
|
|||
|
`\\[magit-diff-larger-hunks]` and `\\[magit-diff-smaller-hunks]` to change the \
|
|||
|
size of the hunks.
|
|||
|
Type `\\[magit-apply-item]` to apply a change to your worktree and \
|
|||
|
`\\[magit-revert-item]` to reverse it.
|
|||
|
|
|||
|
\\{magit-commit-mode-map}
|
|||
|
Unless shadowed by the mode specific bindings above, bindings
|
|||
|
from the parent keymap `magit-mode-map' are also available."
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defvar magit-commit-buffer-name "*magit-commit*"
|
|||
|
"Name of buffer used to display a commit.")
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-show-commit (commit &optional noselect)
|
|||
|
"Show information about COMMIT."
|
|||
|
(interactive (list (magit-read-rev-with-default
|
|||
|
"Show commit (hash or ref)")))
|
|||
|
(when (magit-git-failure "cat-file" "commit" commit)
|
|||
|
(user-error "%s is not a commit" commit))
|
|||
|
(magit-mode-setup magit-commit-buffer-name
|
|||
|
(if noselect 'display-buffer 'pop-to-buffer)
|
|||
|
#'magit-commit-mode
|
|||
|
#'magit-refresh-commit-buffer
|
|||
|
commit))
|
|||
|
|
|||
|
(defun magit-show-item-or-scroll-up ()
|
|||
|
"Update commit or status buffer for item at point.
|
|||
|
|
|||
|
Either show the commit or stash at point in another buffer,
|
|||
|
or if that buffer is already displayed in the current frame
|
|||
|
and contains information about that commit or stash, then
|
|||
|
instead scroll the buffer up. If there is no commit or
|
|||
|
stash at point, then prompt for a commit."
|
|||
|
(interactive)
|
|||
|
(magit-show-item-or-scroll 'scroll-up))
|
|||
|
|
|||
|
(defun magit-show-item-or-scroll-down ()
|
|||
|
"Update commit or status buffer for item at point.
|
|||
|
|
|||
|
Either show the commit or stash at point in another buffer,
|
|||
|
or if that buffer is already displayed in the current frame
|
|||
|
and contains information about that commit or stash, then
|
|||
|
instead scroll the buffer down. If there is no commit or
|
|||
|
stash at point, then prompt for a commit."
|
|||
|
(interactive)
|
|||
|
(magit-show-item-or-scroll 'scroll-down))
|
|||
|
|
|||
|
(defun magit-show-item-or-scroll (fn)
|
|||
|
(let (rev cmd buf win)
|
|||
|
(magit-section-case (info)
|
|||
|
(commit (setq rev info
|
|||
|
cmd 'magit-show-commit
|
|||
|
buf magit-commit-buffer-name))
|
|||
|
(stash (setq rev info
|
|||
|
cmd 'magit-diff-stash
|
|||
|
buf magit-stash-buffer-name)))
|
|||
|
(if rev
|
|||
|
(if (and (setq buf (get-buffer buf))
|
|||
|
(setq win (get-buffer-window buf))
|
|||
|
(with-current-buffer buf
|
|||
|
(equal rev (car magit-refresh-args))))
|
|||
|
(with-selected-window win
|
|||
|
(condition-case err
|
|||
|
(funcall fn)
|
|||
|
(error
|
|||
|
(goto-char (cl-case fn
|
|||
|
(scroll-up (point-min))
|
|||
|
(scroll-down (point-max)))))))
|
|||
|
(funcall cmd rev t))
|
|||
|
(call-interactively 'magit-show-commit))))
|
|||
|
|
|||
|
(defun magit-refresh-commit-buffer (commit)
|
|||
|
(magit-git-insert-section (commitbuf nil)
|
|||
|
#'magit-wash-commit
|
|||
|
"log" "-1" "--decorate=full"
|
|||
|
"--pretty=medium" (magit-diff-U-arg)
|
|||
|
"--cc" "-p" (and magit-show-diffstat "--stat")
|
|||
|
magit-diff-options commit))
|
|||
|
|
|||
|
;;;;; Commit Washing
|
|||
|
|
|||
|
(defun magit-wash-commit ()
|
|||
|
(looking-at "^commit \\([a-z0-9]+\\)\\(?: \\(.+\\)\\)?$")
|
|||
|
(let ((rev (match-string 1))
|
|||
|
(refs (match-string 2)))
|
|||
|
(delete-region (point) (1+ (line-end-position)))
|
|||
|
(magit-with-section
|
|||
|
(section headers 'headers
|
|||
|
(concat (propertize rev 'face 'magit-log-sha1)
|
|||
|
(and refs (concat " "(magit-format-ref-labels refs)))
|
|||
|
"\n"))
|
|||
|
(while (re-search-forward "^\\([a-z]+\\): +\\(.+\\)$" nil t)
|
|||
|
(when (string-match-p (match-string 1) "Merge")
|
|||
|
(let ((revs (match-string 2)))
|
|||
|
(delete-region (match-beginning 2) (match-end 2))
|
|||
|
(dolist (rev (split-string revs))
|
|||
|
(magit-insert-commit-button rev)
|
|||
|
(insert ?\s)))))
|
|||
|
(forward-line)))
|
|||
|
(forward-line)
|
|||
|
(let ((bound (save-excursion
|
|||
|
(when (re-search-forward "^diff" nil t)
|
|||
|
(copy-marker (match-beginning 0)))))
|
|||
|
(summary (buffer-substring-no-properties
|
|||
|
(point) (line-end-position))))
|
|||
|
(delete-region (point) (1+ (line-end-position)))
|
|||
|
(magit-with-section (section message 'message (concat summary "\n"))
|
|||
|
(cond ((re-search-forward "^---" bound t)
|
|||
|
(goto-char (match-beginning 0))
|
|||
|
(delete-region (match-beginning 0) (match-end 0)))
|
|||
|
((re-search-forward "^.[^ ]" bound t)
|
|||
|
(goto-char (1- (match-beginning 0)))))))
|
|||
|
(forward-line)
|
|||
|
(when magit-show-diffstat
|
|||
|
(magit-wash-diffstats))
|
|||
|
(forward-line)
|
|||
|
(magit-wash-diffs))
|
|||
|
|
|||
|
(defun magit-insert-commit-button (hash)
|
|||
|
(magit-with-section (section commit hash)
|
|||
|
(insert-text-button hash
|
|||
|
'help-echo "Visit commit"
|
|||
|
'action (lambda (button)
|
|||
|
(save-excursion
|
|||
|
(goto-char button)
|
|||
|
(magit-visit-item)))
|
|||
|
'follow-link t
|
|||
|
'mouse-face magit-item-highlight-face
|
|||
|
'face 'magit-log-sha1)))
|
|||
|
|
|||
|
;;;; Status Mode
|
|||
|
|
|||
|
(define-derived-mode magit-status-mode magit-mode "Magit"
|
|||
|
"Mode for looking at git status.
|
|||
|
|
|||
|
\\<magit-status-mode-map>Type `\\[magit-stage-item]` to stage (add) an item, \
|
|||
|
`\\[magit-unstage-item]` to unstage it.
|
|||
|
Type `\\[magit-key-mode-popup-committing]` to have a popup to commit, type \
|
|||
|
`\\[magit-key-mode-popup-dispatch]` to see others
|
|||
|
available popup.
|
|||
|
Type `\\[magit-visit-item]` to visit something, and \
|
|||
|
`\\[magit-toggle-section]` to show or hide section.
|
|||
|
|
|||
|
More information can be found in Info node `(magit)Status'
|
|||
|
|
|||
|
Other key binding:
|
|||
|
\\{magit-status-mode-map}"
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defvar magit-status-buffer-name "*magit: %t*"
|
|||
|
"Name of buffer used to display a repository's status.")
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-status (dir &optional switch-function)
|
|||
|
"Open a Magit status buffer for the Git repository containing DIR.
|
|||
|
If DIR is not within a Git repository, offer to create a Git
|
|||
|
repository in DIR.
|
|||
|
|
|||
|
Interactively, a prefix argument means to ask the user which Git
|
|||
|
repository to use even if `default-directory' is under Git
|
|||
|
control. Two prefix arguments means to ignore `magit-repo-dirs'
|
|||
|
when asking for user input.
|
|||
|
|
|||
|
Depending on option `magit-status-buffer-switch-function' the
|
|||
|
status buffer is shown in another window (the default) or the
|
|||
|
current window. Non-interactively optional SWITCH-FUNCTION
|
|||
|
can be used to override this."
|
|||
|
(interactive (list (if current-prefix-arg
|
|||
|
(magit-read-top-dir
|
|||
|
(> (prefix-numeric-value current-prefix-arg)
|
|||
|
4))
|
|||
|
(or (magit-get-top-dir)
|
|||
|
(magit-read-top-dir nil)))))
|
|||
|
(magit-save-some-buffers)
|
|||
|
(let ((topdir (magit-get-top-dir dir)))
|
|||
|
(when (or topdir
|
|||
|
(and (yes-or-no-p
|
|||
|
(format "There is no Git repository in %s. Create one? "
|
|||
|
dir))
|
|||
|
(progn
|
|||
|
(magit-init dir)
|
|||
|
(setq topdir (magit-get-top-dir dir)))))
|
|||
|
(let ((default-directory topdir))
|
|||
|
(magit-mode-setup magit-status-buffer-name
|
|||
|
(or switch-function
|
|||
|
magit-status-buffer-switch-function)
|
|||
|
#'magit-status-mode
|
|||
|
#'magit-refresh-status)))))
|
|||
|
|
|||
|
(defalias 'magit-status-internal 'magit-status) ; forward compatibility
|
|||
|
|
|||
|
(defun magit-refresh-status ()
|
|||
|
(magit-git-exit-code "update-index" "--refresh")
|
|||
|
(magit-with-section (section status 'status nil t)
|
|||
|
(run-hooks 'magit-status-sections-hook))
|
|||
|
(run-hooks 'magit-refresh-status-hook))
|
|||
|
|
|||
|
;;; Sections
|
|||
|
;;;; Real Sections
|
|||
|
|
|||
|
(defun magit-insert-stashes ()
|
|||
|
;; #1427 Set log.date to work around an issue in Git <1.7.10.3.
|
|||
|
(let ((stashes (magit-git-lines "-c" "log.date=default" "stash" "list")))
|
|||
|
(when stashes
|
|||
|
(magit-with-section (section stashes 'stashes "Stashes:" t)
|
|||
|
(dolist (stash stashes)
|
|||
|
(string-match "^\\(stash@{\\([0-9]+\\)}\\): \\(.+\\)$" stash)
|
|||
|
(let ((stash (match-string 1 stash))
|
|||
|
(number (match-string 2 stash))
|
|||
|
(message (match-string 3 stash)))
|
|||
|
(magit-with-section (section stash stash)
|
|||
|
(insert number ": " message "\n"))))
|
|||
|
(insert "\n")))))
|
|||
|
|
|||
|
(defun magit-insert-untracked-files ()
|
|||
|
(magit-with-section (section untracked 'untracked "Untracked files:" t)
|
|||
|
(let ((files (cl-mapcan
|
|||
|
(lambda (f)
|
|||
|
(when (eq (aref f 0) ??) (list f)))
|
|||
|
(magit-git-lines
|
|||
|
"status" "--porcelain"))))
|
|||
|
(if (not files)
|
|||
|
(setq section nil)
|
|||
|
(dolist (file files)
|
|||
|
(setq file (magit-decode-git-path (substring file 3)))
|
|||
|
(magit-with-section (section file file)
|
|||
|
(insert "\t" file "\n")))
|
|||
|
(insert "\n")))))
|
|||
|
|
|||
|
(defun magit-insert-pending-commits ()
|
|||
|
(let* ((info (magit-read-rewrite-info))
|
|||
|
(pending (cdr (assq 'pending info))))
|
|||
|
(when pending
|
|||
|
(magit-with-section (section pending 'pending "Pending commits:" t)
|
|||
|
(dolist (p pending)
|
|||
|
(let* ((commit (car p))
|
|||
|
(properties (cdr p))
|
|||
|
(used (plist-get properties 'used)))
|
|||
|
(magit-with-section (section commit commit)
|
|||
|
(insert (magit-git-string
|
|||
|
"log" "-1"
|
|||
|
(if used
|
|||
|
"--pretty=format:. %s"
|
|||
|
"--pretty=format:* %s")
|
|||
|
commit "--")
|
|||
|
"\n")))))
|
|||
|
(insert "\n"))))
|
|||
|
|
|||
|
(defun magit-insert-unstaged-changes ()
|
|||
|
(let ((magit-current-diff-range (cons 'index 'working))
|
|||
|
(magit-diff-options (copy-sequence magit-diff-options)))
|
|||
|
(magit-git-insert-section (unstaged "Unstaged changes:")
|
|||
|
#'magit-wash-raw-diffs
|
|||
|
"diff-files")))
|
|||
|
|
|||
|
(defun magit-insert-staged-changes ()
|
|||
|
(let ((no-commit (not (magit-git-success "log" "-1" "HEAD"))))
|
|||
|
(when (or no-commit (magit-anything-staged-p))
|
|||
|
(let ((magit-current-diff-range (cons "HEAD" 'index))
|
|||
|
(base (if no-commit
|
|||
|
(magit-git-string "mktree")
|
|||
|
"HEAD"))
|
|||
|
(magit-diff-options (append '("--cached") magit-diff-options)))
|
|||
|
(magit-git-insert-section (staged "Staged changes:")
|
|||
|
(apply-partially #'magit-wash-raw-diffs t)
|
|||
|
"diff-index" "--cached" base)))))
|
|||
|
|
|||
|
(defun magit-insert-unpulled-or-recent-commits ()
|
|||
|
(let ((tracked (magit-get-tracked-branch nil t)))
|
|||
|
(if (and tracked
|
|||
|
(not (equal (magit-git-string "rev-parse" "HEAD")
|
|||
|
(magit-git-string "rev-parse" tracked))))
|
|||
|
(magit-insert-unpulled-commits)
|
|||
|
(magit-git-insert-section (recent "Recent commits:")
|
|||
|
(apply-partially 'magit-wash-log 'unique)
|
|||
|
"log" "--format=format:%h %s" "-n" "10"))))
|
|||
|
|
|||
|
(defun magit-insert-unpulled-commits ()
|
|||
|
(let ((tracked (magit-get-tracked-branch nil t)))
|
|||
|
(when tracked
|
|||
|
(magit-git-insert-section (unpulled "Unpulled commits:")
|
|||
|
(apply-partially 'magit-wash-log 'unique)
|
|||
|
"log" "--format=format:%h %s" (concat "HEAD.." tracked)))))
|
|||
|
|
|||
|
(defun magit-insert-unpushed-commits ()
|
|||
|
(let ((tracked (magit-get-tracked-branch nil t)))
|
|||
|
(when tracked
|
|||
|
(magit-git-insert-section (unpushed "Unpushed commits:")
|
|||
|
(apply-partially 'magit-wash-log 'unique)
|
|||
|
"log" "--format=format:%h %s" (concat tracked "..HEAD")))))
|
|||
|
|
|||
|
(defun magit-insert-unpulled-cherries ()
|
|||
|
(let ((tracked (magit-get-tracked-branch nil t)))
|
|||
|
(when tracked
|
|||
|
(magit-git-insert-section (unpulled "Unpulled commits:")
|
|||
|
(apply-partially 'magit-wash-log 'cherry)
|
|||
|
"cherry" "-v" (magit-abbrev-arg) (magit-get-current-branch) tracked))))
|
|||
|
|
|||
|
(defun magit-insert-unpushed-cherries ()
|
|||
|
(let ((tracked (magit-get-tracked-branch nil t)))
|
|||
|
(when tracked
|
|||
|
(magit-git-insert-section (unpushed "Unpushed commits:")
|
|||
|
(apply-partially 'magit-wash-log 'cherry)
|
|||
|
"cherry" "-v" (magit-abbrev-arg) tracked))))
|
|||
|
|
|||
|
;;;; Line Sections
|
|||
|
|
|||
|
(defun magit-insert-empty-line ()
|
|||
|
(insert "\n"))
|
|||
|
|
|||
|
(defun magit-insert-status-local-line ()
|
|||
|
(let ((branch (or (magit-get-current-branch) "(detached)")))
|
|||
|
(magit-insert-line-section (branch branch)
|
|||
|
(concat "Local: "
|
|||
|
(propertize branch 'face 'magit-branch)
|
|||
|
" " (abbreviate-file-name default-directory)))))
|
|||
|
|
|||
|
(defun magit-insert-status-remote-line ()
|
|||
|
(let* ((branch (magit-get-current-branch))
|
|||
|
(tracked (magit-get-tracked-branch branch)))
|
|||
|
(when tracked
|
|||
|
(magit-insert-line-section (branch tracked)
|
|||
|
(concat "Remote: "
|
|||
|
(and (magit-get-boolean "branch" branch "rebase") "onto ")
|
|||
|
(magit-format-tracked-line tracked branch))))))
|
|||
|
|
|||
|
(defun magit-format-tracked-line (tracked branch)
|
|||
|
(when tracked
|
|||
|
(setq tracked (propertize tracked 'face 'magit-branch))
|
|||
|
(let ((remote (magit-get "branch" branch "remote")))
|
|||
|
(concat (if (string= "." remote)
|
|||
|
(concat "branch " tracked)
|
|||
|
(when (string-match (concat "^" remote) tracked)
|
|||
|
(setq tracked (substring tracked (1+ (length remote)))))
|
|||
|
(concat tracked " @ " remote
|
|||
|
" (" (magit-get "remote" remote "url") ")"))))))
|
|||
|
|
|||
|
(defun magit-insert-status-head-line ()
|
|||
|
(let ((hash (magit-git-string "rev-parse" "--verify" "HEAD")))
|
|||
|
(if hash
|
|||
|
(magit-insert-line-section (commit hash)
|
|||
|
(concat "Head: " (magit-format-rev-summary "HEAD")))
|
|||
|
(magit-insert-line-section (no-commit)
|
|||
|
"Head: nothing committed yet"))))
|
|||
|
|
|||
|
(defun magit-insert-status-tags-line ()
|
|||
|
(let* ((current-tag (magit-get-current-tag t))
|
|||
|
(next-tag (magit-get-next-tag t))
|
|||
|
(both-tags (and current-tag next-tag t))
|
|||
|
(tag-subject (eq magit-status-tags-line-subject 'tag)))
|
|||
|
(when (or current-tag next-tag)
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat
|
|||
|
(if both-tags "Tags: " "Tag: ")
|
|||
|
(and current-tag (apply 'magit-format-status-tag-sentence
|
|||
|
tag-subject current-tag))
|
|||
|
(and both-tags ", ")
|
|||
|
(and next-tag (apply 'magit-format-status-tag-sentence
|
|||
|
(not tag-subject) next-tag)))))))
|
|||
|
|
|||
|
(defun magit-format-status-tag-sentence (behindp tag cnt &rest ignored)
|
|||
|
(concat (propertize tag 'face 'magit-tag)
|
|||
|
(and (> cnt 0)
|
|||
|
(concat (if (eq magit-status-tags-line-subject 'tag)
|
|||
|
(concat " (" (propertize (format "%s" cnt)
|
|||
|
'face 'magit-branch))
|
|||
|
(format " (%i" cnt))
|
|||
|
" " (if behindp "behind" "ahead") ")"))))
|
|||
|
|
|||
|
;;;; Progress Sections
|
|||
|
|
|||
|
(defun magit-insert-status-merge-line ()
|
|||
|
(let ((heads (magit-file-lines (magit-git-dir "MERGE_HEAD"))))
|
|||
|
(when heads
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat
|
|||
|
"Merging: "
|
|||
|
(mapconcat 'identity (mapcar 'magit-name-rev heads) ", ")
|
|||
|
(and magit-status-show-sequence-help
|
|||
|
"; Resolve conflicts, or press \"m A\" to Abort"))))))
|
|||
|
|
|||
|
(defun magit-insert-status-rebase-lines ()
|
|||
|
(let ((rebase (magit-rebase-info)))
|
|||
|
(when rebase
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat (if (nth 4 rebase) "Applying" "Rebasing")
|
|||
|
(apply 'format ": onto %s (%s of %s)" rebase)
|
|||
|
(and magit-status-show-sequence-help
|
|||
|
"; Press \"R\" to Abort, Skip, or Continue")))
|
|||
|
(when (and (null (nth 4 rebase)) (nth 3 rebase))
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat "Stopped: "
|
|||
|
(magit-format-rev-summary (nth 3 rebase))))))))
|
|||
|
|
|||
|
(defun magit-insert-rebase-sequence ()
|
|||
|
(let ((f (magit-git-dir "rebase-merge/git-rebase-todo")))
|
|||
|
(when (file-exists-p f)
|
|||
|
(magit-with-section (section rebase-todo 'rebase-todo "Rebasing:" t)
|
|||
|
(cl-loop
|
|||
|
for line in (magit-file-lines f)
|
|||
|
when (string-match
|
|||
|
"^\\(pick\\|reword\\|edit\\|squash\\|fixup\\) \\([^ ]+\\) \\(.*\\)$"
|
|||
|
line)
|
|||
|
do (let ((cmd (match-string 1 line))
|
|||
|
(hash (match-string 2 line))
|
|||
|
(msg (match-string 3 line)))
|
|||
|
(magit-with-section (section commit hash)
|
|||
|
(insert cmd " ")
|
|||
|
(insert (propertize
|
|||
|
(magit-git-string "rev-parse" "--short" hash)
|
|||
|
'face 'magit-log-sha1))
|
|||
|
(insert " " msg "\n"))))
|
|||
|
(insert "\n")))))
|
|||
|
|
|||
|
(defun magit-insert-bisect-output ()
|
|||
|
(when (magit-bisecting-p)
|
|||
|
(let ((lines
|
|||
|
(or (magit-file-lines (magit-git-dir "BISECT_CMD_OUTPUT"))
|
|||
|
(list "Bisecting: (no saved bisect output)"
|
|||
|
"It appears you have invoked `git bisect' from a shell."
|
|||
|
"There is nothing wrong with that, we just cannot display"
|
|||
|
"anything useful here. Consult the shell output instead.")))
|
|||
|
(done-re "^[a-z0-9]\\{40\\} is the first bad commit$"))
|
|||
|
(magit-with-section
|
|||
|
(section bisect-output 'bisect-output
|
|||
|
(propertize
|
|||
|
(or (and (string-match done-re (car lines)) (pop lines))
|
|||
|
(cl-find-if (apply-partially 'string-match done-re)
|
|||
|
lines)
|
|||
|
(pop lines))
|
|||
|
'face 'magit-section-title)
|
|||
|
t t)
|
|||
|
(dolist (line lines)
|
|||
|
(insert line "\n"))))
|
|||
|
(insert "\n")))
|
|||
|
|
|||
|
(defun magit-insert-bisect-rest ()
|
|||
|
(when (magit-bisecting-p)
|
|||
|
(magit-git-insert-section (bisect-view "Bisect Rest:")
|
|||
|
(apply-partially 'magit-wash-log 'bisect-vis)
|
|||
|
"bisect" "visualize" "git" "log"
|
|||
|
"--pretty=format:%h%d %s" "--decorate=full")))
|
|||
|
|
|||
|
(defun magit-insert-bisect-log ()
|
|||
|
(when (magit-bisecting-p)
|
|||
|
(magit-git-insert-section (bisect-log "Bisect Log:")
|
|||
|
#'magit-wash-bisect-log
|
|||
|
"bisect" "log")))
|
|||
|
|
|||
|
(defun magit-wash-bisect-log ()
|
|||
|
(let (beg)
|
|||
|
(while (progn (setq beg (point-marker))
|
|||
|
(re-search-forward "^\\(git bisect [^\n]+\n\\)" nil t))
|
|||
|
(let ((heading (match-string-no-properties 1)))
|
|||
|
(delete-region (match-beginning 0) (match-end 0))
|
|||
|
(save-restriction
|
|||
|
(narrow-to-region beg (point))
|
|||
|
(goto-char (point-min))
|
|||
|
(magit-with-section (section bisect-log 'bisect-log heading nil t)
|
|||
|
(magit-wash-sequence
|
|||
|
(apply-partially 'magit-wash-log-line 'bisect-log
|
|||
|
(magit-abbrev-length)))))))
|
|||
|
(when (re-search-forward
|
|||
|
"# first bad commit: \\[\\([a-z0-9]\\{40\\}\\)\\] [^\n]+\n" nil t)
|
|||
|
(let ((hash (match-string-no-properties 1)))
|
|||
|
(delete-region (match-beginning 0) (match-end 0))
|
|||
|
(magit-with-section
|
|||
|
(section 'bisect-log 'bisect-log
|
|||
|
(concat hash " is the first bad commit\n")))))))
|
|||
|
|
|||
|
(defun magit-bisecting-p ()
|
|||
|
(file-exists-p (magit-git-dir "BISECT_LOG")))
|
|||
|
|
|||
|
;;; Utilities (2)
|
|||
|
;;;; Save Buffers
|
|||
|
|
|||
|
(defvar magit-default-directory nil)
|
|||
|
|
|||
|
(defun magit-save-some-buffers (&optional msg pred topdir)
|
|||
|
"Save some buffers if variable `magit-save-some-buffers' is non-nil.
|
|||
|
If variable `magit-save-some-buffers' is set to `dontask' then
|
|||
|
don't ask the user before saving the buffers, just go ahead and
|
|||
|
do it.
|
|||
|
|
|||
|
Optional argument MSG is displayed in the minibuffer if variable
|
|||
|
`magit-save-some-buffers' is nil.
|
|||
|
|
|||
|
Optional second argument PRED determines which buffers are considered:
|
|||
|
If PRED is nil, all the file-visiting buffers are considered.
|
|||
|
If PRED is t, then certain non-file buffers will also be considered.
|
|||
|
If PRED is a zero-argument function, it indicates for each buffer whether
|
|||
|
to consider it or not when called with that buffer current."
|
|||
|
(interactive)
|
|||
|
(let ((predicate-function (or pred magit-save-some-buffers-predicate))
|
|||
|
(magit-default-directory (or topdir default-directory)))
|
|||
|
(if magit-save-some-buffers
|
|||
|
(save-some-buffers
|
|||
|
(eq magit-save-some-buffers 'dontask)
|
|||
|
predicate-function)
|
|||
|
(when msg
|
|||
|
(message msg)))))
|
|||
|
|
|||
|
(defun magit-save-buffers-predicate-all ()
|
|||
|
"Prompt to save all buffers with unsaved changes."
|
|||
|
t)
|
|||
|
|
|||
|
(defun magit-save-buffers-predicate-tree-only ()
|
|||
|
"Only prompt to save buffers which are within the current git project.
|
|||
|
As determined by the directory passed to `magit-status'."
|
|||
|
(and buffer-file-name
|
|||
|
(let ((topdir (magit-get-top-dir magit-default-directory)))
|
|||
|
(and topdir
|
|||
|
(equal (file-remote-p topdir) (file-remote-p buffer-file-name))
|
|||
|
;; ^ Avoid needlessly connecting to unrelated tramp remotes.
|
|||
|
(string= topdir (magit-get-top-dir
|
|||
|
(file-name-directory buffer-file-name)))))))
|
|||
|
|
|||
|
;;; Porcelain
|
|||
|
;;;; Apply
|
|||
|
;;;;; Apply Commands
|
|||
|
;;;;;; Apply
|
|||
|
|
|||
|
(defun magit-apply-item ()
|
|||
|
"Apply the item at point to the current working tree."
|
|||
|
(interactive)
|
|||
|
(magit-section-action apply (info)
|
|||
|
(([* unstaged] [* staged])
|
|||
|
(user-error "Change is already in your working tree"))
|
|||
|
(hunk (magit-apply-hunk-item it))
|
|||
|
(diff (magit-apply-diff-item it))
|
|||
|
(stash (magit-stash-apply info))
|
|||
|
(commit (magit-apply-commit info))))
|
|||
|
|
|||
|
;;;;;; Stage
|
|||
|
|
|||
|
(defun magit-stage-item (&optional file)
|
|||
|
"Add the item at point to the staging area.
|
|||
|
With a prefix argument, prompt for a file to be staged instead."
|
|||
|
(interactive
|
|||
|
(when current-prefix-arg
|
|||
|
(list (file-relative-name (read-file-name "File to stage: " nil nil t)
|
|||
|
(magit-get-top-dir)))))
|
|||
|
(if file
|
|||
|
(magit-run-git "add" file)
|
|||
|
(magit-section-action stage (info)
|
|||
|
([file untracked]
|
|||
|
(magit-run-git
|
|||
|
(cond
|
|||
|
((use-region-p)
|
|||
|
(cons "add" (magit-section-region-siblings #'magit-section-info)))
|
|||
|
((and (string-match-p "/$" info)
|
|||
|
(file-exists-p (expand-file-name ".git" info)))
|
|||
|
(let ((repo (read-string
|
|||
|
"Add submodule tracking remote repo (empty to abort): "
|
|||
|
(let ((default-directory
|
|||
|
(file-name-as-directory
|
|||
|
(expand-file-name info default-directory))))
|
|||
|
(magit-get "remote.origin.url")))))
|
|||
|
(if (equal repo "")
|
|||
|
(user-error "Abort")
|
|||
|
(list "submodule" "add" repo (substring info 0 -1)))))
|
|||
|
(t
|
|||
|
(list "add" info)))))
|
|||
|
(untracked
|
|||
|
(magit-run-git "add" "--" (magit-git-lines "ls-files" "--other"
|
|||
|
"--exclude-standard")))
|
|||
|
([hunk diff unstaged]
|
|||
|
(magit-apply-hunk-item it "--cached"))
|
|||
|
([diff unstaged]
|
|||
|
(magit-run-git "add" "-u"
|
|||
|
(if (use-region-p)
|
|||
|
(magit-section-region-siblings #'magit-section-info)
|
|||
|
info)))
|
|||
|
(unstaged
|
|||
|
(magit-stage-all))
|
|||
|
([* staged]
|
|||
|
(user-error "Already staged"))
|
|||
|
(hunk (user-error "Can't stage this hunk"))
|
|||
|
(diff (user-error "Can't stage this diff")))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-stage-all (&optional including-untracked)
|
|||
|
"Add all remaining changes in tracked files to staging area.
|
|||
|
With a prefix argument, add remaining untracked files as well.
|
|||
|
\('git add [-u] .')."
|
|||
|
(interactive "P")
|
|||
|
(when (or (not magit-stage-all-confirm)
|
|||
|
(not (magit-anything-staged-p))
|
|||
|
(yes-or-no-p "Stage all changes? "))
|
|||
|
(if including-untracked
|
|||
|
(magit-run-git "add" ".")
|
|||
|
(magit-run-git "add" "-u" "."))))
|
|||
|
|
|||
|
;;;;;; Unstage
|
|||
|
|
|||
|
(defun magit-unstage-item ()
|
|||
|
"Remove the item at point from the staging area."
|
|||
|
(interactive)
|
|||
|
(magit-section-action unstage (info)
|
|||
|
([hunk diff staged]
|
|||
|
(magit-apply-hunk-item it "--reverse" "--cached"))
|
|||
|
([diff staged]
|
|||
|
(when (eq info 'unmerged)
|
|||
|
(user-error "Can't unstage an unmerged file. Resolve it first"))
|
|||
|
(let ((files (if (use-region-p)
|
|||
|
(magit-section-region-siblings #'magit-section-info)
|
|||
|
(list info))))
|
|||
|
(if (magit-no-commit-p)
|
|||
|
(magit-run-git "rm" "--cached" "--" files)
|
|||
|
(magit-run-git "reset" "-q" "HEAD" "--" files))))
|
|||
|
(staged
|
|||
|
(magit-unstage-all))
|
|||
|
([* unstaged]
|
|||
|
(user-error "Already unstaged"))
|
|||
|
(hunk (user-error "Can't unstage this hunk"))
|
|||
|
(diff (user-error "Can't unstage this diff"))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-unstage-all ()
|
|||
|
"Remove all changes from staging area.
|
|||
|
\('git reset --mixed HEAD')."
|
|||
|
(interactive)
|
|||
|
(when (or (not magit-unstage-all-confirm)
|
|||
|
(and (not (magit-anything-unstaged-p))
|
|||
|
(not (magit-git-lines "ls-files" "--others" "-t"
|
|||
|
"--exclude-standard")))
|
|||
|
(yes-or-no-p "Unstage all changes? "))
|
|||
|
(magit-run-git "reset" "HEAD" "--")))
|
|||
|
|
|||
|
;;;;;; Discard
|
|||
|
|
|||
|
(defun magit-discard-item ()
|
|||
|
"Remove the change introduced by the item at point."
|
|||
|
(interactive)
|
|||
|
(magit-section-action discard (info parent-info)
|
|||
|
([file untracked]
|
|||
|
(when (yes-or-no-p (format "Delete %s? " info))
|
|||
|
(if (and (file-directory-p info)
|
|||
|
(not (file-symlink-p info)))
|
|||
|
(delete-directory info 'recursive)
|
|||
|
(delete-file info))
|
|||
|
(magit-refresh)))
|
|||
|
(untracked
|
|||
|
(when (yes-or-no-p "Delete all untracked files and directories? ")
|
|||
|
(magit-run-git "clean" "-df")))
|
|||
|
([hunk diff unstaged]
|
|||
|
(when (yes-or-no-p (if (use-region-p)
|
|||
|
"Discard changes in region? "
|
|||
|
"Discard hunk? "))
|
|||
|
(magit-apply-hunk-item it "--reverse")))
|
|||
|
([hunk diff staged]
|
|||
|
(if (magit-file-uptodate-p parent-info)
|
|||
|
(when (yes-or-no-p (if (use-region-p)
|
|||
|
"Discard changes in region? "
|
|||
|
"Discard hunk? "))
|
|||
|
(magit-apply-hunk-item it "--reverse" "--index"))
|
|||
|
(user-error "Can't discard this hunk. Please unstage it first")))
|
|||
|
([diff unstaged]
|
|||
|
(magit-discard-diff it nil))
|
|||
|
([diff staged]
|
|||
|
(if (magit-file-uptodate-p (magit-section-info it))
|
|||
|
(magit-discard-diff it t)
|
|||
|
(user-error "Can't discard staged changes to this file. \
|
|||
|
Please unstage it first")))
|
|||
|
(hunk (user-error "Can't discard this hunk"))
|
|||
|
(diff (user-error "Can't discard this diff"))
|
|||
|
(stash (when (yes-or-no-p "Discard stash? ")
|
|||
|
(magit-stash-drop info)))
|
|||
|
(branch (when (yes-or-no-p
|
|||
|
(if current-prefix-arg
|
|||
|
(concat "Force delete branch [" info "]? ")
|
|||
|
(concat "Delete branch [" info "]? ")))
|
|||
|
(magit-delete-branch info current-prefix-arg)))
|
|||
|
(remote (when (yes-or-no-p "Remove remote? ")
|
|||
|
(magit-remove-remote info)))))
|
|||
|
|
|||
|
;;;;;; Revert
|
|||
|
|
|||
|
(defun magit-revert-item ()
|
|||
|
"Revert the item at point.
|
|||
|
The change introduced by the item is reversed in the current
|
|||
|
working tree."
|
|||
|
(interactive)
|
|||
|
(magit-section-action revert (info)
|
|||
|
([* unstaged] (magit-discard-item))
|
|||
|
(commit (when (or (not magit-revert-item-confirm)
|
|||
|
(yes-or-no-p "Revert this commit? "))
|
|||
|
(magit-revert-commit info)))
|
|||
|
(diff (when (or (not magit-revert-item-confirm)
|
|||
|
(yes-or-no-p "Revert this diff? "))
|
|||
|
(magit-apply-diff-item it "--reverse")))
|
|||
|
(hunk (when (or (not magit-revert-item-confirm)
|
|||
|
(yes-or-no-p "Revert this hunk? "))
|
|||
|
(magit-apply-hunk-item it "--reverse")))))
|
|||
|
|
|||
|
(defun magit-revert-commit (commit)
|
|||
|
(magit-assert-one-parent commit "revert")
|
|||
|
(magit-run-git "revert" "--no-commit" commit))
|
|||
|
|
|||
|
(defconst magit-revert-backup-file "magit/reverted.diff")
|
|||
|
|
|||
|
(defun magit-revert-undo ()
|
|||
|
"Re-apply the previously reverted hunk.
|
|||
|
Also see option `magit-revert-backup'."
|
|||
|
(interactive)
|
|||
|
(let ((file (magit-git-dir magit-revert-backup-file)))
|
|||
|
(if (file-readable-p file)
|
|||
|
(magit-run-git "apply" file)
|
|||
|
(user-error "No backups exist"))
|
|||
|
(magit-refresh)))
|
|||
|
|
|||
|
;;;;; Apply Core
|
|||
|
|
|||
|
(defun magit-discard-diff (diff stagedp)
|
|||
|
(let ((file (magit-section-info diff)))
|
|||
|
(cl-case (magit-section-diff-status diff)
|
|||
|
(deleted
|
|||
|
(when (yes-or-no-p (format "Resurrect %s? " file))
|
|||
|
(when stagedp
|
|||
|
(magit-run-git "reset" "-q" "--" file))
|
|||
|
(magit-run-git "checkout" "--" file)))
|
|||
|
(new
|
|||
|
(when (yes-or-no-p (format "Delete %s? " file))
|
|||
|
(magit-run-git "rm" "-f" "--" file)))
|
|||
|
(t
|
|||
|
(when (yes-or-no-p (format "Discard changes to %s? " file))
|
|||
|
(if stagedp
|
|||
|
(magit-run-git "checkout" "HEAD" "--" file)
|
|||
|
(magit-run-git "checkout" "--" file)))))))
|
|||
|
|
|||
|
(defun magit-apply-commit (commit)
|
|||
|
(magit-assert-one-parent commit "cherry-pick")
|
|||
|
(magit-run-git "cherry-pick" "--no-commit" commit))
|
|||
|
|
|||
|
(defun magit-apply-diff-item (diff &rest args)
|
|||
|
(when (zerop magit-diff-context-lines)
|
|||
|
(setq args (cons "--unidiff-zero" args)))
|
|||
|
(let ((buf (generate-new-buffer " *magit-input*")))
|
|||
|
(unwind-protect
|
|||
|
(progn (magit-insert-diff-item-patch diff buf)
|
|||
|
(magit-run-git-with-input
|
|||
|
buf "apply" args "--ignore-space-change" "-"))
|
|||
|
(kill-buffer buf))))
|
|||
|
|
|||
|
(defun magit-apply-hunk-item (hunk &rest args)
|
|||
|
"Apply single hunk or part of a hunk to the index or working file.
|
|||
|
|
|||
|
This function is the core of magit's stage, unstage, apply, and
|
|||
|
revert operations. HUNK (or the portion of it selected by the
|
|||
|
region) will be applied to either the index, if \"--cached\" is a
|
|||
|
member of ARGS, or to the working file otherwise."
|
|||
|
(when (string-match "^diff --cc" (magit-section-parent-info hunk))
|
|||
|
(user-error (concat "Cannot un-/stage individual resolution hunks. "
|
|||
|
"Please stage the whole file.")))
|
|||
|
(let ((use-region (use-region-p)))
|
|||
|
(when (zerop magit-diff-context-lines)
|
|||
|
(setq args (cons "--unidiff-zero" args))
|
|||
|
(when use-region
|
|||
|
(user-error (concat "Not enough context to partially apply hunk. "
|
|||
|
"Use `+' to increase context."))))
|
|||
|
(let ((buf (generate-new-buffer " *magit-input*")))
|
|||
|
(unwind-protect
|
|||
|
(progn (if use-region
|
|||
|
(magit-insert-hunk-item-region-patch
|
|||
|
hunk (member "--reverse" args)
|
|||
|
(region-beginning) (region-end) buf)
|
|||
|
(magit-insert-hunk-item-patch hunk buf))
|
|||
|
(magit-revert-backup buf args)
|
|||
|
(magit-run-git-with-input
|
|||
|
buf "apply" args "--ignore-space-change" "-"))
|
|||
|
(kill-buffer buf)))))
|
|||
|
|
|||
|
(defun magit-insert-diff-item-patch (diff buf)
|
|||
|
(magit-insert-region (magit-section-content-beginning diff)
|
|||
|
(magit-section-end diff)
|
|||
|
buf))
|
|||
|
|
|||
|
(defun magit-insert-hunk-item-patch (hunk buf)
|
|||
|
(magit-diff-item-insert-header (magit-section-parent hunk) buf)
|
|||
|
(magit-insert-region (magit-section-beginning hunk)
|
|||
|
(magit-section-end hunk)
|
|||
|
buf))
|
|||
|
|
|||
|
(defun magit-insert-hunk-item-region-patch (hunk reverse beg end buf)
|
|||
|
(magit-diff-item-insert-header (magit-section-parent hunk) buf)
|
|||
|
(save-excursion
|
|||
|
(goto-char (magit-section-beginning hunk))
|
|||
|
(magit-insert-current-line buf)
|
|||
|
(forward-line)
|
|||
|
(let ((copy-op (if reverse "+" "-")))
|
|||
|
(while (< (point) (magit-section-end hunk))
|
|||
|
(cond ((and (<= beg (point)) (< (point) end))
|
|||
|
(magit-insert-current-line buf))
|
|||
|
((looking-at " ")
|
|||
|
(magit-insert-current-line buf))
|
|||
|
((looking-at copy-op)
|
|||
|
(let ((text (buffer-substring-no-properties
|
|||
|
(+ (point) 1) (line-beginning-position 2))))
|
|||
|
(with-current-buffer buf
|
|||
|
(insert " " text)))))
|
|||
|
(forward-line))))
|
|||
|
(with-current-buffer buf
|
|||
|
(diff-fixup-modifs (point-min) (point-max))))
|
|||
|
|
|||
|
(defun magit-diff-item-insert-header (diff buf)
|
|||
|
(magit-insert-region (magit-section-content-beginning diff)
|
|||
|
(if (magit-section-children diff)
|
|||
|
(magit-section-beginning
|
|||
|
(car (magit-section-children diff)))
|
|||
|
(magit-section-end diff))
|
|||
|
buf))
|
|||
|
|
|||
|
(defun magit-insert-region (beg end buf)
|
|||
|
(let ((text (buffer-substring-no-properties beg end)))
|
|||
|
(with-current-buffer buf
|
|||
|
(insert text))))
|
|||
|
|
|||
|
(defun magit-insert-current-line (buf)
|
|||
|
(let ((text (buffer-substring-no-properties
|
|||
|
(line-beginning-position) (line-beginning-position 2))))
|
|||
|
(with-current-buffer buf
|
|||
|
(insert text))))
|
|||
|
|
|||
|
(defun magit-revert-backup (buffer args)
|
|||
|
(when (and magit-revert-backup (member "--reverse" args))
|
|||
|
(with-current-buffer buffer
|
|||
|
(let ((buffer-file-name (magit-git-dir magit-revert-backup-file))
|
|||
|
(make-backup-files t)
|
|||
|
(backup-directory-alist nil)
|
|||
|
(version-control t)
|
|||
|
(kept-old-versions 0)
|
|||
|
(kept-new-versions 10))
|
|||
|
(make-directory (file-name-directory buffer-file-name) t)
|
|||
|
(save-buffer 16)))))
|
|||
|
|
|||
|
;;;; Visit
|
|||
|
|
|||
|
(defun magit-visit-item (&optional other-window)
|
|||
|
"Visit current item.
|
|||
|
With a prefix argument, visit in other window."
|
|||
|
(interactive "P")
|
|||
|
(magit-section-action visit (info parent-info)
|
|||
|
((diff diffstat [file untracked])
|
|||
|
(magit-visit-file-item info other-window))
|
|||
|
(hunk (magit-visit-file-item parent-info other-window
|
|||
|
(magit-hunk-item-target-line it)
|
|||
|
(current-column)))
|
|||
|
(commit (magit-show-commit info))
|
|||
|
(stash (magit-diff-stash info))
|
|||
|
(branch (magit-checkout info))))
|
|||
|
|
|||
|
(defun magit-visit-file-item (file &optional other-window line column)
|
|||
|
(unless file
|
|||
|
(user-error "Can't get pathname for this file"))
|
|||
|
(unless (file-exists-p file)
|
|||
|
(user-error "Can't visit deleted file: %s" file))
|
|||
|
(if (file-directory-p file)
|
|||
|
(progn
|
|||
|
(setq file (file-name-as-directory (expand-file-name file)))
|
|||
|
(if (equal (magit-get-top-dir (file-name-directory file))
|
|||
|
(magit-get-top-dir))
|
|||
|
(magit-dired-jump other-window)
|
|||
|
(magit-status file (if other-window
|
|||
|
'pop-to-buffer
|
|||
|
'switch-to-buffer))))
|
|||
|
(if other-window
|
|||
|
(find-file-other-window file)
|
|||
|
(find-file file))
|
|||
|
(when line
|
|||
|
(goto-char (point-min))
|
|||
|
(forward-line (1- line))
|
|||
|
(when (> column 0)
|
|||
|
(move-to-column (1- column))))))
|
|||
|
|
|||
|
(defun magit-hunk-item-target-line (hunk)
|
|||
|
(save-excursion
|
|||
|
(beginning-of-line)
|
|||
|
(let ((line (line-number-at-pos)))
|
|||
|
(goto-char (magit-section-beginning hunk))
|
|||
|
(unless (looking-at "@@+ .* \\+\\([0-9]+\\)\\(,[0-9]+\\)? @@+")
|
|||
|
(user-error "Hunk header not found"))
|
|||
|
(let ((target (string-to-number (match-string 1))))
|
|||
|
(forward-line)
|
|||
|
(while (< (line-number-at-pos) line)
|
|||
|
;; XXX - deal with combined diffs
|
|||
|
(unless (looking-at "-")
|
|||
|
(setq target (+ target 1)))
|
|||
|
(forward-line))
|
|||
|
target))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-dired-jump (&optional other-window)
|
|||
|
"Visit current item in dired.
|
|||
|
With a prefix argument, visit in other window."
|
|||
|
(interactive "P")
|
|||
|
(require 'dired-x)
|
|||
|
(dired-jump other-window
|
|||
|
(file-truename
|
|||
|
(magit-section-action dired-jump (info parent-info)
|
|||
|
([file untracked] info)
|
|||
|
((diff diffstat) info)
|
|||
|
(hunk parent-info)
|
|||
|
(t default-directory)))))
|
|||
|
|
|||
|
(defvar-local magit-file-log-file nil)
|
|||
|
(defvar-local magit-show-current-version nil)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-show (rev file &optional switch-function)
|
|||
|
"Display and select a buffer containing FILE as stored in REV.
|
|||
|
|
|||
|
Insert the contents of FILE as stored in the revision REV into a
|
|||
|
buffer. Then select the buffer using `pop-to-buffer' or with a
|
|||
|
prefix argument using `switch-to-buffer'. Non-interactivity use
|
|||
|
SWITCH-FUNCTION to switch to the buffer, if that is nil simply
|
|||
|
return the buffer, without displaying it."
|
|||
|
;; REV may also be one of the symbols `index' or `working' but
|
|||
|
;; that is only intended for use by `magit-ediff'.
|
|||
|
(interactive
|
|||
|
(let (rev file section)
|
|||
|
(magit-section-case (info parent)
|
|||
|
(commit (setq file magit-file-log-file rev info))
|
|||
|
(hunk (setq section parent))
|
|||
|
(diff (setq section it)))
|
|||
|
(if section
|
|||
|
(setq rev (car (magit-diff-range section))
|
|||
|
file (magit-section-info section))
|
|||
|
(unless rev
|
|||
|
(setq rev (magit-get-current-branch))))
|
|||
|
(setq rev (magit-read-rev "Retrieve file from revision" rev)
|
|||
|
file (cl-case rev
|
|||
|
(working (read-file-name "Find file: "))
|
|||
|
(index (magit-read-file-from-rev "HEAD" file))
|
|||
|
(t (magit-read-file-from-rev rev file))))
|
|||
|
(list rev file (if current-prefix-arg
|
|||
|
'switch-to-buffer
|
|||
|
'pop-to-buffer))))
|
|||
|
(let (buffer)
|
|||
|
(if (eq rev 'working)
|
|||
|
(setq buffer (find-file-noselect file))
|
|||
|
(let ((name (format "%s.%s" file
|
|||
|
(if (symbolp rev)
|
|||
|
(format "@{%s}" rev)
|
|||
|
(replace-regexp-in-string "/" ":" rev)))))
|
|||
|
(setq buffer (get-buffer name))
|
|||
|
(when buffer
|
|||
|
(with-current-buffer buffer
|
|||
|
(if (and (equal file magit-file-name)
|
|||
|
(equal rev magit-show-current-version))
|
|||
|
(let ((inhibit-read-only t))
|
|||
|
(erase-buffer))
|
|||
|
(setq buffer nil))))
|
|||
|
(with-current-buffer
|
|||
|
(or buffer (setq buffer (create-file-buffer name)))
|
|||
|
(setq buffer-read-only t)
|
|||
|
(with-silent-modifications
|
|||
|
(if (eq rev 'index)
|
|||
|
(let ((temp (car (split-string
|
|||
|
(magit-git-string "checkout-index"
|
|||
|
"--temp" file)
|
|||
|
"\t")))
|
|||
|
(inhibit-read-only t))
|
|||
|
(insert-file-contents temp nil nil nil t)
|
|||
|
(delete-file temp))
|
|||
|
(magit-git-insert "cat-file" "-p" (concat rev ":" file))))
|
|||
|
(let ((buffer-file-name (expand-file-name file (magit-get-top-dir))))
|
|||
|
(normal-mode t))
|
|||
|
(setq magit-file-name file)
|
|||
|
(setq magit-show-current-version rev)
|
|||
|
(goto-char (point-min)))))
|
|||
|
(when switch-function
|
|||
|
(with-current-buffer buffer
|
|||
|
(funcall switch-function (current-buffer))))
|
|||
|
buffer))
|
|||
|
|
|||
|
;;;; Act
|
|||
|
;;;;; Merging
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-merge (revision &optional do-commit)
|
|||
|
"Merge REVISION into the current 'HEAD', leaving changes uncommitted.
|
|||
|
With a prefix argument, skip editing the log message and commit.
|
|||
|
\('git merge [--no-commit] REVISION')."
|
|||
|
(interactive (list (magit-read-rev "Merge"
|
|||
|
(or (magit-guess-branch)
|
|||
|
(magit-get-previous-branch)))
|
|||
|
current-prefix-arg))
|
|||
|
(when (or (not (magit-anything-modified-p))
|
|||
|
(not magit-merge-warn-dirty-worktree)
|
|||
|
(yes-or-no-p
|
|||
|
"Running merge in a dirty worktree could cause data loss. Continue?"))
|
|||
|
(magit-run-git "merge" revision magit-custom-options
|
|||
|
(unless do-commit "--no-commit"))
|
|||
|
(when (file-exists-p ".git/MERGE_MSG")
|
|||
|
(let ((magit-custom-options nil))
|
|||
|
(magit-commit)))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-merge-abort ()
|
|||
|
"Abort the current merge operation."
|
|||
|
(interactive)
|
|||
|
(if (file-exists-p (magit-git-dir "MERGE_HEAD"))
|
|||
|
(when (yes-or-no-p "Abort merge? ")
|
|||
|
(magit-run-git-async "merge" "--abort"))
|
|||
|
(user-error "No merge in progress")))
|
|||
|
|
|||
|
;;;;; Branching
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-checkout (revision)
|
|||
|
"Switch 'HEAD' to REVISION and update working tree.
|
|||
|
Fails if working tree or staging area contain uncommitted changes.
|
|||
|
If REVISION is a remote branch, offer to create a local branch.
|
|||
|
\('git checkout [-b] REVISION')."
|
|||
|
(interactive
|
|||
|
(list (let ((current-branch (magit-get-current-branch))
|
|||
|
(default (or (magit-guess-branch)
|
|||
|
(magit-get-previous-branch))))
|
|||
|
(magit-read-rev (format "Switch from '%s' to" current-branch)
|
|||
|
(unless (string= current-branch default)
|
|||
|
default)
|
|||
|
current-branch))))
|
|||
|
(magit-save-some-buffers)
|
|||
|
(or (and (string-match "^\\(?:refs/\\)?remotes/\\([^/]+\\)/\\(.+\\)" revision)
|
|||
|
(let ((remote (match-string 1 revision))
|
|||
|
(branch (match-string 2 revision)))
|
|||
|
(and (yes-or-no-p (format "Create local branch for %s? " branch))
|
|||
|
(let ((local (read-string
|
|||
|
(format "Call local branch (%s): " branch)
|
|||
|
nil nil branch)))
|
|||
|
(if (magit-ref-exists-p (concat "refs/heads/" local))
|
|||
|
(user-error "'%s' already exists" local)
|
|||
|
(magit-run-git "checkout" "-b" local revision)
|
|||
|
t)))))
|
|||
|
(magit-run-git "checkout" revision)))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-create-branch (branch parent)
|
|||
|
"Switch 'HEAD' to new BRANCH at revision PARENT and update working tree.
|
|||
|
Fails if working tree or staging area contain uncommitted changes.
|
|||
|
\('git checkout -b BRANCH REVISION')."
|
|||
|
(interactive
|
|||
|
(list (read-string "Create branch: ")
|
|||
|
(magit-read-rev "Parent" (or (magit-guess-branch)
|
|||
|
(magit-get-current-branch)))))
|
|||
|
(cond ((run-hook-with-args-until-success
|
|||
|
'magit-create-branch-hook branch parent))
|
|||
|
((and branch (not (string= branch "")))
|
|||
|
(magit-save-some-buffers)
|
|||
|
(magit-run-git "checkout" magit-custom-options
|
|||
|
"-b" branch parent))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-delete-branch (branch &optional force)
|
|||
|
"Delete the BRANCH.
|
|||
|
If the branch is the current one, offers to switch to `master' first.
|
|||
|
With prefix, forces the removal even if it hasn't been merged.
|
|||
|
Works with local or remote branches.
|
|||
|
\('git branch [-d|-D] BRANCH' or 'git push <remote-part-of-BRANCH> :refs/heads/BRANCH')."
|
|||
|
(interactive (list (magit-read-rev "Branch to delete"
|
|||
|
(or (magit-guess-branch)
|
|||
|
(magit-get-previous-branch)))
|
|||
|
current-prefix-arg))
|
|||
|
(if (string-match "^\\(?:refs/\\)?remotes/\\([^/]+\\)/\\(.+\\)" branch)
|
|||
|
(magit-run-git-async "push"
|
|||
|
(match-string 1 branch)
|
|||
|
(concat ":" (match-string 2 branch)))
|
|||
|
(let* ((current (magit-get-current-branch))
|
|||
|
(is-current (string= branch current))
|
|||
|
(is-master (string= branch "master"))
|
|||
|
(args (list "branch"
|
|||
|
(if force "-D" "-d")
|
|||
|
branch)))
|
|||
|
(cond
|
|||
|
((and is-current is-master)
|
|||
|
(message "Cannot delete master branch while it's checked out."))
|
|||
|
(is-current
|
|||
|
(if (and (magit-ref-exists-p "refs/heads/master")
|
|||
|
(y-or-n-p "Cannot delete current branch. Switch to master first? "))
|
|||
|
(progn
|
|||
|
(magit-checkout "master")
|
|||
|
(magit-run-git args))
|
|||
|
(message "The current branch was not deleted.")))
|
|||
|
(t
|
|||
|
(magit-run-git args))))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-rename-branch (old new &optional force)
|
|||
|
"Rename branch OLD to NEW.
|
|||
|
With prefix, forces the rename even if NEW already exists.
|
|||
|
\('git branch [-m|-M] OLD NEW')."
|
|||
|
(interactive
|
|||
|
(let* ((old (magit-read-rev-with-default "Old name"))
|
|||
|
(new (read-string "New name: " old)))
|
|||
|
(list old new current-prefix-arg)))
|
|||
|
(if (or (null new) (string= new "")
|
|||
|
(string= old new))
|
|||
|
(message "Cannot rename branch \"%s\" to \"%s\"." old new)
|
|||
|
(magit-run-git "branch" (if force "-M" "-m") old new)))
|
|||
|
|
|||
|
(defun magit-guess-branch ()
|
|||
|
"Return a branch name depending on the context of cursor.
|
|||
|
If no branch is found near the cursor return nil."
|
|||
|
(magit-section-case (info parent-info)
|
|||
|
(branch info)
|
|||
|
([commit wazzup] parent-info)
|
|||
|
([commit ] (magit-name-rev info))
|
|||
|
([ wazzup] info)))
|
|||
|
|
|||
|
;;;;; Remoting
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-add-remote (remote url)
|
|||
|
"Add the REMOTE and fetch it.
|
|||
|
\('git remote add REMOTE URL')."
|
|||
|
(interactive (list (read-string "Remote name: ")
|
|||
|
(read-string "Remote url: ")))
|
|||
|
(magit-run-git-async "remote" "add" "-f" remote url))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-remove-remote (remote)
|
|||
|
"Delete the REMOTE.
|
|||
|
\('git remote rm REMOTE')."
|
|||
|
(interactive (list (magit-read-remote "Delete remote")))
|
|||
|
(magit-run-git "remote" "rm" remote))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-rename-remote (old new)
|
|||
|
"Rename remote OLD to NEW.
|
|||
|
\('git remote rename OLD NEW')."
|
|||
|
(interactive
|
|||
|
(let* ((old (magit-read-remote "Old name"))
|
|||
|
(new (read-string "New name: " old)))
|
|||
|
(list old new)))
|
|||
|
(if (or (null old) (string= old "")
|
|||
|
(null new) (string= new "")
|
|||
|
(string= old new))
|
|||
|
(message "Cannot rename remote \"%s\" to \"%s\"." old new)
|
|||
|
(magit-run-git "remote" "rename" old new)))
|
|||
|
|
|||
|
(defun magit-guess-remote ()
|
|||
|
(magit-section-case (info parent-info)
|
|||
|
(remote info)
|
|||
|
(branch parent-info)
|
|||
|
(t (if (string= info ".") info (magit-get-current-remote)))))
|
|||
|
|
|||
|
;;;;; Rebase
|
|||
|
|
|||
|
(defun magit-rebase-info ()
|
|||
|
"Return a list indicating the state of an in-progress rebase.
|
|||
|
|
|||
|
The returned list has the form (ONTO DONE TOTAL STOPPED AM).
|
|||
|
ONTO is the commit being rebased onto.
|
|||
|
DONE and TOTAL are integers with obvious meanings.
|
|||
|
STOPPED is the SHA-1 of the commit at which rebase stopped.
|
|||
|
AM is non-nil if the current rebase is actually a git-am.
|
|||
|
|
|||
|
Return nil if there is no rebase in progress."
|
|||
|
(let ((m (magit-git-dir "rebase-merge"))
|
|||
|
(a (magit-git-dir "rebase-apply")))
|
|||
|
(cond
|
|||
|
((file-directory-p m) ; interactive
|
|||
|
(list
|
|||
|
(magit-name-rev (magit-file-line (expand-file-name "onto" m)))
|
|||
|
(length (magit-file-lines (expand-file-name "done" m)))
|
|||
|
(cl-loop for line in (magit-file-lines
|
|||
|
(expand-file-name "git-rebase-todo.backup" m))
|
|||
|
count (string-match "^[^#\n]" line))
|
|||
|
(magit-file-line (expand-file-name "stopped-sha" m))
|
|||
|
nil))
|
|||
|
|
|||
|
((file-regular-p (expand-file-name "onto" a)) ; non-interactive
|
|||
|
(list
|
|||
|
(magit-name-rev (magit-file-line (expand-file-name "onto" a)))
|
|||
|
(1- (string-to-number (magit-file-line (expand-file-name "next" a))))
|
|||
|
(string-to-number (magit-file-line (expand-file-name "last" a)))
|
|||
|
(let ((patch-header (magit-file-line
|
|||
|
(car (directory-files a t "^[0-9]\\{4\\}$")))))
|
|||
|
(when (string-match "^From \\([a-z0-9]\\{40\\}\\) " patch-header)
|
|||
|
(match-string 1 patch-header)))
|
|||
|
nil))
|
|||
|
|
|||
|
((file-regular-p (expand-file-name "applying" a)) ; am
|
|||
|
(list
|
|||
|
(magit-name-rev "HEAD")
|
|||
|
(1- (string-to-number (magit-file-line (expand-file-name "next" a))))
|
|||
|
(string-to-number (magit-file-line (expand-file-name "last" a)))
|
|||
|
(let ((patch-header (magit-file-line
|
|||
|
(car (directory-files a t "^[0-9]\\{4\\}$")))))
|
|||
|
(when (string-match "^From \\([a-z0-9]\\{40\\}\\) " patch-header)
|
|||
|
(match-string 1 patch-header)))
|
|||
|
t)))))
|
|||
|
|
|||
|
(defun magit-rebase-step ()
|
|||
|
"Initiate or continue a rebase."
|
|||
|
(interactive)
|
|||
|
(let ((rebase (magit-rebase-info)))
|
|||
|
(if rebase
|
|||
|
(let ((cursor-in-echo-area t)
|
|||
|
(message-log-max nil)
|
|||
|
(am (nth 4 rebase)))
|
|||
|
(message "%s in progress. [A]bort, [S]kip, or [C]ontinue? "
|
|||
|
(if am "Apply mailbox" "Rebase"))
|
|||
|
(cl-case (read-event)
|
|||
|
((?A ?a) (magit-run-git-async (if am "am" "rebase") "--abort"))
|
|||
|
((?S ?s) (magit-run-git-async (if am "am" "rebase") "--skip"))
|
|||
|
((?C ?c) (magit-with-emacsclient magit-server-window-for-commit
|
|||
|
(magit-run-git-async (if am "am" "rebase") "--continue")))))
|
|||
|
(let* ((branch (magit-get-current-branch))
|
|||
|
(rev (magit-read-rev
|
|||
|
"Rebase to"
|
|||
|
(magit-get-tracked-branch branch)
|
|||
|
branch)))
|
|||
|
(magit-run-git "rebase" rev)))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-interactive-rebase (commit)
|
|||
|
"Start a git rebase -i session, old school-style."
|
|||
|
(interactive (let ((commit (magit-section-case (info) (commit info))))
|
|||
|
(list (if commit
|
|||
|
(concat commit "^")
|
|||
|
(magit-read-rev "Interactively rebase to"
|
|||
|
(magit-guess-branch))))))
|
|||
|
(magit-assert-emacsclient "rebase interactively")
|
|||
|
(magit-with-emacsclient magit-server-window-for-rebase
|
|||
|
(magit-run-git-async "rebase" "-i" commit)))
|
|||
|
|
|||
|
;;;;; AM
|
|||
|
|
|||
|
(defun magit-apply-mailbox (&optional file-or-dir)
|
|||
|
"Apply a series of patches from a mailbox."
|
|||
|
(interactive "fmbox or Maildir file or directory: ")
|
|||
|
(magit-with-emacsclient magit-server-window-for-rebase
|
|||
|
(magit-run-git-async "am" file-or-dir)))
|
|||
|
|
|||
|
;;;;; Reset
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-reset-head (revision &optional hard)
|
|||
|
"Switch 'HEAD' to REVISION, keeping prior working tree and staging area.
|
|||
|
Any differences from REVISION become new changes to be committed.
|
|||
|
With prefix argument, all uncommitted changes in working tree
|
|||
|
and staging area are lost.
|
|||
|
\('git reset [--soft|--hard] REVISION')."
|
|||
|
(interactive (list (magit-read-rev (format "%s head to"
|
|||
|
(if current-prefix-arg
|
|||
|
"Hard reset"
|
|||
|
"Reset"))
|
|||
|
(or (magit-guess-branch) "HEAD"))
|
|||
|
current-prefix-arg))
|
|||
|
(magit-run-git "reset" (if hard "--hard" "--soft") revision "--"))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-reset-head-hard (revision)
|
|||
|
"Switch 'HEAD' to REVISION, losing all changes.
|
|||
|
Uncomitted changes in both working tree and staging area are lost.
|
|||
|
\('git reset --hard REVISION')."
|
|||
|
(interactive (list (magit-read-rev (format "Hard reset head to")
|
|||
|
(or (magit-guess-branch) "HEAD"))))
|
|||
|
(magit-reset-head revision t))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-reset-working-tree (&optional arg)
|
|||
|
"Revert working tree and clear changes from staging area.
|
|||
|
\('git reset --hard HEAD').
|
|||
|
|
|||
|
With a prefix arg, also remove untracked files.
|
|||
|
With two prefix args, remove ignored files as well."
|
|||
|
(interactive "p")
|
|||
|
(let ((include-untracked (>= arg 4))
|
|||
|
(include-ignored (>= arg 16)))
|
|||
|
(when (yes-or-no-p (format "Discard all uncommitted changes%s%s? "
|
|||
|
(if include-untracked
|
|||
|
", untracked files"
|
|||
|
"")
|
|||
|
(if include-ignored
|
|||
|
", ignored files"
|
|||
|
"")))
|
|||
|
(magit-reset-head-hard "HEAD")
|
|||
|
(when include-untracked
|
|||
|
(magit-run-git "clean" "-fd" (if include-ignored "-x" ""))))))
|
|||
|
|
|||
|
;;;;; Rewriting
|
|||
|
|
|||
|
(defun magit-read-rewrite-info ()
|
|||
|
(when (file-exists-p (magit-git-dir "magit-rewrite-info"))
|
|||
|
(with-temp-buffer
|
|||
|
(insert-file-contents (magit-git-dir "magit-rewrite-info"))
|
|||
|
(goto-char (point-min))
|
|||
|
(read (current-buffer)))))
|
|||
|
|
|||
|
(defun magit-write-rewrite-info (info)
|
|||
|
(with-temp-file (magit-git-dir "magit-rewrite-info")
|
|||
|
(prin1 info (current-buffer))
|
|||
|
(princ "\n" (current-buffer))))
|
|||
|
|
|||
|
(defun magit-rewrite-set-commit-property (commit prop value)
|
|||
|
(let* ((info (magit-read-rewrite-info))
|
|||
|
(pending (cdr (assq 'pending info)))
|
|||
|
(p (assoc commit pending)))
|
|||
|
(when p
|
|||
|
(setf (cdr p) (plist-put (cdr p) prop value))
|
|||
|
(magit-write-rewrite-info info)
|
|||
|
(magit-refresh))
|
|||
|
t))
|
|||
|
|
|||
|
(add-hook 'magit-apply-hook 'magit-rewrite-apply)
|
|||
|
(put 'magit-rewrite-apply 'magit-section-action-context [commit pending])
|
|||
|
(defun magit-rewrite-apply (commit)
|
|||
|
(magit-apply-commit commit)
|
|||
|
(magit-rewrite-set-commit-property commit 'used t))
|
|||
|
|
|||
|
(add-hook 'magit-cherry-pick-hook 'magit-rewrite-pick)
|
|||
|
(put 'magit-rewrite-pick 'magit-section-action-context [commit pending])
|
|||
|
(defun magit-rewrite-pick (commit)
|
|||
|
(magit-cherry-pick-commit commit)
|
|||
|
(magit-rewrite-set-commit-property commit 'used t))
|
|||
|
|
|||
|
(add-hook 'magit-revert-hook 'magit-rewrite-revert)
|
|||
|
(put 'magit-rewrite-revert 'magit-section-action-context [commit pending])
|
|||
|
(defun magit-rewrite-revert (commit)
|
|||
|
(when (or (not magit-revert-item-confirm)
|
|||
|
(yes-or-no-p "Revert this commit? "))
|
|||
|
(magit-revert-commit commit)
|
|||
|
(magit-rewrite-set-commit-property commit 'used nil)))
|
|||
|
|
|||
|
(defun magit-rewrite-set-used ()
|
|||
|
(interactive)
|
|||
|
(magit-section-case (info)
|
|||
|
([commit pending]
|
|||
|
(magit-rewrite-set-commit-property info 'used t)
|
|||
|
(magit-refresh))))
|
|||
|
|
|||
|
(defun magit-rewrite-set-unused ()
|
|||
|
(interactive)
|
|||
|
(magit-section-case (info)
|
|||
|
([commit pending]
|
|||
|
(magit-rewrite-set-commit-property info 'used nil)
|
|||
|
(magit-refresh))))
|
|||
|
|
|||
|
(defun magit-rewrite-start (from &optional onto)
|
|||
|
(interactive (list (magit-read-rev-with-default "Rewrite from")))
|
|||
|
(when (magit-anything-modified-p)
|
|||
|
(user-error "You have uncommitted changes"))
|
|||
|
(or (not (magit-read-rewrite-info))
|
|||
|
(user-error "Rewrite in progress"))
|
|||
|
(let* ((orig (magit-rev-parse "HEAD"))
|
|||
|
(base (if (or (eq magit-rewrite-inclusive t)
|
|||
|
(and (eq magit-rewrite-inclusive 'ask)
|
|||
|
(y-or-n-p "Include selected revision in rewrite? ")))
|
|||
|
(or (car (magit-commit-parents from))
|
|||
|
(user-error "Can't rewrite a parentless commit"))
|
|||
|
from))
|
|||
|
(pending (magit-git-lines "rev-list" (concat base ".."))))
|
|||
|
(magit-write-rewrite-info `((orig ,orig)
|
|||
|
(pending ,@(mapcar #'list pending))))
|
|||
|
(magit-run-git "reset" "--hard" base "--")))
|
|||
|
|
|||
|
(defun magit-rewrite-stop (&optional noconfirm)
|
|||
|
(interactive)
|
|||
|
(let* ((info (magit-read-rewrite-info)))
|
|||
|
(or info
|
|||
|
(user-error "No rewrite in progress"))
|
|||
|
(when (or noconfirm
|
|||
|
(yes-or-no-p "Stop rewrite? "))
|
|||
|
(magit-write-rewrite-info nil)
|
|||
|
(magit-refresh))))
|
|||
|
|
|||
|
(defun magit-rewrite-abort ()
|
|||
|
(interactive)
|
|||
|
(let* ((info (magit-read-rewrite-info))
|
|||
|
(orig (cadr (assq 'orig info))))
|
|||
|
(or info
|
|||
|
(user-error "No rewrite in progress"))
|
|||
|
(when (magit-anything-modified-p)
|
|||
|
(user-error "You have uncommitted changes"))
|
|||
|
(when (yes-or-no-p "Abort rewrite? ")
|
|||
|
(magit-write-rewrite-info nil)
|
|||
|
(magit-run-git "reset" "--hard" orig "--"))))
|
|||
|
|
|||
|
(defun magit-rewrite-finish ()
|
|||
|
(interactive)
|
|||
|
(magit-rewrite-finish-step)
|
|||
|
(magit-refresh))
|
|||
|
|
|||
|
(defun magit-rewrite-finish-step ()
|
|||
|
(let ((info (magit-read-rewrite-info)))
|
|||
|
(or info
|
|||
|
(user-error "No rewrite in progress"))
|
|||
|
(let* ((pending (cdr (assq 'pending info)))
|
|||
|
(first-unused
|
|||
|
(let ((rpend (reverse pending)))
|
|||
|
(while (and rpend (plist-get (cdr (car rpend)) 'used))
|
|||
|
(setq rpend (cdr rpend)))
|
|||
|
(car rpend)))
|
|||
|
(commit (car first-unused)))
|
|||
|
(cond ((not first-unused)
|
|||
|
(magit-rewrite-stop t))
|
|||
|
((magit-git-success "cherry-pick" commit)
|
|||
|
(magit-rewrite-set-commit-property commit 'used t)
|
|||
|
(magit-rewrite-finish-step))
|
|||
|
(t
|
|||
|
(magit-refresh)
|
|||
|
(error "Could not apply %s" commit))))))
|
|||
|
|
|||
|
(defun magit-rewrite-diff-pending ()
|
|||
|
(interactive)
|
|||
|
(let* ((info (magit-read-rewrite-info))
|
|||
|
(orig (cadr (assq 'orig info))))
|
|||
|
(if orig
|
|||
|
(magit-diff orig nil "-R")
|
|||
|
(user-error "No rewrite in progress"))))
|
|||
|
|
|||
|
(defun magit-rewrite-diff-pending-transition ()
|
|||
|
(interactive)
|
|||
|
(message "Please remove magit-insert-pending-changes from your magit-status-sections-hook, or move to magit-rewrite-diff-pending"))
|
|||
|
|
|||
|
(define-obsolete-function-alias
|
|||
|
'magit-insert-pending-changes 'magit-rewrite-diff-pending-transition
|
|||
|
"382351845e"
|
|||
|
"https://github.com/magit/magit/commit/382351845eca2425713f640f9bb55756c9ddf723")
|
|||
|
|
|||
|
;;;;; Fetching
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-fetch (remote)
|
|||
|
"Fetch from REMOTE."
|
|||
|
(interactive (list (magit-read-remote "Fetch remote")))
|
|||
|
(magit-run-git-async "fetch" remote magit-custom-options))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-fetch-current ()
|
|||
|
"Run fetch for default remote.
|
|||
|
|
|||
|
If there is no default remote, ask for one."
|
|||
|
(interactive)
|
|||
|
(magit-fetch (or (magit-get-current-remote)
|
|||
|
(magit-read-remote "Fetch remote"))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-remote-update ()
|
|||
|
"Update all remotes."
|
|||
|
(interactive)
|
|||
|
(or (run-hook-with-args-until-success 'magit-remote-update-hook)
|
|||
|
(magit-run-git-async "remote" "update" magit-custom-options)))
|
|||
|
|
|||
|
;;;;; Pulling
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-pull ()
|
|||
|
"Run git pull.
|
|||
|
|
|||
|
If there is no default remote, the user is prompted for one and
|
|||
|
its values is saved with git config. If there is no default
|
|||
|
merge branch, the user is prompted for one and its values is
|
|||
|
saved with git config. With a prefix argument, the default
|
|||
|
remote is not used and the user is prompted for a remote. With
|
|||
|
two prefix arguments, the default merge branch is not used and
|
|||
|
the user is prompted for a merge branch. Values entered by the
|
|||
|
user because of prefix arguments are not saved with git config."
|
|||
|
(interactive)
|
|||
|
(or (run-hook-with-args-until-success 'magit-pull-hook)
|
|||
|
(let* ((branch (magit-get-current-branch))
|
|||
|
(branch-remote (magit-get-remote branch))
|
|||
|
(branch-merge (magit-get "branch" branch "merge"))
|
|||
|
(branch-merge-name (and branch-merge
|
|||
|
(save-match-data
|
|||
|
(string-match "^refs/heads/\\(.+\\)" branch-merge)
|
|||
|
(match-string 1 branch-merge))))
|
|||
|
(choose-remote (>= (prefix-numeric-value current-prefix-arg) 4))
|
|||
|
(choose-branch (>= (prefix-numeric-value current-prefix-arg) 16))
|
|||
|
(remote-needed (or choose-remote
|
|||
|
(not branch-remote)))
|
|||
|
(branch-needed (or choose-branch
|
|||
|
(not branch-merge-name)))
|
|||
|
(chosen-branch-remote
|
|||
|
(if remote-needed
|
|||
|
(magit-read-remote "Pull from remote" branch-remote)
|
|||
|
branch-remote))
|
|||
|
(chosen-branch-merge-name
|
|||
|
(if branch-needed
|
|||
|
(magit-read-remote-branch (format "Pull branch from remote %s"
|
|||
|
chosen-branch-remote)
|
|||
|
chosen-branch-remote)
|
|||
|
branch-merge-name)))
|
|||
|
(when (and (not branch-remote)
|
|||
|
(not choose-remote))
|
|||
|
(magit-set chosen-branch-remote "branch" branch "remote"))
|
|||
|
(when (and (not branch-merge-name)
|
|||
|
(not choose-branch))
|
|||
|
(magit-set (format "%s" chosen-branch-merge-name)
|
|||
|
"branch" branch "merge"))
|
|||
|
(magit-run-git-async
|
|||
|
"pull" magit-custom-options
|
|||
|
(and choose-remote chosen-branch-remote)
|
|||
|
(and (or choose-remote choose-branch)
|
|||
|
(list (format "refs/heads/%s:refs/remotes/%s/%s"
|
|||
|
chosen-branch-merge-name
|
|||
|
chosen-branch-remote
|
|||
|
chosen-branch-merge-name)))))))
|
|||
|
|
|||
|
;;;;; Pushing
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-push-tags ()
|
|||
|
"Push tags to a remote repository.
|
|||
|
|
|||
|
Push tags to the current branch's remote. If that isn't set push
|
|||
|
to \"origin\" or if that remote doesn't exit but only a single
|
|||
|
remote is defined use that. Otherwise or with a prefix argument
|
|||
|
ask the user what remote to use."
|
|||
|
(interactive)
|
|||
|
(let* ((branch (magit-get-current-branch))
|
|||
|
(remotes (magit-git-lines "remote"))
|
|||
|
(remote (or (and branch (magit-get-remote branch))
|
|||
|
(car (member "origin" remotes))
|
|||
|
(and (= (length remotes) 1)
|
|||
|
(car remotes)))))
|
|||
|
(when (or current-prefix-arg (not remote))
|
|||
|
(setq remote (magit-read-remote "Push to remote")))
|
|||
|
(magit-run-git-async "push" remote "--tags" magit-custom-options)))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-push ()
|
|||
|
"Push the current branch to a remote repository.
|
|||
|
|
|||
|
This command runs the `magit-push-remote' hook. By default that
|
|||
|
means running `magit-push-dwim'. So unless you have customized
|
|||
|
the hook this command behaves like this:
|
|||
|
|
|||
|
With a single prefix argument ask the user what branch to push
|
|||
|
to. With two or more prefix arguments also ask the user what
|
|||
|
remote to push to. Otherwise use the remote and branch as
|
|||
|
configured using the Git variables `branch.<name>.remote' and
|
|||
|
`branch.<name>.merge'. If the former is undefined ask the user.
|
|||
|
If the latter is undefined push without specifing the remote
|
|||
|
branch explicitly.
|
|||
|
|
|||
|
Also see option `magit-set-upstream-on-push'."
|
|||
|
(interactive)
|
|||
|
(run-hook-with-args-until-success 'magit-push-hook current-prefix-arg))
|
|||
|
|
|||
|
(defun magit-push-dwim (arg)
|
|||
|
"Push the current branch to a remote repository.
|
|||
|
|
|||
|
With a single prefix argument ask the user what remote to push
|
|||
|
to. With two or more prefix arguments also ask the user the
|
|||
|
name of the remote branch to push to.
|
|||
|
|
|||
|
Otherwise use the remote and branch as configured using the
|
|||
|
Git variables `branch.<name>.remote' and `branch.<name>.merge'.
|
|||
|
If the former is undefined ask the user. If the latter is
|
|||
|
undefined push without specifing the remote branch explicitly.
|
|||
|
|
|||
|
Also see option `magit-set-upstream-on-push'."
|
|||
|
(interactive "P")
|
|||
|
(let* ((branch (or (magit-get-current-branch)
|
|||
|
(user-error "Don't push a detached head. That's gross")))
|
|||
|
(auto-remote (magit-get-remote branch))
|
|||
|
(used-remote (if (or arg (not auto-remote))
|
|||
|
(magit-read-remote
|
|||
|
(format "Push %s to remote" branch) auto-remote)
|
|||
|
auto-remote))
|
|||
|
(auto-branch (and (equal used-remote auto-remote)
|
|||
|
(magit-get "branch" branch "merge")))
|
|||
|
(used-branch (if (>= (prefix-numeric-value arg) 16)
|
|||
|
(magit-read-remote-branch
|
|||
|
(format "Push %s as branch" branch)
|
|||
|
used-remote auto-branch)
|
|||
|
auto-branch)))
|
|||
|
(cond ;; Pushing to what's already configured.
|
|||
|
((and auto-branch
|
|||
|
(equal auto-branch used-branch)
|
|||
|
(equal auto-remote used-remote)))
|
|||
|
;; Setting upstream because of magit-custom-options.
|
|||
|
((member "-u" magit-custom-options))
|
|||
|
;; Two prefix arguments; ignore magit-set-upstream-on-push.
|
|||
|
((>= (prefix-numeric-value arg) 16)
|
|||
|
(and (yes-or-no-p "Set upstream while pushing? ")
|
|||
|
(setq magit-custom-options
|
|||
|
(cons "-u" magit-custom-options))))
|
|||
|
;; Else honor magit-set-upstream-on-push.
|
|||
|
((eq magit-set-upstream-on-push 'refuse)
|
|||
|
(user-error "Not pushing since no upstream has been set."))
|
|||
|
((or (eq magit-set-upstream-on-push 'dontask)
|
|||
|
(and (eq magit-set-upstream-on-push t)
|
|||
|
(yes-or-no-p "Set upstream while pushing? ")))
|
|||
|
(setq magit-custom-options (cons "-u" magit-custom-options))))
|
|||
|
(magit-run-git-async
|
|||
|
"push" "-v" used-remote
|
|||
|
(if used-branch (format "%s:%s" branch used-branch) branch)
|
|||
|
magit-custom-options)))
|
|||
|
|
|||
|
;;;;; Committing
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-commit (&optional amendp)
|
|||
|
"Create a new commit on HEAD.
|
|||
|
With a prefix argument amend to the commit at HEAD instead.
|
|||
|
\('git commit [--amend]')."
|
|||
|
(interactive "P")
|
|||
|
(let ((args magit-custom-options))
|
|||
|
(when amendp
|
|||
|
(setq args (cons "--amend" args)))
|
|||
|
(when (setq args (magit-commit-assert args))
|
|||
|
(magit-commit-maybe-expand)
|
|||
|
(magit-commit-internal "commit" args))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-commit-amend ()
|
|||
|
"Amend the last commit.
|
|||
|
\('git commit --amend')."
|
|||
|
(interactive)
|
|||
|
(magit-commit-maybe-expand)
|
|||
|
(magit-commit-internal "commit" (cons "--amend" magit-custom-options)))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-commit-extend (&optional override-date)
|
|||
|
"Amend the last commit, without editing the message.
|
|||
|
With a prefix argument do change the committer date, otherwise
|
|||
|
don't. The option `magit-commit-extend-override-date' can be
|
|||
|
used to inverse the meaning of the prefix argument.
|
|||
|
\('git commit --no-edit --amend [--keep-date]')."
|
|||
|
(interactive (list (if current-prefix-arg
|
|||
|
(not magit-commit-reword-override-date)
|
|||
|
magit-commit-reword-override-date)))
|
|||
|
(magit-commit-maybe-expand)
|
|||
|
(let ((process-environment process-environment))
|
|||
|
(unless override-date
|
|||
|
(setenv "GIT_COMMITTER_DATE"
|
|||
|
(magit-git-string "log" "-1" "--format:format=%cd")))
|
|||
|
(magit-commit-internal "commit" (nconc (list "--no-edit" "--amend")
|
|||
|
magit-custom-options))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-commit-reword (&optional override-date)
|
|||
|
"Reword the last commit, ignoring staged changes.
|
|||
|
|
|||
|
With a prefix argument do change the committer date, otherwise
|
|||
|
don't. The option `magit-commit-rewrite-override-date' can be
|
|||
|
used to inverse the meaning of the prefix argument.
|
|||
|
|
|||
|
Non-interactively respect the optional OVERRIDE-DATE argument
|
|||
|
and ignore the option.
|
|||
|
|
|||
|
\('git commit --only --amend')."
|
|||
|
(interactive (list (if current-prefix-arg
|
|||
|
(not magit-commit-reword-override-date)
|
|||
|
magit-commit-reword-override-date)))
|
|||
|
(let ((process-environment process-environment))
|
|||
|
(unless override-date
|
|||
|
(setenv "GIT_COMMITTER_DATE"
|
|||
|
(magit-git-string "log" "-1" "--format:format=%cd")))
|
|||
|
(magit-commit-internal "commit" (nconc (list "--only" "--amend")
|
|||
|
magit-custom-options))))
|
|||
|
|
|||
|
(defvar-local magit-commit-squash-args nil)
|
|||
|
(defvar-local magit-commit-squash-fixup nil)
|
|||
|
|
|||
|
(defvar magit-commit-unmark-after-squash t)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-commit-fixup (&optional commit)
|
|||
|
"Create a fixup commit.
|
|||
|
With a prefix argument the user is always queried for the commit
|
|||
|
to be fixed. Otherwise the current or marked commit may be used
|
|||
|
depending on the value of option `magit-commit-squash-commit'.
|
|||
|
\('git commit [--no-edit] --fixup=COMMIT')."
|
|||
|
(interactive (list (magit-commit-squash-commit)))
|
|||
|
(magit-commit-squash commit t))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-commit-squash (&optional commit fixup)
|
|||
|
"Create a squash commit.
|
|||
|
With a prefix argument the user is always queried for the commit
|
|||
|
to be fixed. Otherwise the current or marked commit may be used
|
|||
|
depending on the value of option `magit-commit-squash-commit'.
|
|||
|
\('git commit [--no-edit] --fixup=COMMIT')."
|
|||
|
(interactive (list (magit-commit-squash-commit)))
|
|||
|
(let ((args magit-custom-options))
|
|||
|
(cond
|
|||
|
((not commit)
|
|||
|
(magit-commit-assert args)
|
|||
|
(magit-log)
|
|||
|
(setq magit-commit-squash-args args
|
|||
|
magit-commit-squash-fixup fixup)
|
|||
|
(add-hook 'magit-mark-commit-hook 'magit-commit-squash-marked t t)
|
|||
|
(add-hook 'magit-mode-quit-window-hook 'magit-commit-squash-abort t t)
|
|||
|
(message "Select commit using \".\", or abort using \"q\""))
|
|||
|
((setq args (magit-commit-assert args))
|
|||
|
(when (eq args t) (setq args nil))
|
|||
|
(magit-commit-internal
|
|||
|
"commit"
|
|||
|
(nconc (list "--no-edit"
|
|||
|
(concat (if fixup "--fixup=" "--squash=") commit))
|
|||
|
args))))))
|
|||
|
|
|||
|
(defun magit-commit-squash-commit ()
|
|||
|
(unless (or current-prefix-arg
|
|||
|
(eq magit-commit-squash-commit nil))
|
|||
|
(let ((current (magit-section-case (info) (commit info))))
|
|||
|
(cl-ecase magit-commit-squash-commit
|
|||
|
(current-or-marked (or current magit-marked-commit))
|
|||
|
(marked-or-current (or magit-marked-commit current))
|
|||
|
(current current)
|
|||
|
(marked magit-marked-commit)))))
|
|||
|
|
|||
|
(defun magit-commit-squash-marked ()
|
|||
|
(when magit-marked-commit
|
|||
|
(magit-commit-squash magit-marked-commit magit-commit-squash-fixup))
|
|||
|
(when magit-commit-unmark-after-squash
|
|||
|
(setq magit-marked-commit nil))
|
|||
|
(kill-local-variable 'magit-commit-squash-fixup)
|
|||
|
(remove-hook 'magit-mark-commit-hook 'magit-commit-squash-marked t)
|
|||
|
(remove-hook 'magit-mode-quit-window-hook 'magit-commit-squash-abort t)
|
|||
|
(magit-mode-quit-window))
|
|||
|
|
|||
|
(defun magit-commit-squash-abort (buffer)
|
|||
|
(when (buffer-live-p buffer)
|
|||
|
(with-current-buffer buffer
|
|||
|
(remove-hook 'magit-mark-commit-hook 'magit-commit-squash-marked t)
|
|||
|
(remove-hook 'magit-mode-quit-window-hook 'magit-commit-squash-abort t))))
|
|||
|
|
|||
|
(defun magit-commit-assert (args)
|
|||
|
(cond
|
|||
|
((or (magit-anything-staged-p)
|
|||
|
(member "--allow-empty" args)
|
|||
|
(member "--all" args)
|
|||
|
(member "--amend" args))
|
|||
|
(or args (list "--")))
|
|||
|
((and (magit-rebase-info)
|
|||
|
(y-or-n-p "Nothing staged. Continue in-progress rebase? "))
|
|||
|
(magit-run-git-async "rebase" "--continue")
|
|||
|
nil)
|
|||
|
(magit-commit-ask-to-stage
|
|||
|
(magit-commit-maybe-expand t)
|
|||
|
(when (y-or-n-p "Nothing staged. Stage and commit everything? ")
|
|||
|
(magit-run-git "add" "-u" ".")
|
|||
|
(or args (list "--"))))
|
|||
|
(t
|
|||
|
(user-error "Nothing staged. Set --allow-empty, --all, or --amend in popup"))))
|
|||
|
|
|||
|
(defun magit-commit-maybe-expand (&optional unstaged)
|
|||
|
(when (and magit-expand-staged-on-commit
|
|||
|
(derived-mode-p 'magit-status-mode))
|
|||
|
(if unstaged
|
|||
|
(magit-jump-to-unstaged t)
|
|||
|
(magit-jump-to-staged t))))
|
|||
|
|
|||
|
(defun magit-commit-internal (subcmd args)
|
|||
|
(setq git-commit-previous-winconf (current-window-configuration))
|
|||
|
(if (magit-use-emacsclient-p)
|
|||
|
(magit-with-emacsclient magit-server-window-for-commit
|
|||
|
(magit-run-git-async subcmd args))
|
|||
|
(let ((topdir (magit-get-top-dir))
|
|||
|
(editmsg (magit-git-dir (if (equal subcmd "tag")
|
|||
|
"TAG_EDITMSG"
|
|||
|
"COMMIT_EDITMSG"))))
|
|||
|
(when (and (member "--amend" args)
|
|||
|
(not (file-exists-p editmsg)))
|
|||
|
(with-temp-file editmsg
|
|||
|
(magit-git-insert "log" "-1" "--format=format:%B" "HEAD")))
|
|||
|
(with-current-buffer (find-file-noselect editmsg)
|
|||
|
(funcall (if (functionp magit-server-window-for-commit)
|
|||
|
magit-server-window-for-commit
|
|||
|
'switch-to-buffer)
|
|||
|
(current-buffer))
|
|||
|
(add-hook 'git-commit-commit-hook
|
|||
|
(apply-partially
|
|||
|
(lambda (default-directory editmsg args)
|
|||
|
(magit-run-git args)
|
|||
|
(ignore-errors (delete-file editmsg)))
|
|||
|
topdir editmsg
|
|||
|
`(,subcmd
|
|||
|
,"--cleanup=strip"
|
|||
|
,(concat "--file=" (file-relative-name
|
|||
|
(buffer-file-name)
|
|||
|
topdir))
|
|||
|
,@args))
|
|||
|
nil t)))))
|
|||
|
|
|||
|
(defun magit-commit-add-log ()
|
|||
|
"Add a template for the current hunk to the commit message buffer."
|
|||
|
(interactive)
|
|||
|
(let* ((section (magit-current-section))
|
|||
|
(fun (if (eq (magit-section-type section) 'hunk)
|
|||
|
(save-window-excursion
|
|||
|
(save-excursion
|
|||
|
(magit-visit-item)
|
|||
|
(add-log-current-defun)))
|
|||
|
nil))
|
|||
|
(file (magit-section-info
|
|||
|
(cl-case (magit-section-type section)
|
|||
|
(hunk (magit-section-parent section))
|
|||
|
(diff section)
|
|||
|
(t (user-error "No change at point")))))
|
|||
|
(locate-buffer (lambda ()
|
|||
|
(cl-find-if
|
|||
|
(lambda (buf)
|
|||
|
(with-current-buffer buf
|
|||
|
(derived-mode-p 'git-commit-mode)))
|
|||
|
(append (buffer-list (selected-frame))
|
|||
|
(buffer-list)))))
|
|||
|
(buffer (funcall locate-buffer)))
|
|||
|
(unless buffer
|
|||
|
(unless (magit-commit-assert nil)
|
|||
|
(user-error "Abort"))
|
|||
|
(magit-commit)
|
|||
|
(while (not (setq buffer (funcall locate-buffer)))
|
|||
|
(sit-for 0.01)))
|
|||
|
(pop-to-buffer buffer)
|
|||
|
(goto-char (point-min))
|
|||
|
(cond ((not (re-search-forward (format "^\\* %s" (regexp-quote file))
|
|||
|
nil t))
|
|||
|
;; No entry for file, create it.
|
|||
|
(goto-char (point-max))
|
|||
|
(forward-comment -1000)
|
|||
|
(unless (or (bobp) (looking-back "\\(\\*[^\n]+\\|\n\\)"))
|
|||
|
(insert "\n"))
|
|||
|
(insert (format "\n* %s" file))
|
|||
|
(when fun
|
|||
|
(insert (format " (%s)" fun)))
|
|||
|
(insert ": "))
|
|||
|
(fun
|
|||
|
;; found entry for file, look for fun
|
|||
|
(let ((limit (save-excursion
|
|||
|
(or (and (re-search-forward "^\\* " nil t)
|
|||
|
(match-beginning 0))
|
|||
|
(progn (goto-char (point-max))
|
|||
|
(forward-comment -1000)
|
|||
|
(point))))))
|
|||
|
(cond ((re-search-forward
|
|||
|
(format "(.*\\_<%s\\_>.*):" (regexp-quote fun))
|
|||
|
limit t)
|
|||
|
;; found it, goto end of current entry
|
|||
|
(if (re-search-forward "^(" limit t)
|
|||
|
(backward-char 2)
|
|||
|
(goto-char limit)))
|
|||
|
(t
|
|||
|
;; not found, insert new entry
|
|||
|
(goto-char limit)
|
|||
|
(if (bolp)
|
|||
|
(open-line 1)
|
|||
|
(newline))
|
|||
|
(insert (format "(%s): " fun))))))
|
|||
|
(t
|
|||
|
;; found entry for file, look for beginning it
|
|||
|
(when (looking-at ":")
|
|||
|
(forward-char 2))))))
|
|||
|
|
|||
|
;;;;; Tagging
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-tag (name rev &optional annotate)
|
|||
|
"Create a new tag with the given NAME at REV.
|
|||
|
With a prefix argument annotate the tag.
|
|||
|
\('git tag [--annotate] NAME REV')."
|
|||
|
(interactive (list (magit-read-tag "Tag name")
|
|||
|
(magit-read-rev "Place tag on"
|
|||
|
(or (magit-guess-branch) "HEAD"))
|
|||
|
current-prefix-arg))
|
|||
|
(let ((args (append magit-custom-options (list name rev))))
|
|||
|
(if (or (member "--sign" args)
|
|||
|
(member "--annotate" args)
|
|||
|
(and annotate (setq args (cons "--annotate" args))))
|
|||
|
(magit-commit-internal "tag" args)
|
|||
|
(magit-run-git "tag" args))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-delete-tag (name)
|
|||
|
"Delete the tag with the given NAME.
|
|||
|
\('git tag -d NAME')."
|
|||
|
(interactive (list (magit-read-tag "Delete Tag" t)))
|
|||
|
(magit-run-git "tag" "-d" magit-custom-options name))
|
|||
|
|
|||
|
;;;;; Stashing
|
|||
|
|
|||
|
(defvar magit-read-stash-history nil
|
|||
|
"The history of inputs to `magit-stash'.")
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-stash (description)
|
|||
|
"Create new stash of working tree and staging area named DESCRIPTION.
|
|||
|
Working tree and staging area revert to the current 'HEAD'.
|
|||
|
With prefix argument, changes in staging area are kept.
|
|||
|
\('git stash save [--keep-index] DESCRIPTION')"
|
|||
|
(interactive (list (read-string "Stash description: " nil
|
|||
|
'magit-read-stash-history)))
|
|||
|
(magit-run-git-async "stash" "save" magit-custom-options "--" description))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-stash-snapshot ()
|
|||
|
"Create new stash of working tree and staging area; keep changes in place.
|
|||
|
\('git stash save \"Snapshot...\"; git stash apply stash@{0}')"
|
|||
|
(interactive)
|
|||
|
(magit-call-git "stash" "save" magit-custom-options
|
|||
|
(format-time-string
|
|||
|
"Snapshot taken at %Y-%m-%d %H:%M:%S"
|
|||
|
(current-time)))
|
|||
|
(magit-run-git "stash" "apply" "stash@{0}"))
|
|||
|
|
|||
|
(defun magit-stash-apply (stash)
|
|||
|
"Apply a stash on top of the current working tree state.
|
|||
|
\('git stash apply stash@{N}')"
|
|||
|
(interactive (list (magit-read-stash "Apply stash (number): ")))
|
|||
|
(magit-run-git "stash" "apply" stash))
|
|||
|
|
|||
|
(defun magit-stash-pop (stash)
|
|||
|
"Apply a stash on top of working tree state and remove from stash list.
|
|||
|
\('git stash pop stash@{N}')"
|
|||
|
(interactive (list (magit-read-stash "Pop stash (number): ")))
|
|||
|
(magit-run-git "stash" "pop" stash))
|
|||
|
|
|||
|
(defun magit-stash-drop (stash)
|
|||
|
"Remove a stash from the stash list.
|
|||
|
\('git stash drop stash@{N}')"
|
|||
|
(interactive (list (magit-read-stash "Drop stash (number): ")))
|
|||
|
(magit-run-git "stash" "drop" stash))
|
|||
|
|
|||
|
;;;;; Cherry-Pick
|
|||
|
|
|||
|
(defun magit-cherry-pick-item ()
|
|||
|
"Cherry-pick them item at point."
|
|||
|
(interactive)
|
|||
|
(magit-section-action cherry-pick (info)
|
|||
|
(commit (magit-cherry-pick-commit info))
|
|||
|
(stash (magit-stash-pop info))))
|
|||
|
|
|||
|
(defun magit-cherry-pick-commit (commit)
|
|||
|
(magit-assert-one-parent commit "cherry-pick")
|
|||
|
(magit-run-git "cherry-pick" commit))
|
|||
|
|
|||
|
;;;;; Submoduling
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-submodule-update (&optional init)
|
|||
|
"Update the submodule of the current git repository.
|
|||
|
With a prefix arg, do a submodule update --init."
|
|||
|
(interactive "P")
|
|||
|
(let ((default-directory (magit-get-top-dir)))
|
|||
|
(magit-run-git-async "submodule" "update" (and init "--init"))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-submodule-update-init ()
|
|||
|
"Update and init the submodule of the current git repository."
|
|||
|
(interactive)
|
|||
|
(magit-submodule-update t))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-submodule-init ()
|
|||
|
"Initialize the submodules."
|
|||
|
(interactive)
|
|||
|
(let ((default-directory (magit-get-top-dir)))
|
|||
|
(magit-run-git-async "submodule" "init")))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-submodule-sync ()
|
|||
|
"Synchronizes submodule's remote URL configuration."
|
|||
|
(interactive)
|
|||
|
(let ((default-directory (magit-get-top-dir)))
|
|||
|
(magit-run-git-async "submodule" "sync")))
|
|||
|
|
|||
|
;;;;; Bisecting
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-bisect-start (bad good)
|
|||
|
"Start a bisect session.
|
|||
|
|
|||
|
Bisecting a bug means to find the commit that introduced it.
|
|||
|
This command starts such a bisect session by asking for a know
|
|||
|
good and a bad commit. To move the session forward use the
|
|||
|
other actions from the bisect popup (\
|
|||
|
\\<magit-status-mode-map>\\[magit-key-mode-popup-bisecting])."
|
|||
|
(interactive
|
|||
|
(if (magit-bisecting-p)
|
|||
|
(user-error "Already bisecting")
|
|||
|
(list (magit-read-rev "Start bisect with known bad revision" "HEAD")
|
|||
|
(magit-read-rev "Good revision" (magit-guess-branch)))))
|
|||
|
(magit-run-git-bisect "start" (list bad good) t))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-bisect-reset ()
|
|||
|
"After bisecting cleanup bisection state and return to original HEAD."
|
|||
|
(interactive)
|
|||
|
(when (yes-or-no-p "Reset bisect?")
|
|||
|
(magit-run-git "bisect" "reset")
|
|||
|
(ignore-errors (delete-file (magit-git-dir "BISECT_CMD_OUTPUT")))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-bisect-good ()
|
|||
|
"While bisecting, mark the current commit as good.
|
|||
|
Use this after you have asserted that the commit does not contain
|
|||
|
the bug in question."
|
|||
|
(interactive)
|
|||
|
(magit-run-git-bisect "good"))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-bisect-bad ()
|
|||
|
"While bisecting, mark the current commit as bad.
|
|||
|
Use this after you have asserted that the commit does contain the
|
|||
|
bug in question."
|
|||
|
(interactive)
|
|||
|
(magit-run-git-bisect "bad"))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-bisect-skip ()
|
|||
|
"While bisecting, skip the current commit.
|
|||
|
Use this if for some reason the current commit is not a good one
|
|||
|
to test. This command lets Git choose a different one."
|
|||
|
(interactive)
|
|||
|
(magit-run-git-bisect "skip"))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-bisect-run (cmdline)
|
|||
|
"Bisect automatically by running commands after each step."
|
|||
|
(interactive (list (read-shell-command "Bisect shell command: ")))
|
|||
|
(magit-run-git-bisect "run" (list cmdline)))
|
|||
|
|
|||
|
(defun magit-run-git-bisect (subcommand &optional args no-assert)
|
|||
|
(unless (or no-assert (magit-bisecting-p))
|
|||
|
(user-error "Not bisecting"))
|
|||
|
(let ((file (magit-git-dir "BISECT_CMD_OUTPUT"))
|
|||
|
(default-directory (magit-get-top-dir)))
|
|||
|
(ignore-errors (delete-file file))
|
|||
|
(magit-run-git-with-logfile file "bisect" subcommand args)
|
|||
|
(magit-process-wait)
|
|||
|
(magit-refresh)))
|
|||
|
|
|||
|
;;;;; Logging
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-log (&optional range)
|
|||
|
(interactive)
|
|||
|
(magit-mode-setup magit-log-buffer-name nil
|
|||
|
#'magit-log-mode
|
|||
|
#'magit-refresh-log-buffer
|
|||
|
'oneline (or range "HEAD") magit-custom-options))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-log-ranged (range)
|
|||
|
(interactive (list (magit-read-rev-range "Log" "HEAD")))
|
|||
|
(magit-log range))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-log-long (&optional range)
|
|||
|
(interactive)
|
|||
|
(magit-mode-setup magit-log-buffer-name nil
|
|||
|
#'magit-log-mode
|
|||
|
#'magit-refresh-log-buffer
|
|||
|
'long (or range "HEAD") magit-custom-options))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-log-long-ranged (range)
|
|||
|
(interactive (list (magit-read-rev-range "Long Log" "HEAD")))
|
|||
|
(magit-log-long range))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-file-log (file &optional use-graph)
|
|||
|
"Display the log for the currently visited file or another one.
|
|||
|
With a prefix argument show the log graph."
|
|||
|
(interactive
|
|||
|
(list (magit-read-file-from-rev (magit-get-current-branch)
|
|||
|
(magit-buffer-file-name t))
|
|||
|
current-prefix-arg))
|
|||
|
(magit-mode-setup magit-log-buffer-name nil
|
|||
|
#'magit-log-mode
|
|||
|
#'magit-refresh-log-buffer
|
|||
|
'oneline "HEAD"
|
|||
|
`(,@(and use-graph (list "--graph"))
|
|||
|
,@magit-custom-options
|
|||
|
"--follow")
|
|||
|
file))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-reflog (ref)
|
|||
|
"Display the reflog of the current branch.
|
|||
|
With a prefix argument another branch can be chosen."
|
|||
|
(interactive (let ((branch (magit-get-current-branch)))
|
|||
|
(if (and branch (not current-prefix-arg))
|
|||
|
(list branch)
|
|||
|
(list (magit-read-rev "Reflog of" branch)))))
|
|||
|
(magit-mode-setup magit-reflog-buffer-name nil
|
|||
|
#'magit-reflog-mode
|
|||
|
#'magit-refresh-reflog-buffer ref))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-reflog-head ()
|
|||
|
"Display the HEAD reflog."
|
|||
|
(interactive)
|
|||
|
(magit-reflog "HEAD"))
|
|||
|
|
|||
|
;;; Modes (2)
|
|||
|
;;;; Log Mode
|
|||
|
;;;;; Log Core
|
|||
|
|
|||
|
(define-derived-mode magit-log-mode magit-mode "Magit Log"
|
|||
|
"Mode for looking at git log.
|
|||
|
|
|||
|
\\<magit-log-mode-map>Type `\\[magit-visit-item]` to visit a commit, and \
|
|||
|
`\\[magit-show-item-or-scroll-up]` to just show it.
|
|||
|
Type `\\[magit-log-show-more-entries]` to show more commits, \
|
|||
|
and `\\[magit-refresh]` to refresh the log.
|
|||
|
Type `\\[magit-diff-working-tree]` to see the diff between current commit and your working tree,
|
|||
|
Type `\\[magit-diff]` to see diff between any two version
|
|||
|
Type `\\[magit-apply-item]` to apply the change of the current commit to your wortree,
|
|||
|
and `\\[magit-cherry-pick-item]` to apply and commit the result.
|
|||
|
Type `\\[magit-revert-item]` to revert a commit, and `\\[magit-reset-head]` reset your current head to a commit,
|
|||
|
|
|||
|
More information can be found in Info node `(magit)History'
|
|||
|
|
|||
|
Other key binding:
|
|||
|
\\{magit-log-mode-map}"
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defvar magit-log-buffer-name "*magit-log*"
|
|||
|
"Name of buffer used to display log entries.")
|
|||
|
|
|||
|
(defun magit-refresh-log-buffer (style range args &optional file)
|
|||
|
(magit-set-buffer-margin (car magit-log-margin-spec)
|
|||
|
(and magit-log-show-margin
|
|||
|
(eq (car magit-refresh-args) 'oneline)))
|
|||
|
(magit-log-margin-set-timeunit-width)
|
|||
|
(setq magit-file-log-file file)
|
|||
|
(when (consp range)
|
|||
|
(setq range (concat (car range) ".." (cdr range))))
|
|||
|
(magit-git-insert-section
|
|||
|
(logbuf (concat "Commits"
|
|||
|
(and file (concat " for file " file))
|
|||
|
(and range (concat " in " range))))
|
|||
|
(apply-partially 'magit-wash-log style 'color t)
|
|||
|
"log"
|
|||
|
(format "--max-count=%d" magit-log-cutoff-length)
|
|||
|
"--decorate=full" "--color"
|
|||
|
(cl-case style
|
|||
|
(long (if magit-log-show-gpg-status
|
|||
|
(list "--stat" "--show-signature")
|
|||
|
"--stat"))
|
|||
|
(oneline (concat "--pretty=format:%h%d "
|
|||
|
(and magit-log-show-gpg-status "%G?")
|
|||
|
"[%an][%at]%s")))
|
|||
|
args range "--" file)
|
|||
|
(save-excursion
|
|||
|
(goto-char (point-min))
|
|||
|
(magit-format-log-margin)))
|
|||
|
|
|||
|
;;;;; Log Washing
|
|||
|
|
|||
|
(defconst magit-log-oneline-re
|
|||
|
(concat "^"
|
|||
|
"\\(?4:\\(?: *[-_/|\\*o.] *\\)+ *\\)?" ; graph
|
|||
|
"\\(?:"
|
|||
|
"\\(?1:[0-9a-fA-F]+\\) " ; sha1
|
|||
|
"\\(?:\\(?3:([^()]+)\\) \\)?" ; refs
|
|||
|
"\\(?7:[BGUN]\\)?" ; gpg
|
|||
|
"\\[\\(?5:[^]]*\\)\\]" ; author
|
|||
|
"\\[\\(?6:[^]]*\\)\\]" ; date
|
|||
|
"\\(?2:.*\\)" ; msg
|
|||
|
"\\)?$"))
|
|||
|
|
|||
|
(defconst magit-log-long-re
|
|||
|
(concat "^"
|
|||
|
"\\(?4:\\(?:[-_/|\\*o.] *\\)+ *\\)?" ; graph
|
|||
|
"\\(?:"
|
|||
|
"\\(?:commit \\(?1:[0-9a-fA-F]+\\)" ; sha1
|
|||
|
"\\(?: \\(?3:([^()]+)\\)\\)?\\)" ; refs
|
|||
|
"\\|"
|
|||
|
"\\(?2:.+\\)\\)$")) ; "msg"
|
|||
|
|
|||
|
(defconst magit-log-unique-re
|
|||
|
(concat "^"
|
|||
|
"\\(?1:[0-9a-fA-F]+\\) " ; sha1
|
|||
|
"\\(?2:.*\\)$")) ; msg
|
|||
|
|
|||
|
(defconst magit-log-cherry-re
|
|||
|
(concat "^"
|
|||
|
"\\(?8:[-+]\\) " ; cherry
|
|||
|
"\\(?1:[0-9a-fA-F]+\\) " ; sha1
|
|||
|
"\\(?2:.*\\)$")) ; msg
|
|||
|
|
|||
|
(defconst magit-log-bisect-vis-re
|
|||
|
(concat "^"
|
|||
|
"\\(?1:[0-9a-fA-F]+\\) " ; sha1
|
|||
|
"\\(?:\\(?3:([^()]+)\\) \\)?" ; refs
|
|||
|
"\\(?2:.+\\)$")) ; msg
|
|||
|
|
|||
|
(defconst magit-log-bisect-log-re
|
|||
|
(concat "^# "
|
|||
|
"\\(?3:bad:\\|skip:\\|good:\\) " ; "refs"
|
|||
|
"\\[\\(?1:[^]]+\\)\\] " ; sha1
|
|||
|
"\\(?2:.+\\)$")) ; msg
|
|||
|
|
|||
|
(defconst magit-log-reflog-re
|
|||
|
(concat "^"
|
|||
|
"\\(?1:[^ ]+\\) " ; sha1
|
|||
|
"\\[\\(?5:[^]]*\\)\\] " ; author
|
|||
|
"\\(?6:[^ ]*\\) " ; date
|
|||
|
"[^@]+@{\\(?9:[^}]+\\)} " ; refsel
|
|||
|
"\\(?10:merge\\|[^:]+\\)?:? ?" ; refsub
|
|||
|
"\\(?2:.+\\)?$")) ; msg
|
|||
|
|
|||
|
(defconst magit-reflog-subject-re
|
|||
|
(concat "\\([^ ]+\\) ?" ; command (1)
|
|||
|
"\\(\\(?: ?-[^ ]+\\)+\\)?" ; option (2)
|
|||
|
"\\(?: ?(\\([^)]+\\))\\)?")) ; type (3)
|
|||
|
|
|||
|
(defvar magit-log-count nil)
|
|||
|
|
|||
|
(defun magit-wash-log (style &optional color longer)
|
|||
|
(when color
|
|||
|
(let ((ansi-color-apply-face-function
|
|||
|
(lambda (beg end face)
|
|||
|
(when face
|
|||
|
(put-text-property beg end 'font-lock-face face)))))
|
|||
|
(ansi-color-apply-on-region (point-min) (point-max))))
|
|||
|
(when (eq style 'cherry)
|
|||
|
(reverse-region (point-min) (point-max)))
|
|||
|
(let ((magit-log-count 0))
|
|||
|
(magit-wash-sequence (apply-partially 'magit-wash-log-line style
|
|||
|
(magit-abbrev-length)))
|
|||
|
(when (and longer
|
|||
|
(= magit-log-count magit-log-cutoff-length))
|
|||
|
(magit-with-section (section longer 'longer)
|
|||
|
(insert-text-button "type \"e\" to show more history"
|
|||
|
'action (lambda (button)
|
|||
|
(magit-log-show-more-entries))
|
|||
|
'follow-link t
|
|||
|
'mouse-face magit-item-highlight-face)))))
|
|||
|
|
|||
|
(defun magit-wash-log-line (style abbrev)
|
|||
|
(looking-at (cl-ecase style
|
|||
|
(oneline magit-log-oneline-re)
|
|||
|
(long magit-log-long-re)
|
|||
|
(unique magit-log-unique-re)
|
|||
|
(cherry magit-log-cherry-re)
|
|||
|
(reflog magit-log-reflog-re)
|
|||
|
(bisect-vis magit-log-bisect-vis-re)
|
|||
|
(bisect-log magit-log-bisect-log-re)))
|
|||
|
(magit-bind-match-strings
|
|||
|
(hash msg refs graph author date gpg cherry refsel refsub)
|
|||
|
(delete-region (point) (point-at-eol))
|
|||
|
(when cherry
|
|||
|
(magit-insert cherry (if (string= cherry "-")
|
|||
|
'magit-cherry-equivalent
|
|||
|
'magit-cherry-unmatched) ?\s))
|
|||
|
(unless (eq style 'long)
|
|||
|
(when (eq style 'bisect-log)
|
|||
|
(setq hash (magit-git-string "rev-parse" "--short" hash)))
|
|||
|
(if hash
|
|||
|
(insert (propertize hash 'face 'magit-log-sha1) ?\s)
|
|||
|
(insert (make-string (1+ abbrev) ? ))))
|
|||
|
(when graph
|
|||
|
(if magit-log-format-graph-function
|
|||
|
(insert (funcall magit-log-format-graph-function graph))
|
|||
|
(insert graph)))
|
|||
|
(when (and hash (eq style 'long))
|
|||
|
(magit-insert (if refs hash (magit-rev-parse hash)) 'magit-log-sha1 ?\s))
|
|||
|
(when refs
|
|||
|
(magit-insert-ref-labels refs))
|
|||
|
(when refsub
|
|||
|
(insert (format "%-2s " refsel))
|
|||
|
(insert (magit-log-format-reflog refsub)))
|
|||
|
(when msg
|
|||
|
(magit-insert msg (cl-case (and gpg (aref gpg 0))
|
|||
|
(?G 'magit-signature-good)
|
|||
|
(?B 'magit-signature-bad)
|
|||
|
(?U 'magit-signature-untrusted)
|
|||
|
(?N 'magit-signature-none)
|
|||
|
(t 'magit-log-message))))
|
|||
|
(goto-char (line-beginning-position))
|
|||
|
(magit-format-log-margin author date)
|
|||
|
(if hash
|
|||
|
(magit-with-section (section commit hash)
|
|||
|
(when (derived-mode-p 'magit-log-mode)
|
|||
|
(cl-incf magit-log-count))
|
|||
|
(forward-line)
|
|||
|
(when (eq style 'long)
|
|||
|
(magit-wash-sequence
|
|||
|
(lambda ()
|
|||
|
(looking-at magit-log-long-re)
|
|||
|
(when (match-string 2)
|
|||
|
(magit-wash-log-line 'long abbrev))))))
|
|||
|
(forward-line)))
|
|||
|
t)
|
|||
|
|
|||
|
(defun magit-log-format-unicode-graph (string)
|
|||
|
"Translate ascii characters to unicode characters.
|
|||
|
Whether that actually is an improvment depends on the unicode
|
|||
|
support of the font in use. The translation is done using the
|
|||
|
alist in `magit-log-format-unicode-graph-alist'."
|
|||
|
(replace-regexp-in-string
|
|||
|
"[/|\\*o ]"
|
|||
|
(lambda (str)
|
|||
|
(propertize
|
|||
|
(string (or (cdr (assq (aref str 0)
|
|||
|
magit-log-format-unicode-graph-alist))
|
|||
|
(aref str 0)))
|
|||
|
'face (get-text-property 0 'face str)))
|
|||
|
string))
|
|||
|
|
|||
|
(defun magit-format-log-margin (&optional author date)
|
|||
|
(when magit-log-show-margin
|
|||
|
(cl-destructuring-bind (width characterp duration-spec)
|
|||
|
magit-log-margin-spec
|
|||
|
(if author
|
|||
|
(magit-make-margin-overlay
|
|||
|
(propertize (truncate-string-to-width
|
|||
|
author (- width 1 3 (if characterp 0 1)
|
|||
|
magit-log-margin-timeunit-width 1)
|
|||
|
nil ?\s (make-string 1 magit-ellipsis))
|
|||
|
'face 'magit-log-author)
|
|||
|
" "
|
|||
|
(propertize (magit-format-duration
|
|||
|
(abs (truncate (- (float-time)
|
|||
|
(string-to-number date))))
|
|||
|
(symbol-value duration-spec)
|
|||
|
magit-log-margin-timeunit-width)
|
|||
|
'face 'magit-log-date)
|
|||
|
(propertize " " 'face 'fringe))
|
|||
|
(magit-make-margin-overlay
|
|||
|
(propertize (make-string (1- width) ?\s) 'face 'default)
|
|||
|
(propertize " " 'face 'fringe))))))
|
|||
|
|
|||
|
;;;;; Log Commands
|
|||
|
|
|||
|
(defun magit-log-toggle-margin ()
|
|||
|
"Show or hide the log margin.
|
|||
|
This command can only be used inside log buffers (usually
|
|||
|
*magit-log*) and only if that displays a `oneline' log.
|
|||
|
Also see option `magit-log-show-margin'."
|
|||
|
(interactive)
|
|||
|
(unless (derived-mode-p 'magit-log-mode)
|
|||
|
(user-error "The log margin cannot be used outside of log buffers"))
|
|||
|
(when (eq (car magit-refresh-args) 'long)
|
|||
|
(user-error "The log margin cannot be used with verbose logs"))
|
|||
|
(if magit-log-show-margin
|
|||
|
(magit-set-buffer-margin (car magit-log-margin-spec)
|
|||
|
(not (cdr (window-margins))))
|
|||
|
(setq-local magit-log-show-margin t)
|
|||
|
(magit-refresh)))
|
|||
|
|
|||
|
(defun magit-log-show-more-entries (&optional arg)
|
|||
|
"Grow the number of log entries shown.
|
|||
|
|
|||
|
With no prefix optional ARG, show twice as many log entries.
|
|||
|
With a numerical prefix ARG, add this number to the number of shown log entries.
|
|||
|
With a non numeric prefix ARG, show all entries"
|
|||
|
(interactive "P")
|
|||
|
(setq-local magit-log-cutoff-length
|
|||
|
(cond ((numberp arg) (+ magit-log-cutoff-length arg))
|
|||
|
(arg magit-log-infinite-length)
|
|||
|
(t (* magit-log-cutoff-length 2))))
|
|||
|
(let ((old-point (point)))
|
|||
|
(magit-refresh)
|
|||
|
(goto-char old-point)))
|
|||
|
|
|||
|
;;;; Cherry Mode
|
|||
|
|
|||
|
(define-derived-mode magit-cherry-mode magit-mode "Magit Cherry"
|
|||
|
"Mode for looking at commits not merged upstream.
|
|||
|
|
|||
|
\\<magit-cherry-mode-map>Type `\\[magit-toggle-section]` to show or hide \
|
|||
|
section, `\\[magit-visit-item]` to visit an item and \
|
|||
|
`\\[magit-show-item-or-scroll-up]` to show it.
|
|||
|
Type `\\[magit-diff-working-tree]` to display change with your working tree, \
|
|||
|
when `\\[magit-diff]` to display change
|
|||
|
between any two commit.
|
|||
|
Type `\\[magit-cherry-pick-item]` to cherry-pick a commit, and \
|
|||
|
`\\[magit-apply-item]` to apply its change to your
|
|||
|
working tree, without committing, and `\\[magit-key-mode-popup-merging]` to \
|
|||
|
merge.
|
|||
|
`\\[magit-refresh]` will refresh current buffer.
|
|||
|
|
|||
|
|
|||
|
Other key binding:
|
|||
|
\\{magit-cherry-mode-map}")
|
|||
|
|
|||
|
(defvar magit-cherry-buffer-name "*magit-cherry*"
|
|||
|
"Name of buffer used to display commits not merged upstream.")
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-cherry (head upstream)
|
|||
|
"Show commits in a branch that are not merged in the upstream branch."
|
|||
|
(interactive
|
|||
|
(let ((head (magit-read-rev "Cherry head" (magit-get-current-branch))))
|
|||
|
(list head (magit-read-rev "Cherry upstream"
|
|||
|
(magit-get-tracked-branch head nil t)))))
|
|||
|
(magit-mode-setup magit-cherry-buffer-name nil
|
|||
|
#'magit-cherry-mode
|
|||
|
#'magit-refresh-cherry-buffer upstream head))
|
|||
|
|
|||
|
(defun magit-refresh-cherry-buffer (upstream head)
|
|||
|
(magit-with-section (section cherry 'cherry nil t)
|
|||
|
(run-hooks 'magit-cherry-sections-hook)))
|
|||
|
|
|||
|
(defun magit-insert-cherry-head-line ()
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat "Head: "
|
|||
|
(propertize (cadr magit-refresh-args) 'face 'magit-branch) " "
|
|||
|
(abbreviate-file-name default-directory))))
|
|||
|
|
|||
|
(defun magit-insert-cherry-upstream-line ()
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat "Upstream: "
|
|||
|
(propertize (car magit-refresh-args) 'face 'magit-branch))))
|
|||
|
|
|||
|
(defun magit-insert-cherry-help-lines ()
|
|||
|
(when (derived-mode-p 'magit-cherry-mode)
|
|||
|
(insert "\n")
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat (propertize "-" 'face 'magit-cherry-equivalent)
|
|||
|
" equivalent exists in both refs"))
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat (propertize "+" 'face 'magit-cherry-unmatched)
|
|||
|
" unmatched commit tree"))))
|
|||
|
|
|||
|
(defun magit-insert-cherry-commits ()
|
|||
|
(magit-git-insert-section (cherries "Cherry commits:")
|
|||
|
(apply-partially 'magit-wash-log 'cherry)
|
|||
|
"cherry" "-v" (magit-abbrev-arg) magit-refresh-args))
|
|||
|
|
|||
|
;;;; Reflog Mode
|
|||
|
|
|||
|
(defvar magit-reflog-buffer-name "*magit-reflog*"
|
|||
|
"Name of buffer used to display reflog entries.")
|
|||
|
|
|||
|
(define-derived-mode magit-reflog-mode magit-log-mode "Magit Reflog"
|
|||
|
"Mode for looking at git reflog.
|
|||
|
|
|||
|
\\<magit-reflog-mode-map>Type `\\[magit-visit-item]` to visit a commit, and \
|
|||
|
`\\[magit-show-item-or-scroll-up]` to just show it.
|
|||
|
Type `\\[magit-diff-working-tree]` to see the diff between current commit and \
|
|||
|
your working tree,
|
|||
|
Type `\\[magit-diff]` to see the between any two version.
|
|||
|
Type `\\[magit-reset-head]` to reset your head to the current commit, and \
|
|||
|
`\\[magit-apply-item]` to apply its change
|
|||
|
to your working tree and `\\[magit-cherry-pick-item]` to cherry pick it.
|
|||
|
|
|||
|
More information can be found in Info node `(magit)Reflogs'
|
|||
|
|
|||
|
Other key binding:
|
|||
|
\\{magit-reflog-mode-map}"
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defun magit-refresh-reflog-buffer (ref)
|
|||
|
(magit-log-margin-set-timeunit-width)
|
|||
|
(magit-git-insert-section
|
|||
|
(reflogbuf (format "Local history of branch %s" ref))
|
|||
|
(apply-partially 'magit-wash-log 'reflog t)
|
|||
|
"reflog" "show" "--format=format:%h [%an] %ct %gd %gs"
|
|||
|
(format "--max-count=%d" magit-log-cutoff-length) ref))
|
|||
|
|
|||
|
(defvar magit-reflog-labels
|
|||
|
'(("commit" . magit-log-reflog-label-commit)
|
|||
|
("amend" . magit-log-reflog-label-amend)
|
|||
|
("merge" . magit-log-reflog-label-merge)
|
|||
|
("checkout" . magit-log-reflog-label-checkout)
|
|||
|
("branch" . magit-log-reflog-label-checkout)
|
|||
|
("reset" . magit-log-reflog-label-reset)
|
|||
|
("rebase" . magit-log-reflog-label-rebase)
|
|||
|
("cherry-pick" . magit-log-reflog-label-cherry-pick)
|
|||
|
("initial" . magit-log-reflog-label-commit)
|
|||
|
("pull" . magit-log-reflog-label-remote)
|
|||
|
("clone" . magit-log-reflog-label-remote)))
|
|||
|
|
|||
|
(defun magit-log-format-reflog (subject)
|
|||
|
(let* ((match (string-match magit-reflog-subject-re subject))
|
|||
|
(command (and match (match-string 1 subject)))
|
|||
|
(option (and match (match-string 2 subject)))
|
|||
|
(type (and match (match-string 3 subject)))
|
|||
|
(label (if (string= command "commit")
|
|||
|
(or type command)
|
|||
|
command))
|
|||
|
(text (if (string= command "commit")
|
|||
|
label
|
|||
|
(mapconcat #'identity
|
|||
|
(delq nil (list command option type))
|
|||
|
" "))))
|
|||
|
(format "%-16s "
|
|||
|
(propertize text 'face
|
|||
|
(or (cdr (assoc label magit-reflog-labels))
|
|||
|
'magit-log-reflog-label-other)))))
|
|||
|
|
|||
|
;;;; Ediff Support
|
|||
|
|
|||
|
(defvar magit-ediff-buffers nil
|
|||
|
"List of buffers that may be killed by `magit-ediff-restore'.")
|
|||
|
|
|||
|
(defvar magit-ediff-windows nil
|
|||
|
"The window configuration that will be restored when Ediff is finished.")
|
|||
|
|
|||
|
(defun magit-ediff ()
|
|||
|
"View the current DIFF section in ediff."
|
|||
|
(interactive)
|
|||
|
(let ((diff (magit-current-section)))
|
|||
|
(when (eq (magit-section-type (magit-current-section)) 'diffstat)
|
|||
|
(setq diff (magit-diff-section-for-diffstat diff)))
|
|||
|
(when (magit-section-hidden diff)
|
|||
|
;; Range is not set until the first time the diff is visible.
|
|||
|
;; This somewhat hackish code makes sure it's been visible at
|
|||
|
;; least once.
|
|||
|
(magit-toggle-section)
|
|||
|
(magit-toggle-section)
|
|||
|
(setq diff (magit-current-section)))
|
|||
|
(when (eq 'hunk (magit-section-type diff))
|
|||
|
(setq diff (magit-section-parent diff)))
|
|||
|
(unless (eq 'diff (magit-section-type diff))
|
|||
|
(user-error "No diff at this location"))
|
|||
|
(let* ((status (magit-section-diff-status diff))
|
|||
|
(file1 (magit-section-info diff))
|
|||
|
(file2 (magit-section-diff-file2 diff))
|
|||
|
(range (magit-diff-range diff)))
|
|||
|
(cond
|
|||
|
((memq status '(new deleted typechange))
|
|||
|
(message "Why ediff a %s file?" status))
|
|||
|
((and (eq status 'unmerged)
|
|||
|
(eq (cdr range) 'working))
|
|||
|
(magit-interactive-resolve file1))
|
|||
|
((consp (car range))
|
|||
|
(magit-ediff-buffers3 (magit-show (caar range) file2)
|
|||
|
(magit-show (cdar range) file2)
|
|||
|
(magit-show (cdr range) file1)))
|
|||
|
(t
|
|||
|
(magit-ediff-buffers (magit-show (car range) file2)
|
|||
|
(magit-show (cdr range) file1)))))))
|
|||
|
|
|||
|
(defun magit-ediff-buffers (a b)
|
|||
|
(setq magit-ediff-buffers (list a b))
|
|||
|
(setq magit-ediff-windows (current-window-configuration))
|
|||
|
(ediff-buffers a b '(magit-ediff-add-cleanup)))
|
|||
|
|
|||
|
(defun magit-ediff-buffers3 (a b c)
|
|||
|
(setq magit-ediff-buffers (list a b c))
|
|||
|
(setq magit-ediff-windows (current-window-configuration))
|
|||
|
(ediff-buffers3 a b c '(magit-ediff-add-cleanup)))
|
|||
|
|
|||
|
(defun magit-diff-range (diff)
|
|||
|
(if (eq major-mode 'magit-commit-mode)
|
|||
|
(let ((revs (split-string
|
|||
|
(magit-git-string "rev-list" "-1" "--parents"
|
|||
|
(car (last magit-refresh-args))))))
|
|||
|
(when (= (length revs) 2)
|
|||
|
(cons (cadr revs) (car revs))))
|
|||
|
(magit-section-diff-range diff)))
|
|||
|
|
|||
|
(defun magit-ediff-add-cleanup ()
|
|||
|
(make-local-variable 'magit-ediff-buffers)
|
|||
|
(setq-default magit-ediff-buffers ())
|
|||
|
|
|||
|
(make-local-variable 'magit-ediff-windows)
|
|||
|
(setq-default magit-ediff-windows ())
|
|||
|
|
|||
|
(add-hook 'ediff-cleanup-hook 'magit-ediff-restore 'append 'local))
|
|||
|
|
|||
|
(defun magit-ediff-restore ()
|
|||
|
"Kill any buffers in `magit-ediff-buffers' that are not visiting files and
|
|||
|
restore the window state that was saved before ediff was called."
|
|||
|
(dolist (buffer magit-ediff-buffers)
|
|||
|
(when (and (null (buffer-file-name buffer))
|
|||
|
(buffer-live-p buffer))
|
|||
|
(with-current-buffer buffer
|
|||
|
(when (and (eq magit-show-current-version 'index)
|
|||
|
(buffer-modified-p))
|
|||
|
(magit-save-index)))
|
|||
|
(kill-buffer buffer)))
|
|||
|
(let ((buf (current-buffer)))
|
|||
|
(set-window-configuration magit-ediff-windows)
|
|||
|
(set-buffer buf)))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-save-index ()
|
|||
|
"Add the content of current file as if it was the index."
|
|||
|
(interactive)
|
|||
|
(unless (eq magit-show-current-version 'index)
|
|||
|
(user-error "Current buffer doesn't visit the index version of a file"))
|
|||
|
(when (y-or-n-p (format "Stage current version of %s? " magit-file-name))
|
|||
|
(let ((buf (current-buffer))
|
|||
|
(name (magit-git-dir "magit-add-index")))
|
|||
|
(with-temp-file name
|
|||
|
(insert-buffer-substring buf))
|
|||
|
(let ((hash (magit-git-string "hash-object" "-t" "blob" "-w"
|
|||
|
(concat "--path=" magit-file-name)
|
|||
|
"--" name))
|
|||
|
(perm (substring (magit-git-string "ls-files" "-s"
|
|||
|
magit-file-name)
|
|||
|
0 6)))
|
|||
|
(magit-run-git "update-index" "--cacheinfo"
|
|||
|
perm hash magit-file-name)))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-interactive-resolve (file)
|
|||
|
"Resolve a merge conflict using Ediff."
|
|||
|
(interactive (list (magit-section-case (info) (diff (cadr info)))))
|
|||
|
(require 'ediff)
|
|||
|
(let ((merge-status (magit-git-lines "ls-files" "-u" "--" file))
|
|||
|
(base-buffer)
|
|||
|
(our-buffer (generate-new-buffer (concat file ".current")))
|
|||
|
(their-buffer (generate-new-buffer (concat file ".merged")))
|
|||
|
(merger-buffer)
|
|||
|
(windows (current-window-configuration)))
|
|||
|
(unless merge-status
|
|||
|
(user-error "Cannot resolve %s" file))
|
|||
|
(when (string-match "^[0-9]+ [0-9a-f]+ 1" (car merge-status))
|
|||
|
(pop merge-status)
|
|||
|
(setq base-buffer (generate-new-buffer (concat file ".base")))
|
|||
|
(with-current-buffer base-buffer
|
|||
|
(magit-git-insert "cat-file" "blob" (concat ":1:" file))))
|
|||
|
;; If the second or third version do not exit, we use an empty buffer for the deleted file
|
|||
|
(with-current-buffer our-buffer
|
|||
|
(when (string-match "^[0-9]+ [0-9a-f]+ 2" (car merge-status))
|
|||
|
(pop merge-status)
|
|||
|
(magit-git-insert "cat-file" "blob" (concat ":2:" file)))
|
|||
|
(let ((buffer-file-name file))
|
|||
|
(normal-mode t)))
|
|||
|
(with-current-buffer their-buffer
|
|||
|
(when (string-match "^[0-9]+ [0-9a-f]+ 3" (car merge-status))
|
|||
|
(magit-git-insert "cat-file" "blob" (concat ":3:" file)))
|
|||
|
(let ((buffer-file-name file))
|
|||
|
(normal-mode t)))
|
|||
|
;; We have now created the 3 buffer with ours, theirs and the ancestor files
|
|||
|
(if base-buffer
|
|||
|
(setq merger-buffer (ediff-merge-buffers-with-ancestor
|
|||
|
our-buffer their-buffer base-buffer nil nil file))
|
|||
|
(setq merger-buffer (ediff-merge-buffers our-buffer their-buffer nil nil file)))
|
|||
|
(with-current-buffer merger-buffer
|
|||
|
(setq ediff-show-clashes-only t)
|
|||
|
(setq-local magit-ediff-windows windows)
|
|||
|
(make-local-variable 'ediff-quit-hook)
|
|||
|
(add-hook 'ediff-quit-hook
|
|||
|
(lambda ()
|
|||
|
(let ((buffer-A ediff-buffer-A)
|
|||
|
(buffer-B ediff-buffer-B)
|
|||
|
(buffer-C ediff-buffer-C)
|
|||
|
(buffer-Ancestor ediff-ancestor-buffer)
|
|||
|
(windows magit-ediff-windows))
|
|||
|
(ediff-cleanup-mess)
|
|||
|
(kill-buffer buffer-A)
|
|||
|
(kill-buffer buffer-B)
|
|||
|
(when (bufferp buffer-Ancestor)
|
|||
|
(kill-buffer buffer-Ancestor))
|
|||
|
(set-window-configuration windows)))))))
|
|||
|
|
|||
|
;;;; Diff Mode
|
|||
|
;;;;; Diff Core
|
|||
|
|
|||
|
(define-derived-mode magit-diff-mode magit-mode "Magit Diff"
|
|||
|
"Mode for looking at a git diff.
|
|||
|
|
|||
|
\\<magit-diff-mode-map>Type `\\[magit-visit-item]` to visit the changed file, \
|
|||
|
`\\[magit-toggle-section]` to hide or show a hunk,
|
|||
|
`\\[magit-diff-larger-hunks]` and `\\[magit-diff-smaller-hunks]` to change \
|
|||
|
the size of the hunks.
|
|||
|
Type `\\[magit-apply-item]` to apply a change to your worktree and \
|
|||
|
`\\[magit-revert-item]` to reverse it.
|
|||
|
You can also use `\\[magit-ediff]` to see the current change with ediff.
|
|||
|
|
|||
|
More information can be found in Info node `(magit)Diffing'
|
|||
|
|
|||
|
\\{magit-diff-mode-map}"
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defvar magit-diff-buffer-name "*magit-diff*"
|
|||
|
"Name of buffer used to display a diff.")
|
|||
|
|
|||
|
(defvar magit-stash-buffer-name "*magit-stash*"
|
|||
|
"Name of buffer used to display a stash.")
|
|||
|
|
|||
|
;;;;; Diff Entry Commands
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-diff (range &optional working args)
|
|||
|
"Show differences between two commits.
|
|||
|
RANGE should be a range (A..B or A...B) but can also be a single
|
|||
|
commit. If one side of the range is omitted, then it defaults
|
|||
|
to HEAD. If just a commit is given, then changes in the working
|
|||
|
tree relative to that commit are shown."
|
|||
|
(interactive (list (magit-read-rev-range "Diff")))
|
|||
|
(magit-mode-setup magit-diff-buffer-name
|
|||
|
#'pop-to-buffer
|
|||
|
#'magit-diff-mode
|
|||
|
#'magit-refresh-diff-buffer range working args))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-diff-working-tree (rev)
|
|||
|
"Show differences between a commit and the current working tree."
|
|||
|
(interactive (list (magit-read-rev-with-default "Diff working tree with")))
|
|||
|
(magit-diff (or rev "HEAD") t))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-diff-staged ()
|
|||
|
"Show differences between the index and the HEAD commit."
|
|||
|
(interactive)
|
|||
|
(magit-diff nil nil (list "--cached")))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-diff-unstaged ()
|
|||
|
"Show differences between the current working tree and index."
|
|||
|
(interactive)
|
|||
|
(magit-diff nil))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-diff-stash (stash &optional noselect)
|
|||
|
"Show changes in a stash.
|
|||
|
A Stash consist of more than just one commit. This command uses
|
|||
|
a special diff range so that the stashed changes actually were a
|
|||
|
single commit."
|
|||
|
(interactive (list (magit-read-stash "Show stash (number): ")))
|
|||
|
(magit-mode-setup magit-commit-buffer-name
|
|||
|
(if noselect 'display-buffer 'pop-to-buffer)
|
|||
|
#'magit-diff-mode
|
|||
|
#'magit-refresh-diff-buffer
|
|||
|
(concat stash "^2^.." stash)))
|
|||
|
|
|||
|
(defun magit-diff-with-mark (range)
|
|||
|
"Show difference between the marked commit and the one at point.
|
|||
|
If there is no commit at point, then prompt for one."
|
|||
|
(interactive
|
|||
|
(let* ((marked (or magit-marked-commit (user-error "No commit marked")))
|
|||
|
(current (magit-get-current-branch))
|
|||
|
(is-current (string= (magit-name-rev marked) current))
|
|||
|
(commit (or (magit-guess-branch)
|
|||
|
(magit-read-rev
|
|||
|
(format "Diff marked commit %s with" marked)
|
|||
|
(unless is-current current)
|
|||
|
current))))
|
|||
|
(list (concat marked ".." commit))))
|
|||
|
(magit-diff range))
|
|||
|
|
|||
|
(defun magit-refresh-diff-buffer (range &optional working args)
|
|||
|
(let ((magit-current-diff-range
|
|||
|
(cond (working (cons range 'working))
|
|||
|
((null range) nil)
|
|||
|
((consp range)
|
|||
|
(prog1 range
|
|||
|
(setq range (concat (car range) ".." (cdr range)))))
|
|||
|
((string-match "^\\([^.]+\\)\\.\\.\\([^.]\\)$" range)
|
|||
|
(cons (match-string 1 range)
|
|||
|
(match-string 2 range))))))
|
|||
|
(magit-git-insert-section
|
|||
|
(diffbuf (cond (working
|
|||
|
(format "Changes from %s to working tree" range))
|
|||
|
((not range)
|
|||
|
(if (member "--cached" args)
|
|||
|
"Staged changes"
|
|||
|
"Unstaged changes"))
|
|||
|
(t
|
|||
|
(format "Changes in %s" range))))
|
|||
|
#'magit-wash-diffs
|
|||
|
"diff" (magit-diff-U-arg)
|
|||
|
(and magit-show-diffstat "--patch-with-stat")
|
|||
|
range args "--")))
|
|||
|
|
|||
|
;;;;; Diff Washing
|
|||
|
|
|||
|
(defconst magit-diff-statline-re
|
|||
|
(concat "^ ?"
|
|||
|
"\\(.*\\)" ; file
|
|||
|
"\\( +| +\\)" ; separator
|
|||
|
"\\([0-9]+\\|Bin\\(?: +[0-9]+ -> [0-9]+ bytes\\)?$\\) ?"
|
|||
|
"\\(\\+*\\)" ; add
|
|||
|
"\\(-*\\)$")) ; del
|
|||
|
|
|||
|
(defvar magit-current-diff-range nil
|
|||
|
"Used internally when setting up magit diff sections.")
|
|||
|
|
|||
|
(defvar-local magit-diffstat-cached-sections nil)
|
|||
|
(put 'magit-diffstat-cached-sections 'permanent-local t)
|
|||
|
|
|||
|
(defun magit-wash-diffs ()
|
|||
|
(magit-wash-diffstats)
|
|||
|
(when (re-search-forward "^diff" nil t)
|
|||
|
(goto-char (line-beginning-position))
|
|||
|
(magit-wash-sequence #'magit-wash-diff))
|
|||
|
(goto-char (point-max))
|
|||
|
(magit-xref-insert-buttons))
|
|||
|
|
|||
|
(defun magit-wash-diffstats ()
|
|||
|
(let ((beg (point)))
|
|||
|
(when (re-search-forward "^ ?\\([0-9]+ +files? change[^\n]*\n\\)" nil t)
|
|||
|
(let ((heading (match-string-no-properties 1)))
|
|||
|
(delete-region (match-beginning 0) (match-end 0))
|
|||
|
(goto-char beg)
|
|||
|
(magit-with-section (section diffstats 'diffstats heading)
|
|||
|
(magit-wash-sequence #'magit-wash-diffstat)))
|
|||
|
(setq magit-diffstat-cached-sections
|
|||
|
(nreverse magit-diffstat-cached-sections)))))
|
|||
|
|
|||
|
(defun magit-wash-diffstat ()
|
|||
|
(when (looking-at magit-diff-statline-re)
|
|||
|
(magit-bind-match-strings (file sep cnt add del)
|
|||
|
(delete-region (point) (1+ (line-end-position)))
|
|||
|
(magit-with-section (section diffstat 'diffstat)
|
|||
|
(insert " " file sep cnt " ")
|
|||
|
(when add (insert (propertize add 'face 'magit-diff-add)))
|
|||
|
(when del (insert (propertize del 'face 'magit-diff-del)))
|
|||
|
(insert "\n")
|
|||
|
(push section magit-diffstat-cached-sections)))))
|
|||
|
|
|||
|
(defun magit-wash-diff ()
|
|||
|
(magit-with-section (section diff (buffer-substring-no-properties
|
|||
|
(line-beginning-position)
|
|||
|
(line-end-position)))
|
|||
|
(setq section (magit-wash-diff-section section))))
|
|||
|
|
|||
|
(defun magit-wash-diff-section (section)
|
|||
|
(cond ((re-search-forward "^\\* Unmerged path \\(.*\\)" nil t)
|
|||
|
(forward-line 0)
|
|||
|
(let ((file (magit-decode-git-path (match-string-no-properties 1))))
|
|||
|
(delete-region (point) (line-end-position))
|
|||
|
(insert "\tUnmerged " file "\n")
|
|||
|
(setf (magit-section-diff-status section) 'unmerged)
|
|||
|
(setf (magit-section-info section) file)
|
|||
|
section))
|
|||
|
((re-search-forward "^diff" nil t)
|
|||
|
(forward-line 0)
|
|||
|
(let ((file (cond
|
|||
|
((looking-at "^diff --git \\(\".*\"\\) \\(\".*\"\\)$")
|
|||
|
(substring (magit-decode-git-path
|
|||
|
(match-string-no-properties 2)) 2))
|
|||
|
((looking-at "^diff --git ./\\(.*\\) ./\\(.*\\)$")
|
|||
|
(match-string-no-properties 2))
|
|||
|
((looking-at "^diff --cc +\\(.*\\)$")
|
|||
|
(match-string-no-properties 1))))
|
|||
|
(end (save-excursion
|
|||
|
(forward-line) ;; skip over "diff" line
|
|||
|
(if (re-search-forward "^diff\\|^@@" nil t)
|
|||
|
(goto-char (match-beginning 0))
|
|||
|
(goto-char (point-max)))
|
|||
|
(point-marker))))
|
|||
|
(when magit-diffstat-cached-sections
|
|||
|
(setf (magit-section-info (pop magit-diffstat-cached-sections))
|
|||
|
file))
|
|||
|
(let ((status (cond
|
|||
|
((looking-at "^diff --cc")
|
|||
|
'unmerged)
|
|||
|
((save-excursion
|
|||
|
(re-search-forward "^new file" end t))
|
|||
|
'new)
|
|||
|
((save-excursion
|
|||
|
(re-search-forward "^deleted" end t))
|
|||
|
(setf (magit-section-hidden section) t)
|
|||
|
'deleted)
|
|||
|
((save-excursion
|
|||
|
(re-search-forward "^rename" end t))
|
|||
|
'renamed)
|
|||
|
(t
|
|||
|
'modified)))
|
|||
|
(file2 (cond
|
|||
|
((save-excursion
|
|||
|
(re-search-forward "^rename from \\(.*\\)"
|
|||
|
end t))
|
|||
|
(match-string-no-properties 1)))))
|
|||
|
(setf (magit-section-diff-status section) status)
|
|||
|
(setf (magit-section-info section) file)
|
|||
|
(setf (magit-section-diff-file2 section) (or file2 file))
|
|||
|
(setf (magit-section-diff-range section) magit-current-diff-range)
|
|||
|
(magit-insert-diff-title status file file2)
|
|||
|
(when (re-search-forward
|
|||
|
"\\(--- \\(.*\\)\n\\+\\+\\+ \\(.*\\)\n\\)" nil t)
|
|||
|
(magit-put-face-property (match-beginning 1) (match-end 1)
|
|||
|
'magit-diff-hunk-header)
|
|||
|
(magit-put-face-property (match-beginning 2) (match-end 2)
|
|||
|
'magit-diff-file-header)
|
|||
|
(magit-put-face-property (match-beginning 3) (match-end 3)
|
|||
|
'magit-diff-file-header))
|
|||
|
(goto-char end)
|
|||
|
(magit-wash-sequence #'magit-wash-hunk)))
|
|||
|
section)))
|
|||
|
|
|||
|
(defun magit-insert-diff-title (status file file2)
|
|||
|
(insert (format "\t%-10s " (capitalize (symbol-name status)))
|
|||
|
file
|
|||
|
(if (eq status 'renamed) (format " (from %s)" file2) "")
|
|||
|
"\n"))
|
|||
|
|
|||
|
(defun magit-wash-hunk ()
|
|||
|
(when (looking-at "^@@\\(@\\)?.+")
|
|||
|
(let ((merging (match-beginning 1)))
|
|||
|
(magit-with-section (section hunk (match-string 0))
|
|||
|
(magit-put-face-property (point) (line-end-position)
|
|||
|
'magit-diff-hunk-header)
|
|||
|
(forward-line)
|
|||
|
(while (not (or (eobp) (looking-at "^diff\\|^@@")))
|
|||
|
(magit-put-face-property
|
|||
|
(point) (line-end-position)
|
|||
|
(cond
|
|||
|
((looking-at "^\\+\\+<<<<<<<") 'magit-diff-merge-current)
|
|||
|
((looking-at "^\\+\\+=======") 'magit-diff-merge-separator)
|
|||
|
((looking-at "^\\+\\+|||||||") 'magit-diff-merge-diff3-separator)
|
|||
|
((looking-at "^\\+\\+>>>>>>>") 'magit-diff-merge-proposed)
|
|||
|
((looking-at (if merging "^\\(\\+\\| \\+\\)" "^\\+"))
|
|||
|
(magit-diff-highlight-whitespace merging)
|
|||
|
'magit-diff-add)
|
|||
|
((looking-at (if merging "^\\(-\\| \\-\\)" "^-"))
|
|||
|
(magit-diff-highlight-whitespace merging)
|
|||
|
'magit-diff-del)
|
|||
|
(t
|
|||
|
'magit-diff-none)))
|
|||
|
(forward-line))
|
|||
|
(when (eq magit-diff-refine-hunk 'all)
|
|||
|
(magit-diff-refine-hunk section))))
|
|||
|
t))
|
|||
|
|
|||
|
(defun magit-diff-highlight-whitespace (merging)
|
|||
|
(when (and magit-highlight-whitespace
|
|||
|
(or (derived-mode-p 'magit-status-mode)
|
|||
|
(not (eq magit-highlight-whitespace 'status))))
|
|||
|
(let ((prefix (if merging "^[-\\+\s]\\{2\\}" "^[-\\+]"))
|
|||
|
(indent
|
|||
|
(if (local-variable-p 'magit-highlight-indentation)
|
|||
|
magit-highlight-indentation
|
|||
|
(setq-local
|
|||
|
magit-highlight-indentation
|
|||
|
(cdr (cl-find-if (lambda (pair)
|
|||
|
(string-match-p (car pair) default-directory))
|
|||
|
(default-value 'magit-highlight-indentation)
|
|||
|
:from-end t))))))
|
|||
|
(when (and magit-highlight-trailing-whitespace
|
|||
|
(looking-at (concat prefix ".*?\\([ \t]+\\)$")))
|
|||
|
(magit-put-face-property (match-beginning 1) (match-end 1)
|
|||
|
'magit-whitespace-warning-face))
|
|||
|
(when (or (and (eq indent 'tabs)
|
|||
|
(looking-at (concat prefix "\\( *\t[ \t]*\\)")))
|
|||
|
(and (integerp indent)
|
|||
|
(looking-at (format "%s\\([ \t]* \\{%s,\\}[ \t]*\\)"
|
|||
|
prefix indent))))
|
|||
|
(magit-put-face-property (match-beginning 1) (match-end 1)
|
|||
|
'magit-whitespace-warning-face)))))
|
|||
|
|
|||
|
;;;;; Diff Mode Commands
|
|||
|
|
|||
|
(defun magit-diff-toggle-refine-hunk (&optional other)
|
|||
|
"Turn diff-hunk refining on or off.
|
|||
|
|
|||
|
If hunk refining is currently on, then hunk refining is turned off.
|
|||
|
If hunk refining is off, then hunk refining is turned on, in
|
|||
|
`selected' mode (only the currently selected hunk is refined).
|
|||
|
|
|||
|
With a prefix argument, the \"third choice\" is used instead:
|
|||
|
If hunk refining is currently on, then refining is kept on, but
|
|||
|
the refining mode (`selected' or `all') is switched.
|
|||
|
If hunk refining is off, then hunk refining is turned on, in
|
|||
|
`all' mode (all hunks refined).
|
|||
|
|
|||
|
Customize variable `magit-diff-refine-hunk' to change the default mode."
|
|||
|
(interactive "P")
|
|||
|
(let ((hunk (and magit-highlighted-section
|
|||
|
(eq (magit-section-type magit-highlighted-section) 'hunk)
|
|||
|
magit-highlighted-section))
|
|||
|
(old magit-diff-refine-hunk))
|
|||
|
(setq-local magit-diff-refine-hunk
|
|||
|
(if other
|
|||
|
(if (eq old 'all) t 'all)
|
|||
|
(not old)))
|
|||
|
(cond ((or (eq old 'all)
|
|||
|
(eq magit-diff-refine-hunk 'all))
|
|||
|
(magit-refresh))
|
|||
|
((not hunk))
|
|||
|
(magit-diff-refine-hunk
|
|||
|
(magit-diff-refine-hunk hunk))
|
|||
|
(t
|
|||
|
(magit-diff-unrefine-hunk hunk)))
|
|||
|
(message "magit-diff-refine-hunk: %s" magit-diff-refine-hunk)))
|
|||
|
|
|||
|
(defun magit-diff-refine-hunk (hunk)
|
|||
|
(save-excursion
|
|||
|
(goto-char (magit-section-beginning hunk))
|
|||
|
;; `diff-refine-hunk' does not handle combined diffs.
|
|||
|
(unless (looking-at "@@@")
|
|||
|
(diff-refine-hunk))))
|
|||
|
|
|||
|
(defun magit-diff-unrefine-hunk (hunk)
|
|||
|
(remove-overlays (magit-section-beginning hunk)
|
|||
|
(magit-section-end hunk)
|
|||
|
'diff-mode 'fine))
|
|||
|
|
|||
|
;;;; Wazzup Mode
|
|||
|
|
|||
|
(define-derived-mode magit-wazzup-mode magit-mode "Magit Wazzup"
|
|||
|
"Mode for looking at git commits not merged into current HEAD.
|
|||
|
|
|||
|
\\<magit-wazzup-mode-map>Type `\\[magit-toggle-section]` to show or hide \
|
|||
|
section, `\\[magit-visit-item]` to visit an item \
|
|||
|
`\\[magit-show-item-or-scroll-up]` to show it.
|
|||
|
Type `\\[magit-diff-working-tree]` to display change with your working tree, \
|
|||
|
and `\\[magit-diff]` to display change
|
|||
|
between any two commit.
|
|||
|
Type `\\[magit-cherry-pick-item]` to cherry-pick a commit, and \
|
|||
|
`\\[magit-apply-item]` to apply its change to your
|
|||
|
working tree, without committing, and `\\[magit-key-mode-popup-merging]` \
|
|||
|
to merge those change.
|
|||
|
Type `\\[magit-refresh]` to refresh current buffer.
|
|||
|
|
|||
|
More information can be found in Info node `(magit)Wazzup'
|
|||
|
|
|||
|
\\{magit-wazzup-mode-map}"
|
|||
|
:group 'magit)
|
|||
|
|
|||
|
(defvar magit-wazzup-buffer-name "*magit-wazzup*"
|
|||
|
"Name of buffer used to display commits not merged into current HEAD.")
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-wazzup (branch)
|
|||
|
"Show a list of branches in a dedicated buffer.
|
|||
|
Unlike in the buffer created by `magit-branch-manager' each
|
|||
|
branch can be expanded to show a list of commits not merged
|
|||
|
into the selected branch."
|
|||
|
(interactive
|
|||
|
(let ((branch (magit-get-current-branch)))
|
|||
|
(list (if current-prefix-arg
|
|||
|
(magit-read-rev "Wazzup branch" branch)
|
|||
|
branch))))
|
|||
|
(magit-mode-setup magit-wazzup-buffer-name nil
|
|||
|
#'magit-wazzup-mode
|
|||
|
#'magit-refresh-wazzup-buffer branch))
|
|||
|
|
|||
|
(defun magit-refresh-wazzup-buffer (head)
|
|||
|
(magit-with-section (section wazzupbuf 'wazzupbuf nil t)
|
|||
|
(run-hooks 'magit-wazzup-sections-hook)))
|
|||
|
|
|||
|
(defun magit-insert-wazzup-head-line ()
|
|||
|
(magit-insert-line-section (line)
|
|||
|
(concat "Head: "
|
|||
|
(propertize (car magit-refresh-args) 'face 'magit-branch) " "
|
|||
|
(abbreviate-file-name default-directory))))
|
|||
|
|
|||
|
(defun magit-insert-wazzup-branches ()
|
|||
|
(dolist (upstream (magit-git-lines "show-ref"))
|
|||
|
(setq upstream (cadr (split-string upstream " ")))
|
|||
|
(when (and (not (string-match-p "HEAD$" upstream))
|
|||
|
(string-match-p "^refs/\\(heads\\|remotes\\)/" upstream))
|
|||
|
(magit-insert-wazzup-commits upstream (car magit-refresh-args)))))
|
|||
|
|
|||
|
(defun magit-insert-wazzup-commits (upstream head)
|
|||
|
(let ((count (string-to-number
|
|||
|
(magit-git-string "rev-list" "--count" "--right-only"
|
|||
|
(concat head "..." upstream)))))
|
|||
|
(when (> count 0)
|
|||
|
(magit-with-section
|
|||
|
(section wazzup upstream
|
|||
|
(format "%3s %s\n" count (magit-format-ref-label upstream))
|
|||
|
nil t)
|
|||
|
(cond
|
|||
|
((magit-section-hidden section)
|
|||
|
(setf (magit-section-hidden section) t)
|
|||
|
(setf (magit-section-needs-refresh-on-show section) t))
|
|||
|
(t
|
|||
|
(let ((beg (point)))
|
|||
|
(magit-git-insert "cherry" "-v" "--abbrev" head upstream)
|
|||
|
(save-restriction
|
|||
|
(narrow-to-region beg (point))
|
|||
|
(goto-char (point-min))
|
|||
|
(magit-wash-log 'cherry)))))))))
|
|||
|
|
|||
|
;;;; Branch Manager Mode
|
|||
|
;;;;; (core)
|
|||
|
|
|||
|
(define-derived-mode magit-branch-manager-mode magit-mode "Magit Branch"
|
|||
|
"Mode for looking at git branches.
|
|||
|
|
|||
|
\\<magit-branch-manager-mode-map>Type `\\[magit-visit-item]` to checkout a branch, `\\[magit-reset-head]' to reset current branch,
|
|||
|
you can also merge the branch with `\\[magit-key-mode-popup-merging]`
|
|||
|
|
|||
|
Type `\\[magit-discard-item]' to delete a branch, or `\\[universal-argument] \\[magit-discard-item]' to force the deletion.
|
|||
|
Type `\\[magit-rename-item]' to Rename a branch.
|
|||
|
|
|||
|
More information can be found in Info node `(magit)The branch list'
|
|||
|
|
|||
|
\\{magit-branch-manager-mode-map}
|
|||
|
Unless shadowed by the mode specific bindings above, bindings
|
|||
|
from the parent keymap `magit-mode-map' are also available.")
|
|||
|
|
|||
|
(defvar magit-branches-buffer-name "*magit-branches*"
|
|||
|
"Name of buffer used to display and manage branches.")
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-branch-manager ()
|
|||
|
"Show a list of branches in a dedicated buffer."
|
|||
|
(interactive)
|
|||
|
(if (magit-get-top-dir) ; Kludge for #1215
|
|||
|
(magit-mode-setup magit-branches-buffer-name nil
|
|||
|
#'magit-branch-manager-mode
|
|||
|
#'magit-refresh-branch-manager)
|
|||
|
(user-error "There is no Git repository here")))
|
|||
|
|
|||
|
(defun magit-refresh-branch-manager ()
|
|||
|
(magit-git-insert-section (branchbuf nil)
|
|||
|
#'magit-wash-branches
|
|||
|
"branch" "-vva" magit-custom-options))
|
|||
|
|
|||
|
;;;;; Branch List Washing
|
|||
|
|
|||
|
(defconst magit-wash-branch-line-re
|
|||
|
(concat "^\\([ *] \\)" ; 1: current branch marker
|
|||
|
"\\(.+?\\) +" ; 2: branch name
|
|||
|
"\\(?:"
|
|||
|
"\\([0-9a-fA-F]+\\)" ; 3: sha1
|
|||
|
" "
|
|||
|
"\\(?:\\["
|
|||
|
"\\([^:\n]+?\\)" ; 4: tracking
|
|||
|
"\\(?:: \\)?"
|
|||
|
"\\(?:ahead \\([0-9]+\\)\\)?" ; 5: ahead
|
|||
|
"\\(?:, \\)?"
|
|||
|
"\\(?:behind \\([0-9]+\\)\\)?" ; 6: behind
|
|||
|
"\\] \\)?"
|
|||
|
"\\(?:.*\\)" ; message
|
|||
|
"\\|" ; or
|
|||
|
"-> " ; the pointer to
|
|||
|
"\\(.+\\)" ; 7: a ref
|
|||
|
"\\)\n"))
|
|||
|
|
|||
|
(defun magit-wash-branch-line (&optional remote-name)
|
|||
|
(when (looking-at magit-wash-branch-line-re)
|
|||
|
;; ^ Kludge for #1162. v Don't reindent for now.
|
|||
|
(let* ((marker (match-string 1))
|
|||
|
(branch (match-string 2))
|
|||
|
(sha1 (match-string 3))
|
|||
|
(tracking (match-string 4))
|
|||
|
(ahead (match-string 5))
|
|||
|
(behind (match-string 6))
|
|||
|
(other-ref (match-string 7))
|
|||
|
(branch-face (and (equal marker "* ") 'magit-branch)))
|
|||
|
(delete-region (point) (line-beginning-position 2))
|
|||
|
(magit-with-section (section branch branch)
|
|||
|
(insert (propertize (or sha1 (make-string 7 ? ))
|
|||
|
'face 'magit-log-sha1)
|
|||
|
" " marker
|
|||
|
(propertize (if (string-match-p "^remotes/" branch)
|
|||
|
(substring branch 8)
|
|||
|
branch)
|
|||
|
'face branch-face))
|
|||
|
(when other-ref
|
|||
|
(insert " -> " (substring other-ref (+ 1 (length remote-name)))))
|
|||
|
(when (and tracking
|
|||
|
(equal (magit-get-tracked-branch branch t)
|
|||
|
(concat "refs/remotes/" tracking)))
|
|||
|
(insert " [")
|
|||
|
;; getting rid of the tracking branch name if it is
|
|||
|
;; the same as the branch name
|
|||
|
(let* ((remote (magit-get "branch" branch "remote"))
|
|||
|
(merge (substring tracking (+ 1 (length remote)))))
|
|||
|
(insert (propertize (if (string= branch merge)
|
|||
|
(concat "@ " remote)
|
|||
|
(concat merge " @ " remote))
|
|||
|
'face 'magit-log-head-label-remote)))
|
|||
|
(when (or ahead behind)
|
|||
|
(insert ":")
|
|||
|
(and ahead (insert "ahead " (propertize ahead 'face branch-face)))
|
|||
|
(and ahead behind (insert ", "))
|
|||
|
(and behind (insert "behind "
|
|||
|
(propertize behind 'face
|
|||
|
'magit-log-head-label-remote))))
|
|||
|
(insert "]"))
|
|||
|
(insert "\n")))))
|
|||
|
|
|||
|
(defun magit-wash-remote-branches-group (group)
|
|||
|
(let* ((remote (car group))
|
|||
|
(url (magit-get "remote" remote "url"))
|
|||
|
(push-url (magit-get "remote" remote "pushurl"))
|
|||
|
(urls (concat url (and push-url (concat ", " push-url))))
|
|||
|
(marker (cadr group)))
|
|||
|
(magit-with-section
|
|||
|
(section remote remote (format "%s (%s):" remote urls) t)
|
|||
|
(magit-wash-branches-between-point-and-marker marker remote)
|
|||
|
(insert "\n"))))
|
|||
|
|
|||
|
(defun magit-wash-branches-between-point-and-marker (marker &optional remote-name)
|
|||
|
(save-restriction
|
|||
|
(narrow-to-region (point) marker)
|
|||
|
(magit-wash-sequence
|
|||
|
(apply-partially 'magit-wash-branch-line remote-name))))
|
|||
|
|
|||
|
(defun magit-wash-branches ()
|
|||
|
;; get the names of the remotes
|
|||
|
(let* ((remotes (magit-git-lines "remote"))
|
|||
|
;; get the location of remotes in the buffer
|
|||
|
(markers
|
|||
|
(append (mapcar (lambda (remote)
|
|||
|
(save-excursion
|
|||
|
(when (re-search-forward
|
|||
|
(format "^ remotes/%s/" remote) nil t)
|
|||
|
(beginning-of-line)
|
|||
|
(point-marker))))
|
|||
|
remotes)
|
|||
|
(list (save-excursion
|
|||
|
(goto-char (point-max))
|
|||
|
(point-marker)))))
|
|||
|
;; list of remote elements to display in the buffer
|
|||
|
(remote-groups
|
|||
|
(cl-loop for remote in remotes
|
|||
|
for end-markers on (cdr markers)
|
|||
|
for marker = (cl-loop for x in end-markers thereis x)
|
|||
|
collect (list remote marker))))
|
|||
|
;; actual displaying of information
|
|||
|
(magit-with-section (section local "." "Local:" t)
|
|||
|
(magit-wash-branches-between-point-and-marker
|
|||
|
(cl-loop for x in markers thereis x))
|
|||
|
(insert "\n"))
|
|||
|
(mapc 'magit-wash-remote-branches-group remote-groups)
|
|||
|
;; make sure markers point to nil so that they can be garbage collected
|
|||
|
(mapc (lambda (marker)
|
|||
|
(when marker
|
|||
|
(set-marker marker nil)))
|
|||
|
markers)))
|
|||
|
|
|||
|
;;;;; (commands)
|
|||
|
|
|||
|
(defun magit-rename-item ()
|
|||
|
"Rename the item at point."
|
|||
|
(interactive)
|
|||
|
(magit-section-action rename ()
|
|||
|
(branch (call-interactively 'magit-rename-branch))
|
|||
|
(remote (call-interactively 'magit-rename-remote))))
|
|||
|
|
|||
|
(defun magit-change-what-branch-tracks ()
|
|||
|
"Change which remote branch the current branch tracks."
|
|||
|
(interactive)
|
|||
|
(let* ((branch (magit-guess-branch))
|
|||
|
(track (magit-read-rev "Track branch"))
|
|||
|
(track-
|
|||
|
(cond ((string-match "^\\([^ ]+\\) +(\\(.+\\))$" track)
|
|||
|
(cons (match-string 2 track)
|
|||
|
(concat "refs/heads/" (match-string 1 track))))
|
|||
|
((string-match "^\\(?:refs/remotes/\\)?\\([^/]+\\)/\\(.+\\)"
|
|||
|
track)
|
|||
|
(cons (match-string 1 track)
|
|||
|
(concat "refs/heads/" (match-string 2 track))))
|
|||
|
(t
|
|||
|
(user-error "Cannot parse the remote and branch name")))))
|
|||
|
(magit-set (car track-) "branch" branch "remote")
|
|||
|
(magit-set (cdr track-) "branch" branch "merge")
|
|||
|
(magit-refresh)))
|
|||
|
|
|||
|
;;; Miscellaneous
|
|||
|
;;;; Miscellaneous Commands
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-init (directory)
|
|||
|
"Create or reinitialize a Git repository.
|
|||
|
Read directory name and initialize it as new Git repository.
|
|||
|
|
|||
|
If the directory is below an existing repository, then the user
|
|||
|
has to confirm that a new one should be created inside; or when
|
|||
|
the directory is the root of the existing repository, whether
|
|||
|
it should be reinitialized.
|
|||
|
|
|||
|
Non-interactively DIRECTORY is always (re-)initialized."
|
|||
|
(interactive
|
|||
|
(let* ((dir (file-name-as-directory
|
|||
|
(expand-file-name
|
|||
|
(read-directory-name "Create repository in: "))))
|
|||
|
(top (magit-get-top-dir dir)))
|
|||
|
(if (and top
|
|||
|
(not (yes-or-no-p
|
|||
|
(if (string-equal top dir)
|
|||
|
(format "Reinitialize existing repository %s? " dir)
|
|||
|
(format "%s is a repository. Create another in %s? "
|
|||
|
top dir)))))
|
|||
|
(user-error "Abort")
|
|||
|
(list dir))))
|
|||
|
(magit-run-git "init" (expand-file-name directory)))
|
|||
|
|
|||
|
(defun magit-copy-item-as-kill ()
|
|||
|
"Copy sha1 of commit at point into kill ring."
|
|||
|
(interactive)
|
|||
|
(magit-section-action copy (info)
|
|||
|
((branch commit file diff)
|
|||
|
(kill-new info)
|
|||
|
(message "%s" info))))
|
|||
|
|
|||
|
(defun magit-ignore-item (edit &optional local)
|
|||
|
"Ignore the item at point.
|
|||
|
With a prefix argument edit the ignore string."
|
|||
|
(interactive "P")
|
|||
|
(magit-section-action ignore (info)
|
|||
|
([file untracked]
|
|||
|
(magit-ignore-file (concat "/" info) edit local)
|
|||
|
(magit-refresh))
|
|||
|
(diff
|
|||
|
(when (yes-or-no-p (format "%s is tracked. Untrack and ignore? " info))
|
|||
|
(magit-ignore-file (concat "/" info) edit local)
|
|||
|
(magit-run-git "rm" "--cached" info)))))
|
|||
|
|
|||
|
(defun magit-ignore-item-locally (edit)
|
|||
|
"Ignore the item at point locally only.
|
|||
|
With a prefix argument edit the ignore string."
|
|||
|
(interactive "P")
|
|||
|
(magit-ignore-item edit t))
|
|||
|
|
|||
|
(defun magit-ignore-file (file &optional edit local)
|
|||
|
"Add FILE to the list of files to ignore.
|
|||
|
If EDIT is non-nil, prompt the user for the string to be ignored
|
|||
|
instead of using FILE. The changes are written to .gitignore
|
|||
|
except if LOCAL is non-nil in which case they are written to
|
|||
|
.git/info/exclude."
|
|||
|
(let* ((local-ignore-dir (magit-git-dir "info/"))
|
|||
|
(ignore-file (if local
|
|||
|
(concat local-ignore-dir "exclude")
|
|||
|
".gitignore")))
|
|||
|
(when edit
|
|||
|
(setq file (magit-ignore-edit-string file)))
|
|||
|
(when (and local (not (file-exists-p local-ignore-dir)))
|
|||
|
(make-directory local-ignore-dir t))
|
|||
|
(with-temp-buffer
|
|||
|
(when (file-exists-p ignore-file)
|
|||
|
(insert-file-contents ignore-file))
|
|||
|
(goto-char (point-max))
|
|||
|
(unless (bolp)
|
|||
|
(insert "\n"))
|
|||
|
(insert file "\n")
|
|||
|
(write-region nil nil ignore-file))))
|
|||
|
|
|||
|
(defun magit-ignore-edit-string (file)
|
|||
|
"Prompt the user for the string to be ignored.
|
|||
|
A list of predefined values with wildcards is derived from the
|
|||
|
filename FILE."
|
|||
|
(let* ((extension (concat "*." (file-name-extension file)))
|
|||
|
(extension-in-dir (concat (file-name-directory file) extension))
|
|||
|
(filename (file-name-nondirectory file))
|
|||
|
(completions (list extension extension-in-dir filename file)))
|
|||
|
(magit-completing-read "File/pattern to ignore"
|
|||
|
completions nil nil nil nil file)))
|
|||
|
|
|||
|
;;;; Commit Mark
|
|||
|
|
|||
|
(defvar magit-marked-commit nil)
|
|||
|
|
|||
|
(defvar-local magit-mark-overlay nil)
|
|||
|
(put 'magit-mark-overlay 'permanent-local t)
|
|||
|
|
|||
|
(defun magit-mark-item (&optional unmark)
|
|||
|
"Mark the commit at point.
|
|||
|
Some commands act on the marked commit by default or use it as
|
|||
|
default when prompting for a commit."
|
|||
|
(interactive "P")
|
|||
|
(if unmark
|
|||
|
(setq magit-marked-commit nil)
|
|||
|
(magit-section-action mark (info)
|
|||
|
(commit (setq magit-marked-commit
|
|||
|
(if (equal magit-marked-commit info) nil info)))))
|
|||
|
(magit-refresh-marked-commits)
|
|||
|
(run-hooks 'magit-mark-commit-hook))
|
|||
|
|
|||
|
(defun magit-refresh-marked-commits ()
|
|||
|
(magit-map-magit-buffers #'magit-refresh-marked-commits-in-buffer))
|
|||
|
|
|||
|
(defun magit-refresh-marked-commits-in-buffer ()
|
|||
|
(unless magit-mark-overlay
|
|||
|
(setq magit-mark-overlay (make-overlay 1 1))
|
|||
|
(overlay-put magit-mark-overlay 'face 'magit-item-mark))
|
|||
|
(delete-overlay magit-mark-overlay)
|
|||
|
(magit-map-sections
|
|||
|
(lambda (section)
|
|||
|
(when (and (eq (magit-section-type section) 'commit)
|
|||
|
(equal (magit-section-info section)
|
|||
|
magit-marked-commit))
|
|||
|
(move-overlay magit-mark-overlay
|
|||
|
(magit-section-beginning section)
|
|||
|
(magit-section-end section)
|
|||
|
(current-buffer))))
|
|||
|
magit-root-section))
|
|||
|
|
|||
|
;;;; ChangeLog
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-add-change-log-entry (&optional whoami file-name other-window)
|
|||
|
"Find change log file and add date entry and item for current change.
|
|||
|
This differs from `add-change-log-entry' (which see) in that
|
|||
|
it acts on the current hunk in a Magit buffer instead of on
|
|||
|
a position in a file-visiting buffer."
|
|||
|
(interactive (list current-prefix-arg
|
|||
|
(prompt-for-change-log-name)))
|
|||
|
(let (buf pos)
|
|||
|
(save-window-excursion
|
|||
|
(magit-visit-item)
|
|||
|
(setq buf (current-buffer)
|
|||
|
pos (point)))
|
|||
|
(save-excursion
|
|||
|
(with-current-buffer buf
|
|||
|
(goto-char pos)
|
|||
|
(add-change-log-entry whoami file-name other-window)))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-add-change-log-entry-other-window (&optional whoami file-name)
|
|||
|
"Find change log file in other window and add entry and item.
|
|||
|
This differs from `add-change-log-entry-other-window' (which see)
|
|||
|
in that it acts on the current hunk in a Magit buffer instead of
|
|||
|
on a position in a file-visiting buffer."
|
|||
|
(interactive (and current-prefix-arg
|
|||
|
(list current-prefix-arg
|
|||
|
(prompt-for-change-log-name))))
|
|||
|
(magit-add-change-log-entry whoami file-name t))
|
|||
|
|
|||
|
;;;; Read Repository
|
|||
|
|
|||
|
(defun magit-read-top-dir (dir)
|
|||
|
"Ask the user for a Git repository.
|
|||
|
The choices offered by auto-completion will be the repositories
|
|||
|
under `magit-repo-dirs'. If `magit-repo-dirs' is nil or DIR is
|
|||
|
non-nil, then autocompletion will offer directory names."
|
|||
|
(if (and (not dir) magit-repo-dirs)
|
|||
|
(let* ((repos (magit-list-repos magit-repo-dirs))
|
|||
|
(reply (magit-completing-read "Git repository" repos)))
|
|||
|
(file-name-as-directory
|
|||
|
(or (cdr (assoc reply repos))
|
|||
|
(if (file-directory-p reply)
|
|||
|
(expand-file-name reply)
|
|||
|
(user-error "Not a repository or a directory: %s" reply)))))
|
|||
|
(file-name-as-directory
|
|||
|
(read-directory-name "Git repository: "
|
|||
|
(or (magit-get-top-dir) default-directory)))))
|
|||
|
|
|||
|
(defun magit-list-repos (dirs)
|
|||
|
(magit-list-repos-remove-conflicts
|
|||
|
(cl-loop for dir in dirs
|
|||
|
append (cl-loop for repo in
|
|||
|
(magit-list-repos* dir magit-repo-dirs-depth)
|
|||
|
collect (cons (file-name-nondirectory repo) repo)))))
|
|||
|
|
|||
|
(defun magit-list-repos* (dir depth)
|
|||
|
"Return a list of repos found in DIR, recursing up to DEPTH levels deep."
|
|||
|
(if (file-readable-p (expand-file-name ".git" dir))
|
|||
|
(list (expand-file-name dir))
|
|||
|
(and (> depth 0)
|
|||
|
(file-accessible-directory-p dir)
|
|||
|
(not (member (file-name-nondirectory dir)
|
|||
|
'(".." ".")))
|
|||
|
(cl-loop for entry in (directory-files dir t nil t)
|
|||
|
append (magit-list-repos* entry (1- depth))))))
|
|||
|
|
|||
|
(defun magit-list-repos-remove-conflicts (alist)
|
|||
|
(let ((dict (make-hash-table :test 'equal))
|
|||
|
(alist (delete-dups alist))
|
|||
|
(result nil))
|
|||
|
(dolist (a alist)
|
|||
|
(puthash (car a) (cons (cdr a) (gethash (car a) dict))
|
|||
|
dict))
|
|||
|
(maphash
|
|||
|
(lambda (key value)
|
|||
|
(if (= (length value) 1)
|
|||
|
(push (cons key (car value)) result)
|
|||
|
(let ((sub (magit-list-repos-remove-conflicts
|
|||
|
(mapcar
|
|||
|
(lambda (entry)
|
|||
|
(let ((dir (directory-file-name
|
|||
|
(substring entry 0 (- (length key))))))
|
|||
|
(cons (concat (file-name-nondirectory dir) "/" key)
|
|||
|
entry)))
|
|||
|
value))))
|
|||
|
(setq result (append result sub)))))
|
|||
|
dict)
|
|||
|
result))
|
|||
|
|
|||
|
;;;; External Tools
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-run-git-gui ()
|
|||
|
"Run `git gui' for the current git repository."
|
|||
|
(interactive)
|
|||
|
(let* ((default-directory (magit-get-top-dir)))
|
|||
|
(call-process magit-git-executable nil 0 nil "gui")))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-run-git-gui-blame (commit filename &optional linenum)
|
|||
|
"Run `git gui blame' on the given FILENAME and COMMIT.
|
|||
|
Interactively run it for the current file and the HEAD, with a
|
|||
|
prefix or when the current file cannot be determined let the user
|
|||
|
choose. When the current buffer is visiting FILENAME instruct
|
|||
|
blame to center around the line point is on."
|
|||
|
(interactive
|
|||
|
(let (revision filename)
|
|||
|
(when (or current-prefix-arg
|
|||
|
(not (setq revision "HEAD"
|
|||
|
filename (magit-buffer-file-name t))))
|
|||
|
(setq revision (magit-read-rev "Retrieve from revision" "HEAD")
|
|||
|
filename (magit-read-file-from-rev revision)))
|
|||
|
(list revision filename
|
|||
|
(and (equal filename
|
|||
|
(ignore-errors
|
|||
|
(magit-file-relative-name (buffer-file-name))))
|
|||
|
(line-number-at-pos)))))
|
|||
|
(let ((default-directory (magit-get-top-dir)))
|
|||
|
(apply #'call-process magit-git-executable nil 0 nil "gui" "blame"
|
|||
|
`(,@(and linenum (list (format "--line=%d" linenum)))
|
|||
|
,commit
|
|||
|
,filename))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun magit-run-gitk (arg)
|
|||
|
"Run Gitk for the current git repository.
|
|||
|
Without a prefix argument run `gitk --all', with
|
|||
|
a prefix argument run gitk without any arguments."
|
|||
|
(interactive "P")
|
|||
|
(apply #'call-process magit-gitk-executable nil 0 nil
|
|||
|
(if arg nil (list "--all"))))
|
|||
|
|
|||
|
;;;; Maintenance Tools
|
|||
|
|
|||
|
(defun magit-describe-item ()
|
|||
|
"Show information about the section at point.
|
|||
|
This command is intended for debugging purposes."
|
|||
|
(interactive)
|
|||
|
(let* ((section (magit-current-section))
|
|||
|
(head-beg (magit-section-beginning section))
|
|||
|
(body-beg (magit-section-content-beginning section)))
|
|||
|
(message "Section: %s %s%s-%s %S %S"
|
|||
|
(magit-section-type section)
|
|||
|
(marker-position (magit-section-beginning section))
|
|||
|
(if (and body-beg (not (= body-beg head-beg))
|
|||
|
(< body-beg (magit-section-end section)))
|
|||
|
(format "-%s" (marker-position body-beg))
|
|||
|
"")
|
|||
|
(marker-position (magit-section-end section))
|
|||
|
(magit-section-info section)
|
|||
|
(magit-section-context-type section))))
|
|||
|
|
|||
|
;;;; Magit Extensions
|
|||
|
|
|||
|
(defun magit-load-config-extensions ()
|
|||
|
"Try to load magit extensions that are defined at git config layer.
|
|||
|
This can be added to `magit-mode-hook' for example"
|
|||
|
(dolist (ext (magit-get-all "magit.extension"))
|
|||
|
(let ((sym (intern (format "magit-%s-mode" ext))))
|
|||
|
(when (and (fboundp sym)
|
|||
|
(not (eq sym 'magit-wip-save-mode)))
|
|||
|
(funcall sym 1)))))
|
|||
|
|
|||
|
;;; magit.el ends soon
|
|||
|
|
|||
|
(defconst magit-font-lock-keywords
|
|||
|
(eval-when-compile
|
|||
|
`((,(concat "(\\(" (regexp-opt
|
|||
|
'("magit-define-level-shower"
|
|||
|
"magit-define-section-jumper"))
|
|||
|
"\\)\\>[ \t'\(]*\\(\\sw+\\)?")
|
|||
|
(1 font-lock-keyword-face)
|
|||
|
(2 font-lock-function-name-face nil t))
|
|||
|
(,(concat "(" (regexp-opt
|
|||
|
'("magit-with-section"
|
|||
|
"magit-cmd-insert-section"
|
|||
|
"magit-git-insert-section"
|
|||
|
"magit-insert-line-section"
|
|||
|
"magit-section-action"
|
|||
|
"magit-section-case"
|
|||
|
"magit-add-action-clauses"
|
|||
|
"magit-bind-match-strings"
|
|||
|
"magit-visiting-file-item"
|
|||
|
"magit-tests--with-temp-dir"
|
|||
|
"magit-tests--with-temp-repo"
|
|||
|
"magit-tests--with-temp-clone") t)
|
|||
|
"\\>")
|
|||
|
. 1)))
|
|||
|
"Magit expressions to highlight in Emacs-Lisp mode.
|
|||
|
To highlight Magit expressions add something like this to your
|
|||
|
init file:
|
|||
|
|
|||
|
(require 'magit)
|
|||
|
(font-lock-add-keywords 'emacs-lisp-mode
|
|||
|
magit-font-lock-keywords)")
|
|||
|
|
|||
|
(defun magit-version (&optional noerror)
|
|||
|
"The version of Magit that you're using.\n\n\(fn)"
|
|||
|
(interactive)
|
|||
|
(let ((toplib (or load-file-name buffer-file-name)))
|
|||
|
(unless (and toplib
|
|||
|
(equal (file-name-nondirectory toplib) "magit.el"))
|
|||
|
(setq toplib (locate-library "magit.el")))
|
|||
|
(when toplib
|
|||
|
(let* ((dir (file-name-directory toplib))
|
|||
|
(static (expand-file-name "magit-version.el" dir))
|
|||
|
(gitdir (expand-file-name ".git" dir)))
|
|||
|
(cond ((file-exists-p gitdir)
|
|||
|
(setq magit-version
|
|||
|
(let ((default-directory dir))
|
|||
|
(magit-git-string "describe" "--tags" "--dirty")))
|
|||
|
(ignore-errors (delete-file static)))
|
|||
|
((file-exists-p static)
|
|||
|
(load-file static))
|
|||
|
((featurep 'package)
|
|||
|
(setq magit-version
|
|||
|
(or (ignore-errors ; < 24.3.50
|
|||
|
(package-version-join
|
|||
|
(package-desc-vers
|
|||
|
(cdr (assq 'magit package-alist)))))
|
|||
|
(ignore-errors ; >= 24.3.50
|
|||
|
(package-version-join
|
|||
|
(package-desc-version
|
|||
|
(cadr (assq 'magit package-alist)))))))))))
|
|||
|
(if (stringp magit-version)
|
|||
|
(when (called-interactively-p 'any)
|
|||
|
(message "magit-%s" magit-version))
|
|||
|
(if noerror
|
|||
|
(progn (setq magit-version 'error)
|
|||
|
(message "Cannot determine Magit's version"))
|
|||
|
(user-error "Cannot determine Magit's version")))
|
|||
|
magit-version))
|
|||
|
|
|||
|
(defvar magit-last-seen-setup-instructions "0")
|
|||
|
|
|||
|
(defun magit-maybe-show-setup-instructions ()
|
|||
|
(when (version< magit-last-seen-setup-instructions "1.4.0")
|
|||
|
(require 'warnings)
|
|||
|
(display-warning :warning "for magit-1.4.0
|
|||
|
|
|||
|
You have just updated to version 1.4.0 of Magit, and have to
|
|||
|
make a choice.
|
|||
|
|
|||
|
Before running Git, Magit by default reverts all unmodified
|
|||
|
buffers which visit files tracked in the current repository.
|
|||
|
This can potentially lead to dataloss so you might want to
|
|||
|
disable this by adding the following line to your init file:
|
|||
|
|
|||
|
(setq magit-auto-revert-mode nil)
|
|||
|
|
|||
|
The risk is not as high as it might seem. If snapshots on Melpa
|
|||
|
and Melpa-Stable had this enabled for a long time, so if you did
|
|||
|
not experience any dataloss in the past, then you should probably
|
|||
|
keep this enabled.
|
|||
|
|
|||
|
Keeping this mode enabled is only problematic if you, for
|
|||
|
example, use `git reset --hard REV' or `magit-reset-head-hard',
|
|||
|
and expect Emacs to preserve the old state of some file in a
|
|||
|
buffer. If you turn of this mode then file-visiting buffers and
|
|||
|
Magit buffer will no longer by in sync, which can be confusing
|
|||
|
and complicates many operations. Also note that it is possible
|
|||
|
to undo a buffer revert using `C-x u' (`undo').
|
|||
|
|
|||
|
Then you also have to add the following line to your init file
|
|||
|
to prevent this message from being shown again when you restart
|
|||
|
Emacs:
|
|||
|
|
|||
|
(setq magit-last-seen-setup-instructions \"1.4.0\")
|
|||
|
|
|||
|
You might also want to read the release notes:
|
|||
|
https://raw.githubusercontent.com/magit/magit/next/Documentation/RelNotes/1.4.0.txt"))
|
|||
|
(when (featurep 'magit-log-edit)
|
|||
|
(display-warning :error "magit-log-edit has to be removed
|
|||
|
|
|||
|
Magit is no longer compatible with the library `magit-log-edit',
|
|||
|
which was used in earlier releases. Please remove it, so that
|
|||
|
Magit can use the successor `git-commit-mode' without the
|
|||
|
obsolete library getting in the way. Then restart Emacs.
|
|||
|
|
|||
|
You might also want to read:
|
|||
|
https://github.com/magit/magit/wiki/Emacsclient")))
|
|||
|
|
|||
|
(add-hook 'after-init-hook #'magit-maybe-show-setup-instructions)
|
|||
|
|
|||
|
(provide 'magit)
|
|||
|
|
|||
|
(cl-eval-when (load eval)
|
|||
|
(magit-version t)
|
|||
|
(when after-init-time
|
|||
|
(magit-maybe-show-setup-instructions)))
|
|||
|
|
|||
|
(require 'magit-key-mode)
|
|||
|
|
|||
|
;; Local Variables:
|
|||
|
;; coding: utf-8
|
|||
|
;; indent-tabs-mode: nil
|
|||
|
;; End:
|
|||
|
;;; magit.el ends here
|