;;; magit.el --- control Git from Emacs
-;; Copyright (C) 2010 Aaron Culich.
-;; Copyright (C) 2010 Alan Falloon.
-;; Copyright (C) 2008, 2010 Alex Ott.
-;; Copyright (C) 2008, 2009, 2010 Alexey Voinov.
-;; Copyright (C) 2010 Ben Walton.
-;; Copyright (C) 2010 Chris Bernard.
-;; Copyright (C) 2010 Christian Kluge.
-;; Copyright (C) 2008 Daniel Farina.
-;; Copyright (C) 2010 David Abrahams.
-;; Copyright (C) 2009 David Wallin.
-;; Copyright (C) 2009, 2010 Hannu Koivisto.
-;; Copyright (C) 2009 Ian Eure.
-;; Copyright (C) 2009 Jesse Alama.
-;; Copyright (C) 2009 John Wiegley.
-;; Copyright (C) 2010 Leo.
-;; Copyright (C) 2008, 2009 Marcin Bachry.
-;; Copyright (C) 2008, 2009 Marius Vollmer.
-;; Copyright (C) 2010 Mark Hepburn.
-;; Copyright (C) 2010 Moritz Bunkus.
-;; Copyright (C) 2010 Nathan Weizenbaum.
-;; Copyright (C) 2010 Oscar Fuentes.
-;; Copyright (C) 2009 Pavel Holejsovsky.
-;; Copyright (C) 2011-2012 Peter J Weisberg
-;; Copyright (C) 2009, 2010 Phil Jackson.
-;; Copyright (C) 2010 Philip Weaver.
-;; Copyright (C) 2010 Ramkumar Ramachandra.
-;; Copyright (C) 2010 Remco van 't Veer.
-;; Copyright (C) 2009 René Stadler.
-;; Copyright (C) 2010 Robin Green.
-;; Copyright (C) 2010 Roger Crew.
-;; Copyright (C) 2009, 2010, 2011 Rémi Vanicat.
-;; Copyright (C) 2010 Sean Bryant.
-;; Copyright (C) 2009, 2011 Steve Purcell.
-;; Copyright (C) 2010 Timo Juhani Lindfors.
-;; Copyright (C) 2010, 2011 Yann Hodique.
-;; Copyright (C) 2010 Ævar Arnfjörð Bjarmason.
-;; Copyright (C) 2010 Óscar Fuentes.
-
-;; Original Author: Marius Vollmer <marius.vollmer@nokia.com>
-;; Former Maintainer: Phil Jackson <phil@shellarchive.co.uk>
-;; Maintenance Group: https://github.com/organizations/magit/teams/53130
-;; Currently composed of:
-;; - Phil Jackson
-;; - Peter J Weisberg
-;; - Yann Hodique
-;; - Rémi Vanicat
-;; Version: 1.2.0
-;; Keywords: tools
-
+;; Copyright (C) 2008-2014 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: ((emacs "23.2") (cl-lib "0.3") (git-commit-mode "0.14.0") (git-rebase-mode "0.14.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)
;;; Code:
-(eval-when-compile (require 'cl))
-(require 'log-edit)
-(require 'easymenu)
-(require 'diff-mode)
-
-;; Silences byte-compiler warnings
-(eval-and-compile
- (unless (fboundp 'declare-function) (defmacro declare-function (&rest args))))
-
-(eval-when-compile (require 'view))
-(declare-function view-mode 'view)
-(eval-when-compile (require 'iswitchb))
-(eval-when-compile (require 'ido))
-(eval-when-compile (require 'ediff))
+(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.
-;; Dummy to be used by the defcustoms when first loading the file.
-(eval-when (load eval)
- (defalias 'magit-set-variable-and-refresh 'set-default))
+;;;; Dependencies
-;;; Code:
-(defgroup magit nil
- "Controlling Git from Emacs."
- :prefix "magit-"
- :group 'tools)
+(when (version< emacs-version "23.2")
+ (error "Magit requires at least GNU Emacs 23.2"))
-(defcustom magit-git-executable "git"
- "The name of the Git executable."
- :group 'magit
- :type 'string)
+;; Users may choose to use `magit-log-edit' instead of the preferred
+;; `git-commit-mode', by simply putting it on the `load-path'. If
+;; it can be found there then it is loaded at the end of this file.
+(unless (locate-library "magit-log-edit")
+ (require 'git-commit-mode))
-(defcustom magit-gitk-executable (concat (file-name-directory magit-git-executable)
- "gitk")
- "The name of the Gitk executable."
- :group 'magit
- :type 'string)
+(require 'git-rebase-mode)
-(defcustom magit-git-standard-options '("--no-pager")
- "Standard options when running Git."
- :group 'magit
- :type '(repeat string))
+(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 'iswitchb)
+ (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 iswitchb-read-buffer 'iswitchb)
+(declare-function package-desc-vers 'package)
+(declare-function package-desc-version 'package)
+(declare-function package-version-join 'package)
+(declare-function view-mode 'view)
-(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 string))
+(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
-(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)
+(eval-and-compile
-(defcustom magit-set-upstream-on-push nil
- "Non-nil means that `magit-push' may use --set-upstream when pushing a branch.
-This only applies if the branch does not have an upstream set yet.
-Setting this to t will ask if --set-upstream should be used.
-Setting it to 'dontask will always use --set-upstream.
-Setting it to 'refuse will refuse to push unless a remote branch has already been set.
+ ;; 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)))))
+ )
+
+\f
+;;; Settings
+;;;; Custom Groups
---set-upstream is supported with git > 1.7.0"
- :group 'magit
- :type '(choice (const :tag "Never" nil)
- (const :tag "Ask" t)
- (const :tag "Refuse" refuse)
- (const :tag "Always" dontask)))
+(defgroup magit nil
+ "Controlling Git from Emacs."
+ :group 'tools)
-(defcustom magit-save-some-buffers t
- "Non-nil means that \\[magit-status] will save modified buffers before running.
-Setting this to t will ask which buffers to save, setting it to 'dontask will
-save all modified buffers without asking."
- :group 'magit
- :type '(choice (const :tag "Never" nil)
- (const :tag "Ask" t)
- (const :tag "Save without asking" dontask)))
+(defgroup magit-process nil
+ "Git and other external processes used by Magit."
+ :group 'magit)
-(defcustom magit-save-some-buffers-predicate
- 'magit-save-buffers-predicate-tree-only
- "A predicate function to decide whether to save a buffer.
+(defgroup magit-modes nil
+ "Modes provided by Magit."
+ :group 'magit)
-Used by function `magit-save-some-buffers' when the variable of
-the same name is non-nil."
+(defgroup magit-status nil
+ "Inspect and manipulate Git repositories."
+ :group 'magit-modes)
- :group 'magit
- :type '(radio (function-item magit-save-buffers-predicate-tree-only)
- (function-item magit-save-buffers-predicate-all)
- (function :tag "Other")))
+(defgroup magit-diff nil
+ "Inspect and manipulate Git diffs."
+ :group 'magit-modes)
-(defcustom magit-default-tracking-name-function
- 'magit-default-tracking-name-remote-plus-branch
- "Specifies the function to use to generate default tracking branch names
-when doing a \\[magit-checkout].
+(defgroup magit-commit nil
+ "Inspect and manipulate Git commits."
+ :group 'magit-modes)
-The default is magit-default-tracking-name-remote-plus-branch,
-which generates a tracking name of the form 'REMOTE-BRANCHNAME'."
- :group 'magit
- :type '(radio (function-item magit-default-tracking-name-remote-plus-branch)
- (function-item magit-default-tracking-name-branch-only)
- (function :tag "Other")))
+(defgroup magit-log nil
+ "Inspect and manipulate Git history."
+ :group 'magit-modes)
-(defcustom magit-commit-all-when-nothing-staged 'ask
- "Determines what \\[magit-log-edit] does when nothing is staged.
-Setting this to nil will make it do nothing, setting it to t will
-arrange things so that the actual commit command will use the \"--all\" option,
-setting it to 'ask will first ask for confirmation whether to do this,
-and setting it to 'ask-stage will cause all changes to be staged,
-after a confirmation."
- :group 'magit
- :type '(choice (const :tag "No" nil)
- (const :tag "Always" t)
- (const :tag "Ask" ask)
- (const :tag "Ask to stage everything" ask-stage)))
+(defgroup magit-extensions nil
+ "Extensions to Magit."
+ :group 'magit)
-(defcustom magit-commit-signoff nil
- "Add the \"Signed-off-by:\" line when committing."
+(defgroup magit-faces nil
+ "Faces used by Magit."
:group 'magit
- :type 'boolean)
+ :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)))))
+ (executable-find "git") "git")
+ "The Git executable used by Magit."
+ :group 'magit-process
+ :type 'string)
-(defcustom magit-sha1-abbrev-length 7
- "The number of digits to show when a sha1 is displayed in abbreviated form."
- :group 'magit
- :type 'integer)
+(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-log-cutoff-length 100
- "The maximum number of commits to show in the log and whazzup buffers."
- :group 'magit
- :type 'integer)
+(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)
-(defcustom magit-log-infinite-length 99999
- "Number of log used to show as maximum for `magit-log-cutoff-length'."
- :group 'magit
- :type 'integer)
+(defcustom magit-emacsclient-executable
+ (ignore-errors
+ (shell-quote-argument
+ (let ((version
+ (format "%s.%s" emacs-major-version emacs-minor-version)))
+ (or (and (eq system-type 'darwin)
+ (let ((exec-path
+ (list (expand-file-name "bin" invocation-directory))))
+ (executable-find "emacsclient")))
+ (executable-find (format "emacsclient%s" version))
+ (executable-find (format "emacsclient-%s" version))
+ (executable-find (format "emacsclient%s.exe" version))
+ (executable-find (format "emacsclient-%s.exe" version))
+ (executable-find (format "emacsclient%s" emacs-major-version))
+ (executable-find (format "emacsclient-%s" emacs-major-version))
+ (executable-find (format "emacsclient%s.exe" emacs-major-version))
+ (executable-find (format "emacsclient-%s.exe" emacs-major-version))
+ (executable-find "emacsclient")
+ (executable-find "emacsclient.exe")))))
+ "The Emacsclient executable.
+
+The default value is the full path to the emacsclient executable
+located in the same directory as the executable of the current
+Emacs instance. If the emacsclient cannot be located in that
+directory then the first executable found anywhere on the
+`exec-path' is used instead.
+
+If no executable can be located then nil becomes the default
+value, and some important Magit commands will fallback to an
+alternative code path. However `magit-interactive-rebase'
+will stop working at all."
+ :package-version '(magit . "2.0.0")
+ :group 'magit-process
+ :type '(choice (string :tag "Executable")
+ (const :tag "Don't use Emacsclient" nil)))
-(defcustom magit-log-auto-more nil
- "Insert more log entries automatically when moving past the last entry.
+(defcustom magit-process-connection-type (not (eq system-type 'cygwin))
+ "Connection type used for the git process.
-Only considered when moving past the last entry with
-`magit-goto-*-section' commands."
- :group 'magit
- :type 'boolean)
+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
+ :group 'magit-process
:type '(choice (const :tag "Never" -1)
(const :tag "Immediately" 0)
(integer :tag "After this many seconds")))
-(defcustom magit-revert-item-confirm t
- "Require acknowledgment before reverting an item."
- :group 'magit
+(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 . "2.0.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 . "2.0.0")
+ :group 'magit-process
+ :set-after '(magit-git-executable)
:type 'boolean)
-(defcustom magit-log-edit-confirm-cancellation nil
- "Require acknowledgment before canceling the log edit buffer."
+(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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.0")
:group 'magit
:type 'boolean)
-(defcustom magit-remote-ref-format 'branch-then-remote
- "What format to use for autocompleting refs, in pariticular for remotes.
-
-Autocompletion is used by functions like `magit-checkout',
-`magit-interactive-rebase' and others which offer branch name
-completion.
-
-The value 'name-then-remote means remotes will be of the
-form \"name (remote)\", while the value 'remote-slash-name
-means that they'll be of the form \"remote/name\". I.e. something that's
-listed as \"remotes/upstream/next\" by \"git branch -l -a\"
-will be \"upstream/next\"."
+(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 . "2.0.0")
:group 'magit
- :type '(choice (const :tag "name (remote)" branch-then-remote)
- (const :tag "remote/name" remote-slash-branch)))
-
-(defcustom magit-process-connection-type (not (eq system-type 'cygwin))
- "Connection type used for the git process.
+ :type 'boolean)
-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."
+(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 '(choice (const :tag "pipe" nil)
- (const :tag "pty" t)))
+ :type 'boolean)
-(defcustom magit-completing-read-function 'magit-builtin-completing-read
- "Function to be called when requesting input from the user."
+(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 . "2.1.0")
:group 'magit
- :type '(radio (function-item magit-iswitchb-completing-read)
- (function-item magit-ido-completing-read)
- (function-item magit-builtin-completing-read)
- (function :tag "Other")))
+ :type 'boolean)
-(defcustom magit-create-branch-behaviour 'at-head
- "Where magit will create a new branch if not supplied a branchname or ref.
+(defcustom magit-save-some-buffers t
+ "Whether certain commands save modified buffers before running.
-The value 'at-head means a new branch will be created at the tip
-of your current branch, while the value 'at-point means magit
-will try to find a valid reference at point..."
+nil don't save buffers.
+t ask which buffers to save.
+`dontask' save all buffers without asking."
:group 'magit
- :type '(choice (const :tag "at HEAD" at-head)
- (const :tag "at point" at-point)))
+ :type '(choice (const :tag "Never" nil)
+ (const :tag "Ask" t)
+ (const :tag "Save without asking" dontask)))
-(defcustom magit-status-buffer-switch-function 'pop-to-buffer
- "Function for `magit-status' to use for switching to the status buffer.
+(defcustom magit-save-some-buffers-predicate
+ 'magit-save-buffers-predicate-tree-only
+ "A predicate function to decide whether to save a buffer.
-The function is given one argument, the status buffer."
+Used by function `magit-save-some-buffers' when the variable of
+the same name is non-nil."
:group 'magit
- :type '(radio (function-item switch-to-buffer)
- (function-item pop-to-buffer)
+ :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
(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
- "Specifies where to highlight whitespace errors.
+ "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."
+`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))
- :set 'magit-set-variable-and-refresh)
+ (const :tag "In status buffer" status)))
(defcustom magit-highlight-trailing-whitespace t
- "Highlight whitespace at the end of a line in diffs.
+ "Whether to highlight whitespace at the end of a line in diffs.
Used only when `magit-highlight-whitespace' is non-nil."
:group 'magit
- :type 'boolean
- :set 'magit-set-variable-and-refresh)
+ :set 'magit-set-variable-and-refresh
+ :type 'boolean)
(defcustom magit-highlight-indentation nil
"Highlight the \"wrong\" indentation style.
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))))
- :set 'magit-set-variable-and-refresh)
+ (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 . "2.0.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 . "2.1.0")
+ :group 'magit
+ :group 'magit-faces
+ :set-after '(magit-item-highlight-face)
+ :type 'boolean)
-(defcustom magit-diff-refine-hunk nil
- "Show fine (word-granularity) differences within diff hunks.
+(define-obsolete-variable-alias 'magit-diff-use-overlays
+ 'magit-use-overlays "2.1.0")
-There are three possible settings:
+;;;;; 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-iswitchb-completing-read)
+ (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 . "2.0.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 string))
- nil means to never show fine differences
+(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)
- t means to only show fine differences for the currently
- selected diff hunk
+(defcustom magit-default-tracking-name-function
+ 'magit-default-tracking-name-remote-plus-branch
+ "Function used to generate default tracking branch names
+when doing a \\[magit-checkout].
- `all' means to always show fine differences for all displayed diff hunks"
+The default is `magit-default-tracking-name-remote-plus-branch',
+which generates a tracking name of the form \"REMOTE-BRANCHNAME\"."
:group 'magit
+ :type '(radio (function-item magit-default-tracking-name-remote-plus-branch)
+ (function-item magit-default-tracking-name-branch-only)
+ (function :tag "Other")))
+
+;;;;; Modes
+;;;;;; Common
+
+(defcustom magit-mode-hook nil
+ "Hook run when entering a Magit mode derived mode."
+ :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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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-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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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)
-(defvar magit-current-indentation nil
- "Indentation highlight used in the current buffer.
-This is calculated from `magit-highlight-indentation'.")
-(make-variable-buffer-local 'magit-current-indentation)
+;;;;;; Commit
-(defgroup magit-faces nil
- "Customize the appearance of Magit."
- :prefix "magit-"
- :group 'faces
- :group 'magit)
+(defcustom magit-commit-ask-to-stage t
+ "Whether to ask to stage everything when committing and nothing is staged."
+ :package-version '(magit . "2.0.0")
+ :group 'magit-commit
+ :type 'boolean)
-(defface magit-header
- '((t :inherit header-line))
- "Face for generic header lines.
+(defcustom magit-commit-extend-override-date nil
+ "Whether using `magit-commit-extend' changes the committer date."
+ :package-version '(magit . "2.0.0")
+ :group 'magit-commit
+ :type 'boolean)
-Many Magit faces inherit from this one by default."
- :group 'magit-faces)
+(defcustom magit-commit-reword-override-date nil
+ "Whether using `magit-commit-reword' changes the committer date."
+ :package-version '(magit . "2.0.0")
+ :group 'magit-commit
+ :type 'boolean)
-(defface magit-section-title
- '((t :inherit magit-header))
- "Face for section titles."
- :group 'magit-faces)
+(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 . "2.0.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 . "2.0.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
-(defface magit-branch
- '((t :inherit magit-header))
- "Face for the current branch."
- :group 'magit-faces)
+(defcustom magit-log-auto-more nil
+ "Insert more log entries automatically when moving past the last entry.
-(defface magit-diff-file-header
- '((t :inherit diff-file-header))
- "Face for diff file header lines."
- :group 'magit-faces)
+Only considered when moving past the last entry with
+`magit-goto-*-section' commands."
+ :group 'magit-log
+ :type 'boolean)
-(defface magit-diff-hunk-header
- '((t :inherit diff-hunk-header))
- "Face for diff hunk header lines."
- :group 'magit-faces)
+(defcustom magit-log-cutoff-length 100
+ "The maximum number of commits to show in the log and whazzup buffers."
+ :group 'magit-log
+ :type 'integer)
-(defface magit-diff-add
- '((t :inherit diff-added))
- "Face for lines in a diff that have been added."
- :group 'magit-faces)
+(defcustom magit-log-infinite-length 99999
+ "Number of log used to show as maximum for `magit-log-cutoff-length'."
+ :group 'magit-log
+ :type 'integer)
-(defface magit-diff-none
- '((t :inherit diff-context))
- "Face for lines in a diff that are unchanged."
- :group 'magit-faces)
+(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 . "2.1.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 . "2.1.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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 . "2.0.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")
"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 highlight))
+ '((t :inherit secondary-selection))
"Face for highlighting the current item."
:group 'magit-faces)
(defface magit-item-mark
- '((t :inherit secondary-selection))
+ '((t :inherit highlight))
"Face for highlighting marked item."
:group 'magit-faces)
"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
"Face for highlighting whitespace errors in Magit diffs."
:group 'magit-faces)
-(defvar magit-custom-options '()
- "List of custom options to pass to Git.
-Do not customize this (used in the `magit-key-mode' implementation).")
+(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)
-(defvar magit-read-rev-history nil
- "The history of inputs to `magit-read-rev'.")
+(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)
-(defvar magit-buffer-internal nil
- "Track associated *magit* buffers.
-Do not customize this (used in the `magit-log-edit-mode' implementation
-to switch back to the *magit* buffer associated with a given commit
-operation after commit).")
+(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)
-(defvar magit-back-navigation-history nil
- "History items that will be visited by successively going \"back\".")
-(make-variable-buffer-local 'magit-back-navigation-history)
-(put 'magit-back-navigation-history 'permanent-local t)
+(defface magit-signature-bad
+ '((t :foreground "red"))
+ "Face for bad signatures."
+ :group 'magit-faces)
-(defvar magit-forward-navigation-history nil
- "History items that will be visited by successively going \"forward\".")
-(make-variable-buffer-local 'magit-forward-navigation-history)
-(put 'magit-forward-navigation-history 'permanent-local t)
+(defface magit-signature-untrusted
+ '((t :foreground "cyan"))
+ "Face for good untrusted signatures."
+ :group 'magit-faces)
-(defvar magit-omit-untracked-dir-contents nil
- "When non-nil magit will only list an untracked directory, not its contents.")
+(defface magit-signature-none
+ '((t :inherit magit-log-message))
+ "Face for unsigned commits."
+ :group 'magit-faces)
-(defvar magit-tmp-buffer-name " *magit-tmp*")
-(defface magit-log-head-label-local
+(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"
:box t
:background "Grey13"
:foreground "LightSkyBlue1"))
- "Face for local branch head labels shown in log buffer."
+ "Face for reflog subject labels shown in reflog buffer."
:group 'magit-faces)
-(defface magit-log-head-label-default
+(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 unknown ref labels shown in log buffer."
+ "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 "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-describe-item)
- (define-key map (kbd "!") 'magit-key-mode-popup-running)
+ (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-no-option)
+ (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)
- (define-key map (kbd "R") 'magit-rebase-step)
- (define-key map (kbd "t") 'magit-key-mode-popup-tagging)
- (define-key map (kbd "r") 'magit-key-mode-popup-rewriting)
- (define-key map (kbd "P") 'magit-key-mode-popup-pushing)
- (define-key map (kbd "f") 'magit-key-mode-popup-fetching)
- (define-key map (kbd "b") 'magit-key-mode-popup-branching)
- (define-key map (kbd "M") 'magit-key-mode-popup-remoting)
- (define-key map (kbd "B") 'magit-key-mode-popup-bisecting)
- (define-key map (kbd "F") 'magit-key-mode-popup-pulling)
- (define-key map (kbd "l") 'magit-key-mode-popup-logging)
- (define-key map (kbd "$") 'magit-display-process)
- (define-key map (kbd "c") 'magit-log-edit)
+ (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 "q") 'magit-quit-window)
- (define-key map (kbd "m") 'magit-key-mode-popup-merging)
+ (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 "-") '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-toggle-diff-refine-hunk)
- map))
+ (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 "M-g") 'magit-jump-to-diffstats)
+ (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)))
- (define-key map (kbd "C-c C-b") 'magit-show-commit-backward)
- (define-key map (kbd "C-c C-f") 'magit-show-commit-forward)
- map))
+ (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)
+ 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 "S") 'magit-stage-all)
(define-key map (kbd "u") 'magit-unstage-item)
- (define-key map (kbd "U") 'magit-unstage-all)
(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-add-log)
- (define-key map (kbd "X") 'magit-reset-working-tree)
- (define-key map (kbd "z") 'magit-key-mode-popup-stashing)
- map))
+ (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)
- map))
+ (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)
+ 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))
+ 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-move-item)
+ (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))
-
-(defvar magit-bug-report-url
- "http://github.com/magit/magit/issues")
-
-(defconst magit-version "1.2.0"
- "The version of Magit that you're using.")
-
-(defun magit-bug-report (str)
- "Asks the user to submit a bug report about the error described in STR."
-;; XXX - should propose more information to be included.
- (message (concat
- "Unknown error: %s\n"
- "Please, with as much information as possible, file a bug at\n"
- "%s\n"
- "You are using Magit version %s.")
- str magit-bug-report-url magit-version))
-
-(defun magit-buffer-switch (buf)
- (if (string-match "magit" (buffer-name))
- (switch-to-buffer buf)
- (pop-to-buffer buf)))
-
-;;; Macros
-
-(defmacro magit-with-refresh (&rest body)
- (declare (indent 0))
- `(magit-refresh-wrapper (lambda () ,@body)))
-
-;;; Git features
-
-(defvar magit-have-graph 'unset)
-(defvar magit-have-decorate 'unset)
-(defvar magit-have-abbrev 'unset)
-(make-variable-buffer-local 'magit-have-graph)
-(put 'magit-have-graph 'permanent-local t)
-(make-variable-buffer-local 'magit-have-decorate)
-(put 'magit-have-decorate 'permanent-local t)
-(make-variable-buffer-local 'magit-have-abbrev)
-(put 'magit-have-abbrev 'permanent-local t)
-
-(defun magit-configure-have-graph ()
- (if (eq magit-have-graph 'unset)
- (let ((res (magit-git-exit-code "log" "--graph" "--max-count=0")))
- (setq magit-have-graph (eq res 0)))))
-
-(defun magit-configure-have-decorate ()
- (if (eq magit-have-decorate 'unset)
- (let ((res (magit-git-exit-code "log" "--decorate=full" "--max-count=0")))
- (setq magit-have-decorate (eq res 0)))))
-
-(defun magit-configure-have-abbrev ()
- (if (eq magit-have-abbrev 'unset)
- (let ((res (magit-git-exit-code "log" "--no-abbrev-commit" "--max-count=0")))
- (setq magit-have-abbrev (eq res 0)))))
-
-;;; Compatibilities
-
-(eval-and-compile
- (defun magit-max-args-internal (function)
- "Returns the maximum number of arguments accepted by FUNCTION."
- (if (symbolp function)
- (setq function (symbol-function function)))
- (if (subrp function)
- (let ((max (cdr (subr-arity function))))
- (if (eq 'many max)
- most-positive-fixnum
- max))
- (if (eq 'macro (car-safe function))
- (setq function (cdr function)))
- (let ((arglist (if (byte-code-function-p function)
- (aref function 0)
- (second function))))
- (if (memq '&rest arglist)
- most-positive-fixnum
- (length (remq '&optional arglist))))))
-
- (if (functionp 'start-file-process)
- (defalias 'magit-start-process 'start-file-process)
- (defalias 'magit-start-process 'start-process))
-
- (unless (fboundp 'string-match-p)
- (defun string-match-p (regexp string &optional start)
- "Same as `string-match' except this function does not
-change the match data."
- (let ((inhibit-changing-match-data t))
- (string-match regexp string start))))
-
- (if (fboundp 'with-silent-modifications)
- (defalias 'magit-with-silent-modifications 'with-silent-modifications)
- (defmacro magit-with-silent-modifications (&rest body)
- "Execute body without changing `buffer-modified-p'. Also, do not
-record undo information."
- `(set-buffer-modified-p
- (prog1 (buffer-modified-p)
- (let ((buffer-undo-list t)
- before-change-functions
- after-change-functions)
- ,@body)))))
-
- (if (>= (magit-max-args-internal 'delete-directory) 2)
- (defalias 'magit-delete-directory 'delete-directory)
- (defun magit-delete-directory (directory &optional recursive)
- "Deletes a directory named DIRECTORY. If RECURSIVE is non-nil,
-recursively delete all of DIRECTORY's contents as well.
-
-Does not follow symlinks."
- (if (or (file-symlink-p directory)
- (not (file-directory-p directory)))
- (delete-file directory)
- (if recursive
- ;; `directory-files-no-dot-files-regex' borrowed from Emacs 23
- (dolist (file (directory-files directory 'full "\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*"))
- (magit-delete-directory file recursive)))
- (delete-directory directory)))))
-
-;;; Utilities
-
-(defun magit-set-variable-and-refresh (symbol value)
- "Set SYMBOL to VALUE and call `magit-refresh-all'"
- (set-default symbol value)
- (magit-refresh-all))
-
-(defun magit-iswitchb-completing-read (prompt choices &optional predicate require-match
- initial-input hist def)
- "iswitchb-based completing-read almost-replacement."
- (require 'iswitchb)
- (let ((iswitchb-make-buflist-hook
- (lambda ()
- (setq iswitchb-temp-buflist (if (consp (first choices))
- (mapcar #'car choices)
- choices)))))
- (iswitchb-read-buffer prompt (or initial-input def) require-match)))
-
-(defun magit-ido-completing-read (prompt choices &optional predicate require-match initial-input hist def)
- "ido-based completing-read almost-replacement."
- (require 'ido)
- (let ((selected (ido-completing-read prompt (if (consp (first choices))
- (mapcar #'car choices)
- choices)
- predicate require-match initial-input hist def)))
- (if (consp (first choices))
- (or (cdr (assoc selected choices))
- selected)
- selected)))
-
-(defun magit-builtin-completing-read (prompt choices &optional predicate require-match
- initial-input hist def)
- "Magit wrapper for standard `completing-read' function."
- (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))
-
-(defun magit-completing-read (prompt choices &optional predicate require-match
- initial-input hist def)
- (funcall magit-completing-read-function prompt choices predicate require-match
- initial-input hist def))
-
-(defun magit-use-region-p ()
- (if (fboundp 'use-region-p)
- (use-region-p)
- (and transient-mark-mode mark-active)))
-
-(defun magit-goto-line (line)
- "Like `goto-line' but doesn't set the mark."
- (save-restriction
- (widen)
- (goto-char 1)
- (forward-line (1- line))))
-
-(defun magit-trim-line (str)
- (if (string= str "")
- nil
- (if (equal (elt str (- (length str) 1)) ?\n)
- (substring str 0 (- (length str) 1))
- str)))
-
-(defun magit-split-lines (str)
- (if (string= str "")
- nil
- (let ((lines (nreverse (split-string str "\n"))))
- (if (string= (car lines) "")
- (setq lines (cdr lines)))
- (nreverse lines))))
-
-(defun magit-git-insert (args)
- (insert (magit-git-output args)))
-
-(defun magit-git-output (args)
- (magit-cmd-output magit-git-executable (append magit-git-standard-options args)))
-
-(defun magit-cmd-insert (cmd args)
- (insert (magit-cmd-output cmd args)))
-
-(defun magit-cmd-output (cmd args)
- (let ((cmd-output (with-output-to-string
- (with-current-buffer standard-output
- (apply #'process-file
- cmd
- nil (list t nil) nil
- args)))))
- (replace-regexp-in-string "\e\\[.*?m" "" cmd-output)))
-
-(defun magit-git-string (&rest args)
- (magit-trim-line (magit-git-output args)))
-
-(defun magit-git-lines (&rest args)
- (magit-split-lines (magit-git-output args)))
-
-(defun magit-git-exit-code (&rest args)
- (apply #'process-file magit-git-executable nil nil nil
- (append magit-git-standard-options args)))
-
-(defun magit-file-lines (file)
- (when (file-exists-p file)
- (with-temp-buffer
- (insert-file-contents file)
- (let ((rev (nreverse (split-string (buffer-string) "\n"))))
- (nreverse (if (equal (car rev) "")
- (cdr rev)
- rev))))))
-
-(defun magit-write-file-lines (file lines)
- (with-temp-buffer
- (dolist (l lines)
- (insert l "\n"))
- (write-file file)))
-
-(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."
- (equal (magit-git-string "config" "--bool" (mapconcat 'identity keys "."))
- "true"))
+ map)
+ "Keymap for `magit-branch-manager-mode'.")
-(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 "."))))
+(defvar magit-process-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map magit-mode-map)
+ map)
+ "Keymap for `magit-process-mode'.")
-(defun magit-remove-conflicts (alist)
- (let ((dict (make-hash-table :test 'equal))
- (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-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))
+(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)
-(defun magit-git-repo-p (dir)
- (file-exists-p (expand-file-name ".git" dir)))
+(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]))
-(defun magit-git-dir ()
- "Returns the .git directory for the current repository."
- (concat (expand-file-name (magit-git-string "rev-parse" "--git-dir")) "/"))
+\f
+;;; Utilities (1)
+;;;; Minibuffer Input
-(defun magit-no-commit-p ()
- "Return non-nil if there is no commit in the current git repository."
- (not (magit-git-string
- "rev-list" "HEAD" "--max-count=1")))
+(defun magit-iswitchb-completing-read
+ (prompt choices &optional predicate require-match initial-input hist def)
+ "iswitchb-based completing-read almost-replacement."
+ (require 'iswitchb)
+ (let ((iswitchb-make-buflist-hook
+ (lambda ()
+ (setq iswitchb-temp-buflist (if (consp (car choices))
+ (mapcar #'car choices)
+ choices)))))
+ (iswitchb-read-buffer prompt (or initial-input def) require-match)))
-(defun magit-list-repos* (dir level)
- (if (magit-git-repo-p dir)
- (list dir)
- (apply #'append
- (mapcar (lambda (entry)
- (unless (or (string= (substring entry -3) "/..")
- (string= (substring entry -2) "/."))
- (magit-list-repos* entry (+ level 1))))
- (and (file-directory-p dir)
- (< level magit-repo-dirs-depth)
- (directory-files dir t nil t))))))
+(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 t 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-list-repos (dirs)
- (magit-remove-conflicts
- (apply #'append
- (mapcar (lambda (dir)
- (mapcar #'(lambda (repo)
- (cons (file-name-nondirectory repo)
- repo))
- (magit-list-repos* dir 0)))
- dirs))))
-
-(defun magit-get-top-dir (cwd)
- (let ((cwd (expand-file-name (file-truename cwd))))
- (when (file-directory-p cwd)
- (let* ((default-directory (file-name-as-directory cwd))
- (cdup (magit-git-string "rev-parse" "--show-cdup")))
- (when cdup
- (file-name-as-directory (expand-file-name cdup cwd)))))))
+(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"))
- (pos (and head (string-match "^refs/heads/" head))))
- (if pos
- (substring head 11)
- nil)))
+ (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."
+\"origin\" exists, return that. Otherwise, return nil."
(let ((remote (or (and branch (magit-get "branch" branch "remote"))
(and (magit-get "remote" "origin" "url") "origin"))))
- (if (string= remote "") nil remote)))
+ (unless (string= remote "")
+ remote)))
(defun magit-get-current-remote ()
"Return the name of the remote for the current branch.
(magit-get-remote (magit-get-current-branch)))
(defun magit-ref-exists-p (ref)
- (= (magit-git-exit-code "show-ref" "--verify" ref) 0))
-
-(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-nill, 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)
- (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)
- default-directory)))))
+ (magit-git-success "show-ref" "--verify" ref))
(defun magit-rev-parse (ref)
"Return the SHA hash for REF."
(defun magit-ref-ambiguous-p (ref)
"Return whether or not REF is ambiguous."
- ;; If REF is ambiguous, rev-parse just prints errors,
- ;; so magit-git-string returns nil.
+ ;; 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."
+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,
(setq rev plain-name))))
rev)))
-(defun magit-highlight-line-whitespace ()
- (when (and magit-highlight-whitespace
- (or (derived-mode-p 'magit-status-mode)
- (not (eq magit-highlight-whitespace 'status))))
- (if (and magit-highlight-trailing-whitespace
- (looking-at "^[-+].*?\\([ \t]+\\)$"))
- (overlay-put (make-overlay (match-beginning 1) (match-end 1))
- 'face 'magit-whitespace-warning-face))
- (if (or (and (eq magit-current-indentation 'tabs)
- (looking-at "^[-+]\\( *\t[ \t]*\\)"))
- (and (integerp magit-current-indentation)
- (looking-at (format "^[-+]\\([ \t]* \\{%s,\\}[ \t]*\\)"
- magit-current-indentation))))
- (overlay-put (make-overlay (match-beginning 1) (match-end 1))
- 'face 'magit-whitespace-warning-face))))
-
-(defun magit-put-line-property (prop val)
- (put-text-property (line-beginning-position) (line-beginning-position 2)
- prop val))
-
-(defun magit-format-commit (commit format)
- (magit-git-string "log" "--max-count=1"
- (concat "--pretty=format:" format)
- commit))
-
-(defun magit-current-line ()
- (buffer-substring-no-properties (line-beginning-position)
- (line-end-position)))
-
-(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-file-uptodate-p (file)
- (eq (magit-git-exit-code "diff" "--quiet" "--" file) 0))
+ (magit-git-success "diff" "--quiet" "--" file))
(defun magit-anything-staged-p ()
- (not (eq (magit-git-exit-code "diff" "--quiet" "--cached") 0)))
+ (magit-git-failure "diff" "--quiet" "--cached"))
-(defun magit-everything-clean-p ()
- (and (not (magit-anything-staged-p))
- (eq (magit-git-exit-code "diff" "--quiet") 0)))
+(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))))
-;; XXX - let the user choose the parent
-
-(defun magit-choose-parent-id (commit op)
- (let* ((parents (magit-commit-parents commit)))
- (if (> (length parents) 1)
- (error "Can't %s merge commits" op)
- nil)))
-
-;;; Revisions and ranges
-
-(defvar magit-current-range nil
- "The range described by the current buffer.
-This is only non-nil in diff and log buffers.
-
-This has three possible (non-nil) forms. If it's a string REF or
-a singleton list (REF), then the range is from REF to the current
-working directory state (or HEAD in a log buffer). If it's a
-pair (START . END), then the range is START..END.")
-(make-variable-buffer-local 'magit-current-range)
-
-(defun magit-list-interesting-refs (&optional uninteresting)
- "Return interesting references as given by `git show-ref'.
-Removes references matching UNINTERESTING from the
-results. UNINTERESTING can be either a function taking a single
-argument or a list of strings used as regexps."
- (let ((refs ()))
- (dolist (line (magit-git-lines "show-ref"))
- (if (string-match "[^ ]+ +\\(.*\\)" line)
- (let ((ref (match-string 1 line)))
- (cond ((and (functionp uninteresting)
- (funcall uninteresting ref)))
- ((and (not (functionp uninteresting))
- (loop for i in uninteresting thereis (string-match i ref))))
- (t
- (let ((fmt-ref (magit-format-ref ref)))
- (when fmt-ref
- (push (cons fmt-ref
- (replace-regexp-in-string "^refs/heads/"
- "" ref))
- refs))))))))
- (nreverse refs)))
+(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)
- "Convert fully-specified ref REF into its displayable form
-according to `magit-remote-ref-format'"
- (cond
- ((null ref)
- nil)
- ((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))
- (format "%s/%s"
- (match-string 1 ref)
- (match-string 2 ref))))))
-
-(defun magit-tree-contents (treeish)
- "Returns a list of all files under TREEISH. TREEISH can be a tree,
-a commit, or any reference to one of those."
- (let ((return-value nil))
- (with-temp-buffer
- (magit-git-insert (list "ls-tree" "-r" treeish))
- (if (eql 0 (buffer-size))
- (error "%s is not a commit or tree." treeish))
- (goto-char (point-min))
- (while (search-forward-regexp "\t\\(.*\\)" nil 'noerror)
- (push (match-string 1) return-value)))
- return-value))
-
-(defvar magit-uninteresting-refs '("refs/remotes/\\([^/]+\\)/HEAD$" "refs/stash"))
-
-(defun magit-read-file-from-rev (revision)
- (magit-completing-read (format "Retrieve file from %s: " revision)
- (magit-tree-contents revision)
- nil
- 'require-match
- nil
- 'magit-read-file-hist
- (if buffer-file-name
- (let ((topdir-length (length (magit-get-top-dir default-directory))))
- (substring (buffer-file-name) topdir-length)))))
-
-(defun magit-read-rev (prompt &optional default uninteresting)
- (let* ((interesting-refs (magit-list-interesting-refs
- (or uninteresting magit-uninteresting-refs)))
- (reply (magit-completing-read (concat prompt ": ") interesting-refs
- nil nil nil 'magit-read-rev-history default))
- (rev (or (cdr (assoc reply interesting-refs)) reply)))
- (if (string= rev "")
- nil
- rev)))
+ (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"))))
-(defun magit-read-rev-range (op &optional def-beg def-end)
- (let ((beg (magit-read-rev (format "%s start" op)
- def-beg)))
- (if (not beg)
- nil
- (save-match-data
- (if (string-match "^\\(.+\\)\\.\\.\\(.+\\)$" beg)
- (cons (match-string 1 beg) (match-string 2 beg))
- (let ((end (magit-read-rev (format "%s end" op) def-end)))
- (cons beg end)))))))
-
-(defun magit-rev-to-git (rev)
- (or rev
- (error "No revision specified"))
- (if (string= rev ".")
- (magit-marked-commit)
+(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-rev-range-to-git (range)
- (or range
- (error "No revision range specified"))
- (if (stringp range)
- range
- (if (cdr range)
- (format "%s..%s"
- (magit-rev-to-git (car range))
- (magit-rev-to-git (cdr range)))
- (format "%s" (magit-rev-to-git (car range))))))
-
-(defun magit-rev-describe (rev)
- (or rev
- (error "No revision specified"))
- (if (string= rev ".")
- "mark"
- (magit-name-rev rev)))
-
-(defun magit-rev-range-describe (range things)
- (or range
- (error "No revision range specified"))
- (if (stringp range)
- (format "%s in %s" things range)
- (if (cdr range)
- (format "%s from %s to %s" things
- (magit-rev-describe (car range))
- (magit-rev-describe (cdr range)))
- (format "%s at %s" things (magit-rev-describe (car range))))))
-
-(defun magit-default-rev (&optional no-trim)
- (or (magit-name-rev (magit-commit-at-point t) no-trim)
- (let ((branch (magit-guess-branch)))
- (if branch
- (if (string-match "^refs/\\(.*\\)" branch)
- (match-string 1 branch)
- branch)))))
-
-(defun magit-read-remote (&optional prompt def)
- "Read the name of a remote.
-PROMPT is used as the prompt, and defaults to \"Remote\".
-DEF is the default value."
- (let* ((prompt (or prompt "Remote"))
- (def (or def (magit-guess-remote)))
- (remotes (magit-git-lines "remote"))
-
- (reply (magit-completing-read (concat prompt ": ") remotes
- nil nil nil nil def)))
- (if (string= reply "") nil reply)))
-
-(defun magit-read-remote-branch (remote &optional prompt default)
- (let* ((prompt (or prompt (format "Remote branch (in %s)" remote)))
- (branches (delete nil
- (mapcar
- (lambda (b)
- (and (not (string-match " -> " b))
- (string-match (format "^ *%s/\\(.*\\)$"
- (regexp-quote remote)) b)
- (match-string 1 b)))
- (magit-git-lines "branch" "-r"))))
- (reply (magit-completing-read (concat prompt ": ") branches
- nil nil nil nil default)))
- (if (string= reply "") nil reply)))
-
-;;; Sections
-
-;; A buffer in magit-mode is organized into hierarchical sections.
-;; These sections are used for navigation and for hiding parts of the
-;; buffer.
-;;
-;; Most sections also represent the objects that Magit works with,
-;; such as files, diffs, hunks, commits, etc. The 'type' of a section
-;; identifies what kind of object it represents (if any), and the
-;; parent and grand-parent, etc provide the context.
-
-(defstruct magit-section
- parent title beginning end children hidden type info
- needs-refresh-on-show)
-
-(defvar magit-top-section nil
- "The top section of the current buffer.")
-(make-variable-buffer-local 'magit-top-section)
-(put 'magit-top-section 'permanent-local t)
-
-(defvar magit-old-top-section nil)
-
-(defvar magit-section-hidden-default nil)
-
-(defun magit-new-section (title type)
- "Create a new section with title TITLE and type TYPE in current buffer.
-
-If not `magit-top-section' exist, the new section will be the new top-section
-otherwise, the new-section will be a child of the current top-section.
-
-If TYPE is nil, the section won't be highlighted."
- (let* ((s (make-magit-section :parent magit-top-section
- :title title
- :type type
- :hidden magit-section-hidden-default))
- (old (and magit-old-top-section
- (magit-find-section (magit-section-path s)
- magit-old-top-section))))
- (if magit-top-section
- (push s (magit-section-children magit-top-section))
- (setq magit-top-section s))
- (if old
- (setf (magit-section-hidden s) (magit-section-hidden old)))
- s))
-
-(defun magit-cancel-section (section)
- "Delete the section SECTION."
- (delete-region (magit-section-beginning section)
- (magit-section-end section))
- (let ((parent (magit-section-parent section)))
- (if parent
- (setf (magit-section-children parent)
- (delq section (magit-section-children parent)))
- (setq magit-top-section nil))))
+(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)))))
-(defmacro magit-with-section (title type &rest body)
- "Create a new section of title TITLE and type TYPE and evaluate BODY there.
+(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)
+ (cons (match-string 1 beg) (match-string 2 beg))
+ (let ((end (magit-read-rev (format "%s end" op) def-end nil t)))
+ (if end (cons 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)))
-Sections created inside BODY will become children of the new
-section. BODY must leave point at the end of the created section.
+(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)))
-If TYPE is nil, the section won't be highlighted."
+(defmacro magit-cmd-insert-section (arglist washer program &rest args)
+ "\n\n(fn (TYPE &optional HEADING) WASHER PROGRAM &rest ARGS)"
(declare (indent 2))
- (let ((s (make-symbol "*section*")))
- `(let* ((,s (magit-new-section ,title ,type))
- (magit-top-section ,s))
- (setf (magit-section-beginning ,s) (point))
- ,@body
- (setf (magit-section-end ,s) (point))
- (setf (magit-section-children ,s)
- (nreverse (magit-section-children ,s)))
- ,s)))
+ `(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))
-(defun magit-set-section (title type start end)
- "Create a new section of title TITLE and type TYPE with specified start and
-end positions."
- (let ((section (magit-new-section title type)))
- (setf (magit-section-beginning section) start)
- (setf (magit-section-end section) end)
- section))
-
-(defun magit-set-section-info (info &optional section)
- (setf (magit-section-info (or section magit-top-section)) info))
-
-(defun magit-set-section-needs-refresh-on-show (flag &optional section)
- (setf (magit-section-needs-refresh-on-show
- (or section magit-top-section))
- flag))
-
-(defmacro magit-create-buffer-sections (&rest body)
- "Empty current buffer of text and Magit's sections, and then evaluate BODY."
- (declare (indent 0))
- `(let ((inhibit-read-only t))
- (erase-buffer)
- (let ((magit-old-top-section magit-top-section))
- (setq magit-top-section nil)
- ,@body
- (when (null magit-top-section)
- (magit-with-section 'top nil
- (insert "(empty)\n")))
- (magit-propertize-section magit-top-section)
- (magit-section-set-hidden magit-top-section
- (magit-section-hidden magit-top-section)))))
-
-(defun magit-propertize-section (section)
- "Add text-property needed for SECTION."
- (put-text-property (magit-section-beginning section)
- (magit-section-end section)
- 'magit-section section)
- (dolist (s (magit-section-children section))
- (magit-propertize-section s)))
+(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."
top
(let ((secs (magit-section-children top)))
(while (and secs (not (equal (car path)
- (magit-section-title (car secs)))))
+ (magit-section-info (car secs)))))
(setq secs (cdr secs)))
- (and (car secs)
- (magit-find-section (cdr path) (car secs))))))
+ (when (car secs)
+ (magit-find-section (cdr path) (car secs))))))
(defun magit-section-path (section)
"Return the path of SECTION."
- (if (not (magit-section-parent section))
- '()
- (append (magit-section-path (magit-section-parent section))
- (list (magit-section-title 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-top-section)))
+ (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
(defun magit-find-section-before (pos)
"Return the last section that begins before POS."
(let ((section (magit-find-section-at pos)))
- (do* ((current (or (magit-section-parent section)
- section)
- next)
- (next (if (not (magit-section-hidden current))
- (magit-find-section-before* pos (magit-section-children current)))
- (if (not (magit-section-hidden current))
- (magit-find-section-before* pos (magit-section-children current)))))
+ (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)
(defun magit-find-section-at (pos)
"Return the Magit section at POS."
(or (get-text-property pos 'magit-section)
- magit-top-section))
-
-(defun magit-insert-section (section-title-and-type
- buffer-title washer cmd &rest args)
- "Run CMD and put its result in a new section.
-
-SECTION-TITLE-AND-TYPE is either a string that is the title of the section
-or (TITLE . TYPE) where TITLE is the title of the section and TYPE is its type.
-
-If there is no type, or if type is nil, the section won't be highlighted.
-
-BUFFER-TITLE is the inserted title of the section
-
-WASHER is a function that will be run after CMD.
-The buffer will be narrowed to the inserted text.
-It should add sectioning as needed for Magit interaction.
-
-CMD is an external command that will be run with ARGS as arguments."
- (let* ((body-beg nil)
- (section-title (if (consp section-title-and-type)
- (car section-title-and-type)
- section-title-and-type))
- (section-type (if (consp section-title-and-type)
- (cdr section-title-and-type)
- nil))
- (section
- (magit-with-section section-title section-type
- (if buffer-title
- (insert (propertize buffer-title 'face 'magit-section-title)
- "\n"))
- (setq body-beg (point))
- (magit-cmd-insert cmd args)
- (if (not (eq (char-before) ?\n))
- (insert "\n"))
- (if washer
- (save-restriction
- (narrow-to-region body-beg (point))
- (goto-char (point-min))
- (funcall washer)
- (goto-char (point-max)))))))
- (if (= body-beg (point))
- (magit-cancel-section section)
- (insert "\n"))
- section))
-
-(defun magit-git-section (section-title-and-type
- buffer-title washer &rest args)
- "Run git and put its result in a new section.
-
-see `magit-insert-section' for meaning of the arguments"
- (apply #'magit-insert-section
- section-title-and-type
- buffer-title
- washer
- magit-git-executable
- (append magit-git-standard-options args)))
+ magit-root-section))
+
+;;;;; Section Jumping
(defun magit-goto-next-section ()
"Go to the next section."
(defun magit-goto-next-sibling-section ()
"Go to the next sibling section."
(interactive)
- (let* ((initial (point))
- (section (magit-current-section))
- (end (- (magit-section-end section) 1))
- (parent (magit-section-parent section))
- (siblings (magit-section-children parent))
- (next-sibling (magit-find-section-after* end siblings)))
- (if next-sibling
- (magit-goto-section next-sibling)
+ (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))
- (beginning (magit-section-beginning section))
- (parent (magit-section-parent section))
- (siblings (magit-section-children parent))
- (previous-sibling (magit-find-section-before* beginning siblings)))
- (if previous-sibling
- (magit-goto-section previous-sibling)
- (magit-goto-parent-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))
(forward-line -1)
(magit-goto-next-section))
((and (eq (magit-section-type section) 'commit)
- (derived-mode-p 'magit-log-mode))
- (magit-show-commit section))))
+ (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-top-section)))
+ (let ((sec (magit-find-section path magit-root-section)))
(if sec
(goto-char (magit-section-beginning sec))
(message "No such section"))))
-(defun magit-for-all-sections (func &optional top)
- "Run FUNC on TOP and recursively on all its children.
+(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))))
-Default value for TOP is `magit-top-section'"
- (let ((section (or top magit-top-section)))
- (when section
- (funcall func section)
- (dolist (c (magit-section-children section))
- (magit-for-all-sections func c)))))
+(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."
(forward-line)
(point)))
(end (magit-section-end section)))
- (if (< beg end)
- (put-text-property beg end 'invisible hidden)))
- (if (not hidden)
- (dolist (c (magit-section-children section))
- (magit-section-set-hidden c (magit-section-hidden c))))))
+ (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."
(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 value, the section will be hidden if its true, shown otherwise"
+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))
(defun magit-toggle-file-section ()
"Like `magit-toggle-section' but toggle at file granularity."
(interactive)
- (when (eq 'hunk (first (magit-section-context-type (magit-current-section))))
+ (when (eq (magit-section-type (magit-current-section)) 'hunk)
(magit-goto-parent-section))
(magit-toggle-section))
(lambda (s)
(cond ((magit-section-hidden s)
(magit-section-collapse s))
- ((notany #'magit-section-hidden (magit-section-children 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 (s)
- "Return list of parent, grand-parents... for section S."
- (when s
- (cons s (magit-section-lineage (magit-section-parent 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))
(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."
- (magit-with-refresh
- (if all
- (magit-section-show-level magit-top-section 0 level nil)
- (let ((path (reverse (magit-section-lineage (magit-current-section)))))
- (magit-section-show-level (car path) 0 level (cdr path))))))
+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 there subsection.
+ "Show section that are files, but not their subsection.
Do this in on ancestors and descendants of current section."
(interactive)
(call-interactively 'magit-show-level-1)))
(defun magit-show-only-files-all ()
- "Show section that are files, but not there subsection.
-
+ "Show section that are files, but not their subsection.
Do this for all sections"
(interactive)
(if (derived-mode-p 'magit-status-mode)
"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."
+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)))
(defmacro magit-define-level-shower (level)
"Define two interactive function to show function of level LEVEL.
-one for all, one for current lineage."
+One for all, one for current lineage."
`(progn
(magit-define-level-shower-1 ,level nil)
(magit-define-level-shower-1 ,level t)))
-(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)))
- (doc (format "Jump to section `%s'." title)))
- `(progn
- (defun ,fun ()
- ,doc
- (interactive)
- (magit-goto-section-at-path '(,sym)))
- (put ',fun 'definition-name ',sym))))
+(magit-define-level-shower 1)
+(magit-define-level-shower 2)
+(magit-define-level-shower 3)
+(magit-define-level-shower 4)
-(defmacro magit-define-inserter (sym arglist &rest body)
- (declare (indent defun))
- (let ((fun (intern (format "magit-insert-%s" sym)))
- (before (intern (format "magit-before-insert-%s-hook" sym)))
- (after (intern (format "magit-after-insert-%s-hook" sym)))
- (doc (format "Insert items for `%s'." sym)))
- `(progn
- (defvar ,before nil)
- (defvar ,after nil)
- (defun ,fun ,arglist
- ,doc
- (run-hooks ',before)
- ,@body
- (run-hooks ',after))
- (put ',before 'definition-name ',sym)
- (put ',after 'definition-name ',sym)
- (put ',fun 'definition-name ',sym))))
+;;;;; Section Highlighting
-(defvar magit-highlighted-section nil)
-
-(defun magit-refine-section (section)
- "Apply temporary refinements to the display of SECTION.
-Refinements can be undone with `magit-unrefine-section'."
- (let ((type (and section (magit-section-type section))))
- (cond ((and (eq type 'hunk)
- magit-diff-refine-hunk
- (not (eq magit-diff-refine-hunk 'all)))
- ;; Refine the current hunk to show fine details, using
- ;; diff-mode machinery.
- (save-excursion
- (goto-char (magit-section-beginning magit-highlighted-section))
- (diff-refine-hunk))))))
-
-(defun magit-unrefine-section (section)
- "Remove refinements to the display of SECTION done by `magit-refine-section'."
- (let ((type (and section (magit-section-type section))))
- (cond ((and (eq type 'hunk)
- magit-diff-refine-hunk
- (not (eq magit-diff-refine-hunk 'all)))
- ;; XXX this should be in some diff-mode function, like
- ;; `diff-unrefine-hunk'
- (remove-overlays (magit-section-beginning section)
- (magit-section-end section)
- 'diff-mode 'fine)))))
-
-(defvar magit-highlight-overlay nil)
+(defvar-local magit-highlighted-section nil)
+(defvar-local magit-highlight-overlay nil)
(defun magit-highlight-section ()
- "Highlight current section if it has a type."
- (let ((section (magit-current-section)))
- (when (not (eq section magit-highlighted-section))
- (when magit-highlighted-section
- ;; remove any refinement from previous hunk
- (magit-unrefine-section magit-highlighted-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)
- (if (not magit-highlight-overlay)
- (let ((ov (make-overlay 1 1)))
- (overlay-put ov 'face 'magit-item-highlight)
- (setq magit-highlight-overlay ov)))
- (if (and section (magit-section-type section))
- (progn
- (magit-refine-section section)
- (move-overlay magit-highlight-overlay
- (magit-section-beginning section)
- (magit-section-end section)
- (current-buffer)))
- (delete-overlay magit-highlight-overlay)))))
+ (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)
- (if (null section)
- '()
- (let ((c (or (magit-section-type section)
- (if (symbolp (magit-section-title section))
- (magit-section-title section)))))
- (if c
- (cons c (magit-section-context-type
- (magit-section-parent section)))
- '()))))
-
-(defun magit-prefix-p (prefix list)
- "Returns non-nil if PREFIX is a prefix of LIST. PREFIX and LIST should both be
-lists.
-
-If the car of PREFIX is the symbol '*, then return non-nil if the cdr of PREFIX
-is a sublist of LIST (as if '* matched zero or more arbitrary elements of LIST)"
- ;;; Very schemish...
- (or (null prefix)
- (if (eq (car prefix) '*)
- (or (magit-prefix-p (cdr prefix) list)
- (and (not (null list))
- (magit-prefix-p prefix (cdr list))))
- (and (not (null list))
- (equal (car prefix) (car list))
- (magit-prefix-p (cdr prefix) (cdr list))))))
-
-(defmacro magit-section-case (head &rest clauses)
- "Make different action depending of current section.
-
-HEAD is (SECTION INFO &optional OPNAME),
- SECTION will be bind to the current section,
- INFO will be bind to the info's of the current section,
- OPNAME is a string that will be used to describe current action,
-
-CLAUSES is a list of CLAUSE, each clause is (SECTION-TYPE &BODY)
-where SECTION-TYPE describe section where BODY will be run.
-
-This returns non-nil if some section matches. If the
-corresponding body return a non-nil value, it is returned,
-otherwise it returns t.
-
-If no section matches, this returns nil if no OPNAME was given
-and throws an error otherwise."
- (declare (indent 1))
- (let ((section (car head))
- (info (cadr head))
- (type (make-symbol "*type*"))
- (context (make-symbol "*context*"))
- (opname (caddr head)))
- `(let* ((,section (magit-current-section))
- (,info (and ,section (magit-section-info ,section)))
- (,type (and ,section (magit-section-type ,section)))
- (,context (magit-section-context-type ,section)))
- (cond ,@(mapcar (lambda (clause)
- (if (eq (car clause) t)
- `(t (or (progn ,@(cdr clause))
- t))
- (let ((prefix (reverse (car clause)))
- (body (cdr clause)))
- `((magit-prefix-p ',prefix ,context)
- (or (progn ,@body)
- t)))))
- clauses)
- ,@(when opname
- `(((run-hook-with-args-until-success
- ',(intern (format "magit-%s-action-hook" opname))))
- ((not ,type)
- (error "Nothing to %s here" ,opname))
- (t
- (error "Can't %s a %s"
- ,opname
- (or (get ,type 'magit-description)
- ,type)))))))))
+ (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-action (head &rest clauses)
+(defmacro magit-section-case (slots &rest clauses)
(declare (indent 1))
- `(magit-with-refresh
- (magit-section-case ,head ,@clauses)))
-
-(defmacro magit-add-action (head &rest clauses)
- "Add additional actions to a pre-existing operator.
-The syntax is identical to `magit-section-case', except that
-OPNAME is mandatory and specifies the operation to which to add
-the actions."
- (declare (indent 1))
- (let ((section (car head))
- (info (cadr head))
- (type (caddr head)))
- `(add-hook ',(intern (format "magit-%s-action-hook" type))
- (lambda ()
- ,(macroexpand
- ;; Don't pass in the opname so we don't recursively
- ;; run the hook again, and so we don't throw an
- ;; error if no action matches.
- `(magit-section-case (,section ,info)
- ,@clauses))))))
-
-(defun magit-wash-sequence (func)
- "Run FUNC until end of buffer is reached.
-
-FUNC should leave point at the end of the modified region"
- (while (and (not (eobp))
- (funcall func))))
-
-(defmacro magit-define-command (sym arglist &rest body)
- "Macro to define a magit command.
-It will define the magit-SYM function having ARGLIST as argument.
-It will also define the magit-SYM-command-hook variable.
-
-The defined function will call the function in the hook in
-order until one return non nil. If they all return nil then body will be called.
-
-It is used to define hookable magit command: command defined by this
-function can be enriched by magit extension like magit-topgit and magit-svn"
- (declare (indent defun)
- (debug (&define name lambda-list
- [&optional stringp] ; Match the doc string, if present.
- [&optional ("interactive" interactive)]
- def-body)))
- (let ((fun (intern (format "magit-%s" sym)))
- (hook (intern (format "magit-%s-command-hook" sym)))
- (doc (format "Command for `%s'." sym))
- (inter nil)
- (instr body))
- (when (stringp (car body))
- (setq doc (car body)
- instr (cdr body)))
- (let ((form (car instr)))
- (when (eq (car form) 'interactive)
- (setq inter form
- instr (cdr instr))))
- `(progn
- (defvar ,hook nil)
- (defun ,fun ,arglist
- ,doc
- ,inter
- (or (run-hook-with-args-until-success
- ',hook ,@(remq '&optional (remq '&rest arglist)))
- ,@instr))
- (put ',fun 'definition-name ',sym)
- (put ',hook 'definition-name ',sym))))
-
-;;; Running commands
-
-(defun magit-set-mode-line-process (str)
- (let ((pr (if str (concat " " str) "")))
- (save-excursion
- (magit-for-all-buffers (lambda ()
- (setq mode-line-process pr))))))
-
-(defun magit-process-indicator-from-command (comps)
- (if (magit-prefix-p (cons magit-git-executable magit-git-standard-options)
- comps)
- (setq comps (nthcdr (+ (length magit-git-standard-options) 1) comps)))
- (cond ((or (null (cdr comps))
- (not (member (car comps) '("remote"))))
- (car comps))
- (t
- (concat (car comps) " " (cadr comps)))))
+ `(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 nil)
-(defvar magit-process-client-buffer nil)
(defvar magit-process-buffer-name "*magit-process*"
- "Buffer name for running git commands.")
-
-(defun magit-run* (cmd-and-args
- &optional logline noerase noerror nowait input)
- (if (and magit-process
- (get-buffer magit-process-buffer-name))
- (error "Git is already running"))
- (let ((cmd (car cmd-and-args))
- (args (cdr cmd-and-args))
- (dir default-directory)
- (buf (get-buffer-create magit-process-buffer-name))
- (successp nil))