summaryrefslogtreecommitdiffstats
path: root/emacs.d/lisp/magit/magit.el
diff options
context:
space:
mode:
authorAlexander Sulfrian <alexander@sulfrian.net>2009-12-11 17:03:09 +0100
committerAlexander Sulfrian <alexander@sulfrian.net>2009-12-11 17:03:09 +0100
commita6f973f6c2ec6a518927cd42a9bf769b9a67b818 (patch)
treeeba4e202b47deb495ff0fbe7383809dffc28327c /emacs.d/lisp/magit/magit.el
parentbb0908ad0031fb070306a231d277a3c722890915 (diff)
downloaddotfiles-a6f973f6c2ec6a518927cd42a9bf769b9a67b818.tar.gz
dotfiles-a6f973f6c2ec6a518927cd42a9bf769b9a67b818.tar.xz
dotfiles-a6f973f6c2ec6a518927cd42a9bf769b9a67b818.zip
added magit for emacs (better git handling)
Diffstat (limited to 'emacs.d/lisp/magit/magit.el')
-rw-r--r--emacs.d/lisp/magit/magit.el3244
1 files changed, 3244 insertions, 0 deletions
diff --git a/emacs.d/lisp/magit/magit.el b/emacs.d/lisp/magit/magit.el
new file mode 100644
index 0000000..bfbfe9b
--- /dev/null
+++ b/emacs.d/lisp/magit/magit.el
@@ -0,0 +1,3244 @@
+;;; Magit -- control Git from Emacs.
+
+;; Copyright (C) 2008, 2009 Marius Vollmer
+;; Copyright (C) 2008 Linh Dang
+;; Copyright (C) 2008 Alex Ott
+;; Copyright (C) 2008 Marcin Bachry
+;; Copyright (C) 2009 Alexey Voinov
+;; Copyright (C) 2009 John Wiegley
+;;
+;; Magit is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+;;
+;; Magit is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+;; License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with Magit. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary
+
+;; Invoking the magit-status function will show a buffer with the
+;; status of the current git repository and its working tree. That
+;; buffer offers key bindings for manipulating the status in simple
+;; ways.
+;;
+;; The status buffer mainly shows the difference between the working
+;; tree and the index, and the difference between the index and the
+;; current HEAD. You can add individual hunks from the working tree
+;; to the index, and you can commit the index.
+;;
+;; See the Magit User Manual for more information.
+
+;;; TODO
+
+;; For 0.8:
+;;
+;; - Fix display of unmerged files.
+;; - Fix performance problems with large status buffers.
+;; - Handle the case where remote and local branches have different names.
+;;
+;; Later:
+;;
+;; - Queuing of asynchronous commands.
+;; - Good email integration.
+;; - Showing tags.
+;; - Visiting from staged hunks doesn't always work since the line
+;; numbers don't refer to the working tree. Fix that somehow.
+;; - Figure out how to discard staged changes for files that also have
+;; unstaged changes.
+;; - Get current defun from removed lines in a diff
+;; - Amending commits other than HEAD.
+;; - 'Subsetting', only looking at a subset of all files.
+
+(require 'cl)
+(require 'parse-time)
+(require 'log-edit)
+(require 'easymenu)
+(require 'diff-mode)
+
+(defgroup magit nil
+ "Controlling Git from Emacs."
+ :prefix "magit-"
+ :group 'tools)
+
+(defcustom magit-git-executable "git"
+ "The name of the Git executable."
+ :group 'magit
+ :type 'string)
+
+(defcustom magit-git-standard-options '("--no-pager")
+ "Standard options when running Git."
+ :group 'magit
+ :type '(repeat string))
+
+(defcustom magit-repo-dirs nil
+ "Directories containing Git repositories.
+Magit will look into these directories for Git repositories and offers them as choices for magit-status."
+ :group 'magit
+ :type '(repeat string))
+
+(defcustom magit-repo-dirs-depth 3
+ "When looking for Git repositors below the directories in magit-repo-dirs, Magit will only descend this many levels deep."
+ :group 'magit
+ :type 'integer)
+
+(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)))
+
+(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)))
+
+(defcustom magit-commit-signoff nil
+ "When performing git commit adds --signoff"
+ :group 'magit
+ :type 'boolean)
+
+(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-process-popup-time -1
+ "Popup the process buffer if a command takes longer than this many seconds."
+ :group 'magit
+ :type '(choice (const :tag "Never" -1)
+ (const :tag "Immediately" 0)
+ (integer :tag "After this many seconds")))
+
+(defcustom magit-log-edit-confirm-cancellation nil
+ "Require acknowledgement before cancelling the log edit buffer."
+ :group 'magit
+ :type 'boolean)
+
+(defface magit-header
+ '((t))
+ "Face for generic header lines.
+
+Many Magit faces inherit from this one by default."
+ :group 'magit)
+
+(defface magit-section-title
+ '((t :weight bold :inherit magit-header))
+ "Face for section titles."
+ :group 'magit)
+
+(defface magit-branch
+ '((t :weight bold :inherit magit-header))
+ "Face for the current branch."
+ :group 'magit)
+
+(defface magit-diff-file-header
+ '((t :inherit magit-header))
+ "Face for diff file header lines."
+ :group 'magit)
+
+(defface magit-diff-hunk-header
+ '((t :slant italic :inherit magit-header))
+ "Face for diff hunk header lines."
+ :group 'magit)
+
+(defface magit-diff-add
+ '((((class color) (background light))
+ :foreground "blue1")
+ (((class color) (background dark))
+ :foreground "white"))
+ "Face for lines in a diff that have been added."
+ :group 'magit)
+
+(defface magit-diff-none
+ '((t))
+ "Face for lines in a diff that are unchanged."
+ :group 'magit)
+
+(defface magit-diff-del
+ '((((class color) (background light))
+ :foreground "red")
+ (((class color) (background dark))
+ :foreground "OrangeRed"))
+ "Face for lines in a diff that have been deleted."
+ :group 'magit)
+
+(defface magit-item-highlight
+ '((((class color) (background light))
+ :background "gray95")
+ (((class color) (background dark))
+ :background "dim gray"))
+ "Face for highlighting the current item."
+ :group 'magit)
+
+(defface magit-item-mark
+ '((((class color) (background light))
+ :foreground "red")
+ (((class color) (background dark))
+ :foreground "orange"))
+ "Face for highlighting marked item."
+ :group 'magit)
+
+(defface magit-log-tag-label
+ '((((class color) (background light))
+ :background "LightGoldenRod")
+ (((class color) (background dark))
+ :background "DarkGoldenRod"))
+ "Face for git tag labels shown in log buffer."
+ :group 'magit)
+
+(defface magit-log-head-label
+ '((((class color) (background light))
+ :background "spring green")
+ (((class color) (background dark))
+ :background "DarkGreen"))
+ "Face for branch head labels shown in log buffer."
+ :group 'magit)
+
+;;; Macros
+
+(defmacro magit-with-refresh (&rest body)
+ (declare (indent 0))
+ `(magit-refresh-wrapper (lambda () ,@body)))
+
+;;; Utilities
+
+(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)
+ (apply #'process-file
+ magit-git-executable
+ nil (list t nil) nil
+ (append magit-git-standard-options args)))
+
+(defun magit-git-output (args)
+ (with-output-to-string
+ (with-current-buffer
+ standard-output
+ (magit-git-insert args))))
+
+(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-concat-with-delim (delim seqs)
+ (cond ((null seqs)
+ nil)
+ ((null (cdr seqs))
+ (car seqs))
+ (t
+ (concat (car seqs) delim (magit-concat-with-delim delim (cdr seqs))))))
+
+(defun magit-get (&rest keys)
+ (magit-git-string "config" (magit-concat-with-delim "." keys)))
+
+(defun magit-set (val &rest keys)
+ (if val
+ (magit-git-string "config" (magit-concat-with-delim "." keys) val)
+ (magit-git-string "config" "--unset" (magit-concat-with-delim "." keys))))
+
+(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
+ (subseq entry 0 (- (length key))))))
+ (cons (concat (file-name-nondirectory dir) "/" key)
+ entry)))
+ value))))
+ (setq result (append result sub)))))
+ dict)
+ result))
+
+(defun magit-git-repo-p (dir)
+ (file-exists-p (expand-file-name ".git" dir)))
+
+(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-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 cwd)))
+ (and (file-directory-p cwd)
+ (let* ((default-directory cwd)
+ (magit-dir
+ (magit-git-string "rev-parse" "--git-dir")))
+ (and magit-dir
+ (file-name-as-directory
+ (or (file-name-directory magit-dir) cwd)))))))
+
+(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)))
+
+(defun magit-ref-exists-p (ref)
+ (= (magit-git-exit-code "show-ref" "--verify" ref) 0))
+
+(defun magit-read-top-dir (rawp)
+ (if (and (not rawp) magit-repo-dirs)
+ (let* ((repos (magit-list-repos magit-repo-dirs))
+ (reply (completing-read "Git repository: "
+ (magit-list-repos magit-repo-dirs))))
+ (file-name-as-directory
+ (cdr (assoc reply repos))))
+ (file-name-as-directory
+ (read-directory-name "Git repository: "
+ (or (magit-get-top-dir default-directory)
+ default-directory)))))
+
+(defun magit-name-rev (rev)
+ (and rev
+ (let ((name (magit-git-string "name-rev" "--name-only" rev)))
+ (if (or (not name) (string= name "undefined"))
+ rev
+ name))))
+
+(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))
+
+(defun magit-anything-staged-p ()
+ (not (eq (magit-git-exit-code "diff" "--quiet" "--cached") 0)))
+
+(defun magit-everything-clean-p ()
+ (and (not (magit-anything-staged-p))
+ (eq (magit-git-exit-code "diff" "--quiet") 0)))
+
+(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
+
+(defun magit-list-interesting-refs ()
+ (let ((refs ()))
+ (dolist (line (magit-git-lines "show-ref"))
+ (if (string-match "[^ ]+ +\\(.*\\)" line)
+ (let ((ref (match-string 1 line)))
+ (cond ((string-match "refs/heads/\\(.*\\)" ref)
+ (let ((branch (match-string 1 ref)))
+ (push (cons branch branch) refs)))
+ ((string-match "refs/tags/\\(.*\\)" ref)
+ (push (cons (format "%s (tag)" (match-string 1 ref)) ref)
+ refs))
+ ((string-match "refs/remotes/\\([^/]+\\)/\\(.+\\)" ref)
+ (push (cons (format "%s (%s)"
+ (match-string 2 ref)
+ (match-string 1 ref))
+ ref)
+ refs))))))
+ refs))
+
+(defun magit-read-rev (prompt &optional def)
+ (let* ((prompt (if def
+ (format "%s (default %s): " prompt def)
+ (format "%s: " prompt)))
+ (interesting-refs (magit-list-interesting-refs))
+ (reply (completing-read prompt interesting-refs
+ nil nil nil nil def))
+ (rev (or (cdr (assoc reply interesting-refs)) reply)))
+ (if (string= rev "")
+ nil
+ rev)))
+
+(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
+ (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)
+ 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 ()
+ (magit-name-rev (magit-commit-at-point t)))
+
+;;; 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)
+(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)
+ (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
+ (setf (magit-section-children magit-top-section)
+ (cons 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-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))))
+
+(defmacro magit-with-section (title type &rest body)
+ (declare (indent 2))
+ (let ((s (gensym)))
+ `(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)))
+
+(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)
+ (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)
+ (put-text-property (magit-section-beginning section)
+ (magit-section-end section)
+ 'magit-section section)
+ (dolist (s (magit-section-children section))
+ (magit-propertize-section s)))
+
+(defun magit-find-section (path top)
+ (if (null path)
+ top
+ (let ((sec (find-if (lambda (s) (equal (car path)
+ (magit-section-title s)))
+ (magit-section-children top))))
+ (if sec
+ (magit-find-section (cdr path) sec)
+ nil))))
+
+(defun magit-section-path (section)
+ (if (not (magit-section-parent section))
+ '()
+ (append (magit-section-path (magit-section-parent section))
+ (list (magit-section-title section)))))
+
+(defun magit-find-section-at (pos secs)
+ (while (and secs
+ (not (and (<= (magit-section-beginning (car secs)) pos)
+ (< pos (magit-section-end (car secs))))))
+ (setq secs (cdr secs)))
+ (if secs
+ (or (magit-find-section-at pos (magit-section-children (car secs)))
+ (car secs))
+ nil))
+
+(defun magit-find-section-after (pos secs)
+ (while (and secs
+ (not (> (magit-section-beginning (car secs)) pos)))
+ (setq secs (cdr secs)))
+ (car secs))
+
+(defun magit-find-section-before (pos secs)
+ (let ((prev nil))
+ (while (and secs
+ (not (> (magit-section-beginning (car secs)) pos)))
+ (setq prev (car secs))
+ (setq secs (cdr secs)))
+ prev))
+
+(defun magit-current-section ()
+ (or (get-text-property (point) 'magit-section)
+ magit-top-section))
+
+(defun magit-insert-section (section-title-and-type
+ buffer-title washer cmd &rest args)
+ (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))
+ (apply 'process-file cmd nil t nil (append magit-git-standard-options 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)
+ (apply #'magit-insert-section
+ section-title-and-type
+ buffer-title
+ washer
+ magit-git-executable
+ (append magit-git-standard-options args)))
+
+(defun magit-next-section (section)
+ (let ((parent (magit-section-parent section)))
+ (if parent
+ (let ((next (cadr (memq section
+ (magit-section-children parent)))))
+ (or next
+ (magit-next-section parent))))))
+
+(defun magit-goto-next-section ()
+ (interactive)
+ (let* ((section (magit-current-section))
+ (next (or (and (not (magit-section-hidden section))
+ (magit-section-children section)
+ (magit-find-section-after (point)
+ (magit-section-children
+ section)))
+ (magit-next-section section))))
+
+ (if next
+ (progn
+ (goto-char (magit-section-beginning next))
+ (if (memq magit-submode '(log reflog))
+ (magit-show-commit next))
+ (if (not (magit-section-hidden next))
+ (let ((offset (- (line-number-at-pos
+ (magit-section-beginning next))
+ (line-number-at-pos
+ (magit-section-end next)))))
+ (if (< offset (window-height))
+ (recenter offset)))))
+ (message "No next section"))))
+
+(defun magit-prev-section (section)
+ (let ((parent (magit-section-parent section)))
+ (if parent
+ (let ((prev (cadr (memq section
+ (reverse (magit-section-children parent))))))
+ (cond (prev
+ (while (and (not (magit-section-hidden prev))
+ (magit-section-children prev))
+ (setq prev (car (reverse (magit-section-children prev)))))
+ prev)
+ (t
+ parent))))))
+
+(defun magit-goto-previous-section ()
+ (interactive)
+ (let ((section (magit-current-section)))
+ (cond ((= (point) (magit-section-beginning section))
+ (let ((prev (magit-prev-section (magit-current-section))))
+ (if prev
+ (progn
+ (if (memq magit-submode '(log reflog))
+ (magit-show-commit (or prev section)))
+ (goto-char (magit-section-beginning prev)))
+ (message "No previous section"))))
+ (t
+ (let ((prev (magit-find-section-before (point)
+ (magit-section-children
+ section))))
+ (if (memq magit-submode '(log reflog))
+ (magit-show-commit (or prev section)))
+ (goto-char (magit-section-beginning (or prev section))))))))
+
+(defun magit-goto-section (path)
+ (let ((sec (magit-find-section path magit-top-section)))
+ (if sec
+ (goto-char (magit-section-beginning sec))
+ (message "No such section"))))
+
+(defun magit-for-all-sections (func &optional top)
+ (let ((section (or top magit-top-section)))
+ (when section
+ (funcall func section)
+ (dolist (c (magit-section-children section))
+ (magit-for-all-sections func c)))))
+
+(defun magit-section-set-hidden (section hidden)
+ (setf (magit-section-hidden section) hidden)
+ (if (and (not hidden)
+ (magit-section-needs-refresh-on-show section))
+ (magit-refresh)
+ (let ((inhibit-read-only t)
+ (beg (save-excursion
+ (goto-char (magit-section-beginning section))
+ (forward-line)
+ (point)))
+ (end (magit-section-end section)))
+ (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))))))
+
+(defun magit-section-any-hidden (section)
+ (or (magit-section-hidden section)
+ (some #'magit-section-any-hidden (magit-section-children section))))
+
+(defun magit-section-collapse (section)
+ (dolist (c (magit-section-children section))
+ (setf (magit-section-hidden c) t))
+ (magit-section-set-hidden section nil))
+
+(defun magit-section-expand (section)
+ (dolist (c (magit-section-children section))
+ (setf (magit-section-hidden c) nil))
+ (magit-section-set-hidden section nil))
+
+(defun magit-section-expand-all-aux (section)
+ (dolist (c (magit-section-children section))
+ (setf (magit-section-hidden c) nil)
+ (magit-section-expand-all-aux c)))
+
+(defun magit-section-expand-all (section)
+ (magit-section-expand-all-aux section)
+ (magit-section-set-hidden section nil))
+
+(defun magit-section-hideshow (flag-or-func)
+ (let ((section (magit-current-section)))
+ (cond ((magit-section-parent section)
+ (goto-char (magit-section-beginning section))
+ (if (functionp flag-or-func)
+ (funcall flag-or-func section)
+ (magit-section-set-hidden section flag-or-func))))))
+
+(defun magit-show-section ()
+ (interactive)
+ (magit-section-hideshow nil))
+
+(defun magit-hide-section ()
+ (interactive)
+ (magit-section-hideshow t))
+
+(defun magit-collapse-section ()
+ (interactive)
+ (magit-section-hideshow #'magit-section-collapse))
+
+(defun magit-expand-section ()
+ (interactive)
+ (magit-section-hideshow #'magit-section-expand))
+
+(defun magit-toggle-section ()
+ (interactive)
+ (magit-section-hideshow
+ (lambda (s)
+ (magit-section-set-hidden s (not (magit-section-hidden s))))))
+
+(defun magit-expand-collapse-section ()
+ (interactive)
+ (magit-section-hideshow
+ (lambda (s)
+ (cond ((magit-section-any-hidden s)
+ (magit-section-expand-all s))
+ (t
+ (magit-section-collapse s))))))
+
+(defun magit-cycle-section ()
+ (interactive)
+ (magit-section-hideshow
+ (lambda (s)
+ (cond ((magit-section-hidden s)
+ (magit-section-collapse s))
+ ((notany #'magit-section-hidden (magit-section-children s))
+ (magit-section-set-hidden s t))
+ (t
+ (magit-section-expand s))))))
+
+(defun magit-section-lineage (s)
+ (and s (cons s (magit-section-lineage (magit-section-parent s)))))
+
+(defun magit-section-show-level (section level threshold path)
+ (magit-section-set-hidden section (>= level threshold))
+ (when (< level threshold)
+ (if path
+ (magit-section-show-level (car path) (1+ level) threshold (cdr path))
+ (dolist (c (magit-section-children section))
+ (magit-section-show-level c (1+ level) threshold nil)))))
+
+(defun magit-show-level (level all)
+ (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))))))
+
+(defun magit-show-only-files ()
+ (interactive)
+ (if (eq magit-submode 'status)
+ (call-interactively 'magit-show-level-2)
+ (call-interactively 'magit-show-level-1)))
+
+(defun magit-show-only-files-all ()
+ (interactive)
+ (if (eq magit-submode 'status)
+ (call-interactively 'magit-show-level-2-all)
+ (call-interactively 'magit-show-level-1-all)))
+
+(defmacro magit-define-level-shower-1 (level all)
+ (let ((fun (intern (format "magit-show-level-%s%s"
+ level (if all "-all" ""))))
+ (doc (format "Show sections on level %s." level)))
+ `(defun ,fun ()
+ ,doc
+ (interactive)
+ (magit-show-level ,level ,all))))
+
+(defmacro magit-define-level-shower (level)
+ `(progn
+ (magit-define-level-shower-1 ,level nil)
+ (magit-define-level-shower-1 ,level t)))
+
+(defmacro magit-define-section-jumper (sym title)
+ (let ((fun (intern (format "magit-jump-to-%s" sym)))
+ (doc (format "Jump to section `%s'." title)))
+ `(defun ,fun ()
+ ,doc
+ (interactive)
+ (magit-goto-section '(,sym)))))
+
+(defvar magit-highlight-overlay nil)
+
+(defvar magit-highlighted-section nil)
+
+(defun magit-highlight-section ()
+ (let ((section (magit-current-section)))
+ (when (not (eq section 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))
+ (move-overlay magit-highlight-overlay
+ (magit-section-beginning section)
+ (magit-section-end section)
+ (current-buffer))
+ (delete-overlay magit-highlight-overlay)))))
+
+(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)
+ ;;; 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)
+ (declare (indent 1))
+ (let ((section (car head))
+ (info (cadr head))
+ (type (gensym))
+ (context (gensym))
+ (opname (caddr head)))
+ `(let* ((,section (magit-current-section))
+ (,info (magit-section-info ,section))
+ (,type (magit-section-type ,section))
+ (,context (magit-section-context-type ,section)))
+ (cond ,@(mapcar (lambda (clause)
+ (if (eq (car clause) t)
+ clause
+ (let ((prefix (reverse (car clause)))
+ (body (cdr clause)))
+ `((magit-prefix-p ',prefix ,context)
+ ,@body))))
+ clauses)
+ ,@(if opname
+ `(((not ,type)
+ (error "Nothing to %s here." ,opname))
+ (t
+ (error "Can't %s a %s."
+ ,opname
+ (or (get ,type 'magit-description)
+ ,type)))))))))
+
+(defmacro magit-section-action (head &rest clauses)
+ (declare (indent 1))
+ `(magit-with-refresh
+ (magit-section-case ,head ,@clauses)))
+
+(defun magit-wash-sequence (func)
+ (while (and (not (eobp))
+ (funcall func))))
+
+;;; 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)))))
+
+(defvar magit-process nil)
+(defvar magit-process-client-buffer nil)
+
+(defun magit-run* (cmd-and-args
+ &optional logline noerase noerror nowait input)
+ (if (and magit-process
+ (get-buffer "*magit-process*"))
+ (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*"))
+ (successp nil))
+ (magit-set-mode-line-process
+ (magit-process-indicator-from-command cmd-and-args))
+ (setq magit-process-client-buffer (current-buffer))
+ (save-excursion
+ (set-buffer buf)
+ (setq buffer-read-only t)
+ (let ((inhibit-read-only t))
+ (setq default-directory dir)
+ (if noerase
+ (goto-char (point-max))
+ (erase-buffer))
+ (insert "$ " (or logline
+ (magit-concat-with-delim " " cmd-and-args))
+ "\n")
+ (cond (nowait
+ (setq magit-process
+ (apply 'start-file-process cmd buf cmd args))
+ (set-process-sentinel magit-process 'magit-process-sentinel)
+ (set-process-filter magit-process 'magit-process-filter)
+ (when input
+ (with-current-buffer input
+ (process-send-region magit-process
+ (point-min) (point-max)))
+ (process-send-eof magit-process)
+ (sit-for 0.1 t))
+ (cond ((= magit-process-popup-time 0)
+ (pop-to-buffer (process-buffer magit-process)))
+ ((> magit-process-popup-time 0)
+ (run-with-timer
+ magit-process-popup-time nil
+ (function
+ (lambda (buf)
+ (with-current-buffer buf
+ (when magit-process
+ (display-buffer (process-buffer magit-process))
+ (goto-char (point-max))))))
+ (current-buffer))))
+ (setq successp t))
+ (input
+ (with-current-buffer input
+ (setq default-directory dir)
+ (setq successp
+ (equal (apply 'call-process-region
+ (point-min) (point-max)
+ cmd nil buf nil args) 0)))
+ (magit-set-mode-line-process nil)
+ (magit-need-refresh magit-process-client-buffer))
+ (t
+ (setq successp
+ (equal (apply 'process-file cmd nil buf nil args) 0))
+ (magit-set-mode-line-process nil)
+ (magit-need-refresh magit-process-client-buffer))))
+ (or successp
+ noerror
+ (error "Git failed."))
+ successp)))
+
+(defun magit-process-sentinel (process event)
+ (let ((msg (format "Git %s." (substring event 0 -1)))
+ (successp (string-match "^finished" event)))
+ (with-current-buffer (process-buffer process)
+ (let ((inhibit-read-only t))
+ (goto-char (point-max))
+ (insert msg "\n")
+ (message msg)))
+ (setq magit-process nil)
+ (magit-set-mode-line-process nil)
+ (magit-refresh-buffer magit-process-client-buffer)))
+
+(defun magit-process-filter (proc string)
+ (save-current-buffer
+ (set-buffer (process-buffer proc))
+ (let ((inhibit-read-only t))
+ (goto-char (process-mark proc))
+ ;; Find last ^M in string. If one was found, ignore everything
+ ;; before it and delete the current line.
+ (let ((ret-pos (position ?\r string :from-end t)))
+ (cond (ret-pos
+ (goto-char (line-beginning-position))
+ (delete-region (point) (line-end-position))
+ (insert (substring string (+ ret-pos 1))))
+ (t
+ (insert string))))
+ (set-marker (process-mark proc) (point)))))
+
+(defun magit-run (cmd &rest args)
+ (magit-with-refresh
+ (magit-run* (cons cmd args))))
+
+(defun magit-run-git (&rest args)
+ (magit-with-refresh
+ (magit-run* (append (cons magit-git-executable
+ magit-git-standard-options)
+ args))))
+
+(defun magit-run-with-input (input cmd &rest args)
+ (magit-with-refresh
+ (magit-run* (cons cmd args) nil nil nil nil input)))
+
+(defun magit-run-git-with-input (input &rest args)
+ (magit-with-refresh
+ (magit-run* (append (cons magit-git-executable
+ magit-git-standard-options)
+ args)
+ nil nil nil nil input)))
+
+(defun magit-run-git-async (&rest args)
+ (magit-run* (append (cons magit-git-executable
+ magit-git-standard-options)
+ args)
+ nil nil nil t))
+
+(defun magit-run-async-with-input (input cmd &rest args)
+ (magit-run* (cons cmd args) nil nil nil t input))
+
+(defun magit-display-process ()
+ (interactive)
+ (display-buffer "*magit-process*"))
+
+;;; Mode
+
+;; We define individual functions (instead of using lambda etc) so
+;; that the online help can show something meaningful.
+
+(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 unpushed "Unpushed commits")
+(magit-define-section-jumper svn-unpushed "Unpushed commits (SVN)")
+
+(magit-define-level-shower 1)
+(magit-define-level-shower 2)
+(magit-define-level-shower 3)
+(magit-define-level-shower 4)
+
+(defvar magit-mode-map
+ (let ((map (make-keymap)))
+ (suppress-keymap map t)
+ (define-key map (kbd "n") 'magit-goto-next-section)
+ (define-key map (kbd "p") 'magit-goto-previous-section)
+ (define-key map (kbd "TAB") 'magit-toggle-section)
+ (define-key map (kbd "<backtab>") 'magit-expand-collapse-section)
+ (define-key map (kbd "1") 'magit-show-level-1)
+ (define-key map (kbd "2") 'magit-show-level-2)
+ (define-key map (kbd "3") 'magit-show-level-3)
+ (define-key map (kbd "4") 'magit-show-level-4)
+ (define-key map (kbd "M-1") 'magit-show-level-1-all)
+ (define-key map (kbd "M-2") 'magit-show-level-2-all)
+ (define-key map (kbd "M-3") 'magit-show-level-3-all)
+ (define-key map (kbd "M-4") 'magit-show-level-4-all)
+ (define-key map (kbd "M-h") 'magit-show-only-files)
+ (define-key map (kbd "M-H") 'magit-show-only-files-all)
+ (define-key map (kbd "M-s") 'magit-show-level-4)
+ (define-key map (kbd "M-S") 'magit-show-level-4-all)
+ (define-key map (kbd "g") 'magit-refresh)
+ (define-key map (kbd "G") 'magit-refresh-all)
+ (define-key map (kbd "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 "?") 'magit-describe-item)
+ (define-key map (kbd ".") 'magit-mark-item)
+ (define-key map (kbd "=") 'magit-diff-with-mark)
+ (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 "l") 'magit-log)
+ (define-key map (kbd "L") 'magit-log-long)
+ (define-key map (kbd "h") 'magit-reflog-head)
+ (define-key map (kbd "H") 'magit-reflog)
+ (define-key map (kbd "d") 'magit-diff-working-tree)
+ (define-key map (kbd "D") 'magit-diff)
+ (define-key map (kbd "a") 'magit-apply-item)
+ (define-key map (kbd "A") 'magit-cherry-pick-item)
+ (define-key map (kbd "v") 'magit-revert-item)
+ (define-key map (kbd "x") 'magit-reset-head)
+ (define-key map (kbd "X") 'magit-reset-working-tree)
+ (define-key map (kbd "k") 'magit-discard-item)
+ (define-key map (kbd "!") 'magit-shell-command)
+ (define-key map (kbd "RET") 'magit-visit-item)
+ (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 "b") 'magit-checkout)
+ (define-key map (kbd "B") 'magit-create-branch)
+ (define-key map (kbd "m") 'magit-manual-merge)
+ (define-key map (kbd "M") 'magit-automatic-merge)
+ (define-key map (kbd "e") 'magit-interactive-resolve-item)
+ (define-key map (kbd "N r") 'magit-svn-rebase)
+ (define-key map (kbd "N c") 'magit-svn-dcommit)
+ (define-key map (kbd "R") 'magit-rebase-step)
+ (define-key map (kbd "r s") 'magit-rewrite-start)
+ (define-key map (kbd "r t") 'magit-rewrite-stop)
+ (define-key map (kbd "r a") 'magit-rewrite-abort)
+ (define-key map (kbd "r f") 'magit-rewrite-finish)
+ (define-key map (kbd "r *") 'magit-rewrite-set-unused)
+ (define-key map (kbd "r .") 'magit-rewrite-set-used)
+ (define-key map (kbd "P") 'magit-push)
+ (define-key map (kbd "f") 'magit-remote-update)
+ (define-key map (kbd "F") 'magit-pull)
+ (define-key map (kbd "c") 'magit-log-edit)
+ (define-key map (kbd "C") 'magit-add-log)
+ (define-key map (kbd "t") 'magit-tag)
+ (define-key map (kbd "T") 'magit-annotated-tag)
+ (define-key map (kbd "z") 'magit-stash)
+ (define-key map (kbd "Z") 'magit-stash-snapshot)
+ (define-key map (kbd "w") 'magit-wazzup)
+ (define-key map (kbd "$") 'magit-display-process)
+ (define-key map (kbd "E") 'magit-interactive-rebase)
+ (define-key map (kbd "V") 'magit-show-branches)
+ (define-key map (kbd "q") 'quit-window)
+ map))
+
+(easy-menu-define magit-mode-menu magit-mode-map
+ "Magit menu"
+ '("Magit"
+ ["Refresh" magit-refresh t]
+ ["Refresh all" magit-refresh-all t]
+ "---"
+ ["Stage" magit-stage-item t]
+ ["Stage all" magit-stage-all t]
+ ["Unstage" magit-unstage-item t]
+ ["Unstage all" magit-unstage-all t]
+ ["Commit" magit-log-edit t]
+ ["Add log entry" magit-add-log t]
+ ["Tag" magit-tag t]
+ ["Annotated tag" magit-annotated-tag t]
+ "---"
+ ["Diff working tree" magit-diff-working-tree t]
+ ["Diff" magit-diff t]
+ ["Log" magit-log t]
+ ["Long Log" magit-log-long t]
+ ["Reflog head" magit-reflog-head t]
+ ["Reflog" magit-reflog 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]
+ "---"
+ ["Switch branch" magit-checkout t]
+ ["Create branch" magit-create-branch t]
+ ["Merge" magit-automatic-merge t]
+ ["Merge (no commit)" magit-manual-merge t]
+ ["Interactive resolve" magit-interactive-resolve-item t]
+ ["Rebase" magit-rebase-step t]
+ ("Git SVN"
+ ["Rebase" magit-svn-rebase (magit-svn-enabled)]
+ ["Commit" magit-svn-dcommit (magit-svn-enabled)]
+ )
+ ("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]
+ "---"
+ ["Display Git output" magit-display-process t]
+ ["Quit Magit" quit-window t]))
+
+(defvar magit-mode-hook nil)
+
+(put 'magit-mode 'mode-class 'special)
+
+(defvar magit-submode nil)
+(make-variable-buffer-local 'magit-submode)
+(put 'magit-submode 'permanent-local t)
+
+(defvar magit-refresh-function nil)
+(make-variable-buffer-local 'magit-refresh-function)
+(put 'magit-refresh-function 'permanent-local t)
+
+(defvar magit-refresh-args nil)
+(make-variable-buffer-local 'magit-refresh-args)
+(put 'magit-refresh-args 'permanent-local t)
+
+(defvar last-point)
+
+(defun magit-remember-point ()
+ (setq last-point (point)))
+
+(defun magit-invisible-region-end (pos)
+ (while (and (not (= pos (point-max))) (invisible-p pos))
+ (setq pos (next-char-property-change pos)))
+ pos)
+
+(defun magit-invisible-region-start (pos)
+ (while (and (not (= pos (point-min))) (invisible-p pos))
+ (setq pos (1- (previous-char-property-change pos))))
+ pos)
+
+(defun magit-correct-point-after-command ()
+ ;; Emacs often leaves point in invisible regions, it seems. To fix
+ ;; this, we move point ourselves and never let Emacs do its own
+ ;; adjustements.
+ ;;
+ ;; When point has to be moved out of an invisible region, it can be
+ ;; moved to its end or its beginning. We usually move it to its
+ ;; end, except when that would move point back to where it was
+ ;; before the last command.
+ ;;
+ (if (invisible-p (point))
+ (let ((end (magit-invisible-region-end (point))))
+ (goto-char (if (= end last-point)
+ (magit-invisible-region-start (point))
+ end))))
+ (setq disable-point-adjustment t))
+
+(defun magit-post-command-hook ()
+ (magit-correct-point-after-command)
+ (magit-highlight-section))
+
+(defun magit-mode ()
+ "Review the status of a git repository and act on it.
+
+Please see the manual for a complete description of Magit.
+
+\\{magit-mode-map}"
+ (kill-all-local-variables)
+ (buffer-disable-undo)
+ (setq buffer-read-only t)
+ (make-local-variable 'line-move-visual)
+ (setq major-mode 'magit-mode
+ mode-name "Magit"
+ mode-line-process ""
+ truncate-lines t
+ line-move-visual nil)
+ (add-hook 'pre-command-hook #'magit-remember-point nil t)
+ (add-hook 'post-command-hook #'magit-post-command-hook t t)
+ (use-local-map magit-mode-map)
+ (run-mode-hooks 'magit-mode-hook))
+
+(defun magit-mode-init (dir submode refresh-func &rest refresh-args)
+ (setq default-directory dir
+ magit-submode submode
+ magit-refresh-function refresh-func
+ magit-refresh-args refresh-args)
+ (magit-mode)
+ (magit-refresh-buffer))
+
+(defun magit-find-buffer (submode &optional dir)
+ (let ((topdir (magit-get-top-dir (or dir default-directory))))
+ (dolist (buf (buffer-list))
+ (if (save-excursion
+ (set-buffer buf)
+ (and default-directory
+ (equal (expand-file-name default-directory) topdir)
+ (eq major-mode 'magit-mode)
+ (eq magit-submode submode)))
+ (return buf)))))
+
+(defun magit-find-status-buffer (&optional dir)
+ (magit-find-buffer 'status dir))
+
+(defun magit-for-all-buffers (func &optional dir)
+ (dolist (buf (buffer-list))
+ (save-excursion
+ (set-buffer buf)
+ (if (and (eq major-mode 'magit-mode)
+ (or (null dir)
+ (equal default-directory dir)))
+ (funcall func)))))
+
+(defun magit-refresh-buffer (&optional buffer)
+ (with-current-buffer (or buffer (current-buffer))
+ (let* ((old-line (line-number-at-pos))
+ (old-section (magit-current-section))
+ (old-path (and old-section
+ (magit-section-path (magit-current-section))))
+ (section-line (and old-section
+ (count-lines
+ (magit-section-beginning old-section)
+ (point)))))
+ (if magit-refresh-function
+ (apply magit-refresh-function
+ magit-refresh-args))
+ (magit-refresh-marked-commits-in-buffer)
+ (let ((s (and old-path (magit-find-section old-path magit-top-section))))
+ (cond (s
+ (goto-char (magit-section-beginning s))
+ (forward-line section-line))
+ (t
+ (magit-goto-line old-line)))
+ (dolist (w (get-buffer-window-list (current-buffer)))
+ (set-window-point w (point)))
+ (magit-highlight-section)))))
+
+(defun magit-string-has-prefix-p (string prefix)
+ (eq (compare-strings string nil (length prefix) prefix nil nil) t))
+
+(defun magit-revert-buffers (dir)
+ (dolist (buffer (buffer-list))
+ (when (and buffer
+ (buffer-file-name buffer)
+ (magit-string-has-prefix-p (buffer-file-name buffer) dir)
+ (not (verify-visited-file-modtime buffer))
+ (not (buffer-modified-p buffer)))
+ (with-current-buffer buffer
+ (ignore-errors
+ (revert-buffer t t nil))))))
+
+(defvar magit-refresh-needing-buffers nil)
+(defvar magit-refresh-pending nil)
+
+(defun magit-refresh-wrapper (func)
+ (if magit-refresh-pending
+ (funcall func)
+ (let* ((dir default-directory)
+ (status-buffer (magit-find-buffer 'status dir))
+ (magit-refresh-needing-buffers nil)
+ (magit-refresh-pending t))
+ (unwind-protect
+ (funcall func)
+ (when magit-refresh-needing-buffers
+ (magit-revert-buffers dir)
+ (dolist (b (adjoin status-buffer
+ magit-refresh-needing-buffers))
+ (magit-refresh-buffer b)))))))
+
+(defun magit-need-refresh (&optional buffer)
+ (let ((buffer (or buffer (current-buffer))))
+ (when (not (memq buffer magit-refresh-needing-buffers))
+ (setq magit-refresh-needing-buffers
+ (cons buffer magit-refresh-needing-buffers)))))
+
+(defun magit-refresh ()
+ (interactive)
+ (magit-with-refresh
+ (magit-need-refresh)))
+
+(defun magit-refresh-all ()
+ (interactive)
+ (magit-for-all-buffers #'magit-refresh-buffer default-directory))
+
+;;; Untracked files
+
+(defun magit-wash-untracked-file ()
+ (if (looking-at "^? \\(.*\\)$")
+ (let ((file (match-string-no-properties 1)))
+ (delete-region (point) (+ (line-end-position) 1))
+ (magit-with-section file 'file
+ (magit-set-section-info file)
+ (insert "\t" file "\n"))
+ t)
+ nil))
+
+(defun magit-wash-untracked-files ()
+ ;; Setting magit-old-top-section to nil speeds up washing: no time
+ ;; is wasted looking up the old visibility, which doesn't matter for
+ ;; untracked files.
+ ;;
+ ;; XXX - speed this up in a more general way.
+ ;;
+ (let ((magit-old-top-section nil))
+ (magit-wash-sequence #'magit-wash-untracked-file)))
+
+(defun magit-insert-untracked-files ()
+ (magit-git-section 'untracked "Untracked files:"
+ 'magit-wash-untracked-files
+ "ls-files" "-t" "--others" "--exclude-standard"))
+
+;;; Diffs and Hunks
+
+(defvar magit-diff-context-lines 3)
+
+(defun magit-diff-U-arg ()
+ (format "-U%d" magit-diff-context-lines))
+
+(defun magit-diff-smaller-hunks (&optional count)
+ (interactive "p")
+ (setq magit-diff-context-lines (max 0 (- magit-diff-context-lines count)))
+ (magit-refresh))
+
+(defun magit-diff-larger-hunks (&optional count)
+ (interactive "p")
+ (setq magit-diff-context-lines (+ magit-diff-context-lines count))
+ (magit-refresh))
+
+(defun magit-diff-default-hunks ()
+ (interactive "")
+ (setq magit-diff-context-lines 3)
+ (magit-refresh))
+
+(defun magit-diff-line-file ()
+ (cond ((looking-at "^diff --git ./\\(.*\\) ./\\(.*\\)$")
+ (match-string-no-properties 2))
+ ((looking-at "^diff --cc +\\(.*\\)$")
+ (match-string-no-properties 1))
+ (t
+ nil)))
+
+(defun magit-wash-diffs ()
+ (magit-wash-sequence #'magit-wash-diff-or-other-file))
+
+(defun magit-wash-diff-or-other-file ()
+ (or (magit-wash-diff)
+ (magit-wash-other-file)))
+
+(defun magit-wash-other-file ()
+ (if (looking-at "^? \\(.*\\)$")
+ (let ((file (match-string-no-properties 1)))
+ (delete-region (point) (+ (line-end-position) 1))
+ (magit-with-section file 'file
+ (magit-set-section-info file)
+ (insert "\tNew " file "\n"))
+ t)
+ nil))
+
+(defvar magit-hide-diffs nil)
+
+(defun magit-insert-diff-title (status file file2)
+ (let ((status-text (case status
+ ((unmerged)
+ (format "Unmerged %s" file))
+ ((new)
+ (format "New %s" file))
+ ((deleted)
+ (format "Deleted %s" file))
+ ((renamed)
+ (format "Renamed %s (from %s)"
+ file file2))
+ ((modified)
+ (format "Modified %s" file))
+ (t
+ (format "? %s" file)))))
+ (insert "\t" status-text "\n")))
+
+(defun magit-wash-diff-section ()
+ (cond ((looking-at "^\\* Unmerged path \\(.*\\)")
+ (let ((file (match-string-no-properties 1)))
+ (delete-region (point) (line-end-position))
+ (insert "\tUnmerged " file "\n")
+ (magit-set-section-info (list 'unmerged file nil))
+ t))
+ ((looking-at "^diff")
+ (let ((file (magit-diff-line-file))
+ (end (save-excursion
+ (forward-line) ;; skip over "diff" line
+ (if (search-forward-regexp "^diff\\|^@@" nil t)
+ (goto-char (match-beginning 0))
+ (goto-char (point-max)))
+ (point-marker))))
+ (let* ((status (cond
+ ((looking-at "^diff --cc")
+ 'unmerged)
+ ((save-excursion
+ (search-forward-regexp "^new file" end t))
+ 'new)
+ ((save-excursion
+ (search-forward-regexp "^deleted" end t))
+ 'deleted)
+ ((save-excursion
+ (search-forward-regexp "^rename" end t))
+ 'renamed)
+ (t
+ 'modified)))
+ (file2 (cond
+ ((save-excursion
+ (search-forward-regexp "^rename from \\(.*\\)"
+ end t))
+ (match-string-no-properties 1)))))
+ (magit-set-section-info (list status file file2))
+ (magit-insert-diff-title status file file2)
+ (goto-char end)
+ (let ((magit-section-hidden-default nil))
+ (magit-wash-sequence #'magit-wash-hunk))))
+ t)
+ (t
+ nil)))
+
+(defun magit-wash-diff ()
+ (let ((magit-section-hidden-default magit-hide-diffs))
+ (magit-with-section (magit-current-line) 'diff
+ (magit-wash-diff-section))))
+
+(defun magit-diff-item-kind (diff)
+ (car (magit-section-info diff)))
+
+(defun magit-diff-item-file (diff)
+ (cadr (magit-section-info diff)))
+
+(defun magit-diff-item-file2 (diff)
+ (caddr (magit-section-info diff)))
+
+(defun magit-wash-hunk ()
+ (cond ((looking-at "\\(^@+\\)[^@]*@+")
+ (let ((n-columns (1- (length (match-string 1))))
+ (head (match-string 0)))
+ (magit-with-section head 'hunk
+ (magit-put-line-property 'face 'magit-diff-hunk-header)
+ (forward-line)
+ (while (not (or (eobp)
+ (looking-at "^diff\\|^@@")))
+ (let ((prefix (buffer-substring-no-properties
+ (point) (min (+ (point) n-columns) (point-max)))))
+ (cond ((string-match "\\+" prefix)
+ (magit-put-line-property 'face 'magit-diff-add))
+ ((string-match "-" prefix)
+ (magit-put-line-property 'face 'magit-diff-del))
+ (t
+ (magit-put-line-property 'face 'magit-diff-none))))
+ (forward-line))))
+ t)
+ (t
+ nil)))
+
+(defvar magit-diff-options nil)
+
+(defun magit-insert-diff (file)
+ (let ((cmd magit-git-executable)
+ (args (append (list "diff")
+ (list (magit-diff-U-arg))
+ magit-diff-options
+ (list "--" file))))
+ (let ((p (point)))
+ (magit-git-insert args)
+ (if (not (eq (char-before) ?\n))
+ (insert "\n"))
+ (save-restriction
+ (narrow-to-region p (point))
+ (goto-char p)
+ (magit-wash-diff-section)
+ (goto-char (point-max))))))
+
+(defvar magit-last-raw-diff nil)
+(defvar magit-ignore-unmerged-raw-diffs nil)
+
+(defun magit-wash-raw-diffs ()
+ (let ((magit-last-raw-diff nil))
+ (magit-wash-sequence #'magit-wash-raw-diff)))
+
+(defun magit-wash-raw-diff ()
+ (if (looking-at
+ ":\\([0-7]+\\) \\([0-7]+\\) [0-9a-f]+ [0-9a-f]+ \\(.\\)[0-9]*\t\\([^\t\n]+\\)$")
+ (let ((old-perm (match-string-no-properties 1))
+ (new-perm (match-string-no-properties 2))
+ (status (case (string-to-char (match-string-no-properties 3))
+ (?A 'new)
+ (?D 'deleted)
+ (?M 'modified)
+ (?U 'unmerged)
+ (?T 'new-type)
+ (t nil)))
+ (file (match-string-no-properties 4)))
+ ;; If this is for the same file as the last diff, ignore it.
+ ;; Unmerged files seem to get two entries.
+ ;; We also ignore unmerged files when told so.
+ (if (or (equal file magit-last-raw-diff)
+ (and magit-ignore-unmerged-raw-diffs (eq status 'unmerged)))
+ (delete-region (point) (+ (line-end-position) 1))
+ (setq magit-last-raw-diff file)
+ ;; The 'diff' section that is created here will not work with
+ ;; magit-insert-diff-item-patch etc when we leave it empty.
+ ;; Luckily, raw diffs are only produced for staged and
+ ;; unstaged changes, and we never call
+ ;; magit-insert-diff-item-patch on them. This is a bit
+ ;; brittle, of course.
+ (let ((magit-section-hidden-default magit-hide-diffs))
+ (magit-with-section file 'diff
+ (delete-region (point) (+ (line-end-position) 1))
+ (if (not (magit-section-hidden magit-top-section))
+ (magit-insert-diff file)
+ (magit-set-section-info (list status file nil))
+ (magit-set-section-needs-refresh-on-show t)
+ (magit-insert-diff-title status file nil)))))
+ t)
+ nil))
+
+(defun magit-hunk-item-diff (hunk)
+ (let ((diff (magit-section-parent hunk)))
+ (or (eq (magit-section-type diff) 'diff)
+ (error "Huh? Parent of hunk not a diff."))
+ diff))
+
+(defun magit-diff-item-insert-header (diff buf)
+ (let ((beg (save-excursion
+ (goto-char (magit-section-beginning diff))
+ (forward-line)
+ (point)))
+ (end (if (magit-section-children diff)
+ (magit-section-beginning (car (magit-section-children diff)))
+ (magit-section-end diff))))
+ (magit-insert-region beg end buf)))
+
+(defun magit-insert-diff-item-patch (diff buf)
+ (let ((beg (save-excursion
+ (goto-char (magit-section-beginning diff))
+ (forward-line)
+ (point)))
+ (end (magit-section-end diff)))
+ (magit-insert-region beg end buf)))
+
+(defun magit-insert-hunk-item-patch (hunk buf)
+ (magit-diff-item-insert-header (magit-hunk-item-diff hunk) buf)
+ (magit-insert-region (magit-section-beginning hunk) (magit-section-end hunk)
+ buf))
+
+(defun magit-insert-hunk-item-region-patch (hunk reverse beg end buf)
+ (magit-diff-item-insert-header (magit-hunk-item-diff hunk) buf)
+ (save-excursion
+ (goto-char (magit-section-beginning hunk))
+ (magit-insert-current-line buf)
+ (forward-line)
+ (let ((copy-op (if reverse "+" "-")))
+ (while (< (point) (magit-section-end hunk))
+ (if (and (<= beg (point)) (< (point) end))
+ (magit-insert-current-line buf)
+ (cond ((looking-at " ")
+ (magit-insert-current-line buf))
+ ((looking-at copy-op)
+ (let ((text (buffer-substring-no-properties
+ (+ (point) 1) (line-beginning-position 2))))
+ (with-current-buffer buf
+ (insert " " text))))))
+ (forward-line))))
+ (with-current-buffer buf
+ (diff-fixup-modifs (point-min) (point-max))))
+
+(defun magit-hunk-item-is-conflict-p (hunk)
+ ;;; XXX - Using the title is a bit too clever...
+ (string-match "^diff --cc"
+ (magit-section-title (magit-hunk-item-diff hunk))))
+
+(defun magit-hunk-item-target-line (hunk)
+ (save-excursion
+ (beginning-of-line)
+ (let ((line (line-number-at-pos)))
+ (if (looking-at "-")
+ (error "Can't visit removed lines."))
+ (goto-char (magit-section-beginning hunk))
+ (if (not (looking-at "@@+ .* \\+\\([0-9]+\\),[0-9]+ @@+"))
+ (error "Hunk header not found."))
+ (let ((target (parse-integer (match-string 1))))
+ (forward-line)
+ (while (< (line-number-at-pos) line)
+ ;; XXX - deal with combined diffs
+ (if (not (looking-at "-"))
+ (setq target (+ target 1)))
+ (forward-line))
+ target))))
+
+(defun magit-apply-diff-item (diff &rest args)
+ (when (zerop magit-diff-context-lines)
+ (setq args (cons "--unidiff-zero" args)))
+ (let ((tmp (get-buffer-create "*magit-tmp*")))
+ (with-current-buffer tmp
+ (erase-buffer))
+ (magit-insert-diff-item-patch diff "*magit-tmp*")
+ (apply #'magit-run-git-with-input tmp
+ "apply" (append args (list "-")))))
+
+(defun magit-apply-hunk-item* (hunk reverse &rest args)
+ (when (zerop magit-diff-context-lines)
+ (setq args (cons "--unidiff-zero" args)))
+ (let ((tmp (get-buffer-create "*magit-tmp*")))
+ (with-current-buffer tmp
+ (erase-buffer))
+ (if (magit-use-region-p)
+ (magit-insert-hunk-item-region-patch
+ hunk reverse (region-beginning) (region-end) tmp)
+ (magit-insert-hunk-item-patch hunk tmp))
+ (apply #'magit-run-git-with-input tmp
+ "apply" (append args (list "-")))))
+
+(defun magit-apply-hunk-item (hunk &rest args)
+ (apply #'magit-apply-hunk-item* hunk nil args))
+
+(defun magit-apply-hunk-item-reverse (hunk &rest args)
+ (apply #'magit-apply-hunk-item* hunk t (cons "--reverse" args)))
+
+(defun magit-insert-unstaged-changes (title)
+ (let ((magit-hide-diffs t))
+ (let ((magit-diff-options '()))
+ (magit-git-section 'unstaged title 'magit-wash-raw-diffs
+ "diff-files"))))
+
+(defun magit-insert-staged-changes (no-commit)
+ (let ((magit-hide-diffs t)
+ (base (if no-commit
+ (magit-git-string "mktree")
+ "HEAD")))
+ (let ((magit-diff-options '("--cached"))
+ (magit-ignore-unmerged-raw-diffs t))
+ (magit-git-section 'staged "Staged changes:" 'magit-wash-raw-diffs
+ "diff-index" "--cached"
+ base))))
+
+;;; Logs and Commits
+
+(defun magit-parse-log-ref (refname)
+ "Return shortened and propertized version of full REFNAME, like
+\"refs/remotes/origin/master\"."
+ (let ((face 'magit-log-head-label))
+ (cond ((string-match "^\\(tag: +\\)?refs/tags/\\(.+\\)" refname)
+ (setq refname (match-string 2 refname)
+ face 'magit-log-tag-label))
+ ((string-match "^refs/remotes/\\(.+\\)" refname)
+ (setq refname (match-string 1 refname)))
+ ((string-match "[^/]+$" refname)
+ (setq refname (match-string 0 refname))))
+ (propertize refname 'face face)))
+
+(defun magit-parse-log-refs (refstring)
+ "Parse REFSTRING annotation from `git log --decorate'
+output (for example: \"refs/remotes/origin/master,
+refs/heads/master\") and return prettified string for displaying
+in log buffer."
+ (mapconcat 'identity
+ (mapcar 'magit-parse-log-ref
+ (remove-if (lambda (refname)
+ (string-match "/HEAD$" refname))
+ (reverse (split-string refstring ", *" t))))
+ " - "))
+
+(defun magit-wash-log-line ()
+ (if (and (search-forward-regexp "[0-9a-fA-F]\\{40\\}" (line-end-position) t)
+ (goto-char (match-beginning 0))
+ (not (looking-back "commit ")))
+ (let ((commit (match-string-no-properties 0)))
+ (delete-region (match-beginning 0) (match-end 0))
+ (fixup-whitespace)
+ (goto-char (line-beginning-position))
+ (when (search-forward-regexp "^[|*\\/ ]+\\((\\(tag:.+?\\|refs/.+?\\))\\)"
+ (line-end-position) t)
+ (let ((refstring (match-string-no-properties 2)))
+ (delete-region (match-beginning 1) (match-end 1))
+ (insert (magit-parse-log-refs refstring)))
+ (goto-char (line-beginning-position)))
+ (magit-with-section commit 'commit
+ (magit-set-section-info commit)
+ (forward-line)))
+ (forward-line))
+ t)
+
+(defun magit-wash-log ()
+ (let ((magit-old-top-section nil))
+ (magit-wash-sequence #'magit-wash-log-line)))
+
+(defvar magit-currently-shown-commit nil)
+
+(defun magit-wash-commit ()
+ (cond ((search-forward-regexp "^diff" nil t)
+ (goto-char (match-beginning 0))
+ (magit-wash-diffs))))
+
+(defun magit-refresh-commit-buffer (commit)
+ (magit-create-buffer-sections
+ (magit-git-section nil nil
+ 'magit-wash-commit
+ "log" "--max-count=1"
+ "--pretty=medium"
+ "--cc" "-p" commit)))
+
+(defun magit-show-commit (commit &optional scroll)
+ (when (magit-section-p commit)
+ (setq commit (magit-section-info commit)))
+ (let ((dir default-directory)
+ (buf (get-buffer-create "*magit-commit*")))
+ (cond ((equal magit-currently-shown-commit commit)
+ (let ((win (get-buffer-window buf)))
+ (cond ((not win)
+ (display-buffer buf))
+ (scroll
+ (with-selected-window win
+ (funcall scroll))))))
+ (t
+ (setq magit-currently-shown-commit commit)
+ (display-buffer buf)
+ (with-current-buffer buf
+ (set-buffer buf)
+ (goto-char (point-min))
+ (magit-mode-init dir 'commit
+ #'magit-refresh-commit-buffer commit))))))
+
+(defvar magit-marked-commit nil)
+
+(defvar magit-mark-overlay nil)
+(make-variable-buffer-local 'magit-mark-overlay)
+(put 'magit-mark-overlay 'permanent-local t)
+
+(defun magit-refresh-marked-commits ()
+ (magit-for-all-buffers #'magit-refresh-marked-commits-in-buffer))
+
+(defun magit-refresh-marked-commits-in-buffer ()
+ (if (not magit-mark-overlay)
+ (let ((ov (make-overlay 1 1)))
+ (overlay-put ov 'face 'magit-item-mark)
+ (setq magit-mark-overlay ov)))
+ (delete-overlay magit-mark-overlay)
+ (magit-for-all-sections
+ (lambda (section)
+ (when (and (eq (magit-section-type section) 'commit)
+ (equal (magit-section-info section)
+ magit-marked-commit))
+ (move-overlay magit-mark-overlay
+ (magit-section-beginning section)
+ (magit-section-end section)
+ (current-buffer))))))
+
+(defun magit-set-marked-commit (commit)
+ (setq magit-marked-commit commit)
+ (magit-refresh-marked-commits))
+
+(defun magit-marked-commit ()
+ (or magit-marked-commit
+ (error "No commit marked")))
+
+(defun magit-insert-unpulled-commits (remote branch)
+ (magit-git-section 'unpulled
+ "Unpulled commits:" 'magit-wash-log
+ "log" "--pretty=format:* %H %s"
+ (format "HEAD..%s/%s" remote branch)))
+
+(defun magit-insert-unpushed-commits (remote branch)
+ (magit-git-section 'unpushed
+ "Unpushed commits:" 'magit-wash-log
+ "log" "--pretty=format:* %H %s"
+ (format "%s/%s..HEAD" remote branch)))
+
+(defun magit-insert-unpulled-svn-commits ()
+ (magit-git-section 'svn-unpulled
+ "Unpulled commits (SVN):" 'magit-wash-log
+ "log" "--pretty=format:* %H %s"
+ (format "HEAD..%s" (magit-get-svn-ref))))
+
+(defun magit-insert-unpushed-svn-commits ()
+ (magit-git-section 'svn-unpushed
+ "Unpushed commits (SVN):" 'magit-wash-log
+ "log" "--pretty=format:* %H %s"
+ (format "%s..HEAD" (magit-get-svn-ref))))
+
+;;; Status
+
+(defun magit-refresh-status ()
+ (magit-create-buffer-sections
+ (magit-with-section 'status nil
+ (let* ((branch (magit-get-current-branch))
+ (remote (and branch (magit-get "branch" branch "remote")))
+ (svn-enabled (magit-svn-enabled))
+ (head (magit-git-string
+ "log" "--max-count=1" "--abbrev-commit" "--pretty=oneline"))
+ (no-commit (not head)))
+ (if remote
+ (insert (format "Remote: %s %s\n"
+ remote (magit-get "remote" remote "url"))))
+ (insert (format "Local: %s %s\n"
+ (propertize (or branch "(detached)")
+ 'face 'magit-branch)
+ (abbreviate-file-name default-directory)))
+ (insert (format "Head: %s\n"
+ (if no-commit "nothing commited (yet)" head)))
+ (let ((merge-heads (magit-file-lines ".git/MERGE_HEAD")))
+ (if merge-heads
+ (insert (format "Merging: %s\n"
+ (magit-concat-with-delim
+ ", "
+ (mapcar 'magit-name-rev merge-heads))))))
+ (let ((rebase (magit-rebase-info)))
+ (if rebase
+ (insert (apply 'format "Rebasing: %s (%s of %s)\n" rebase))))
+ (insert "\n")
+ (magit-git-exit-code "update-index" "--refresh")
+ (magit-insert-untracked-files)
+ (magit-insert-stashes)
+ (magit-insert-topics)
+ (magit-insert-pending-changes)
+ (magit-insert-pending-commits)
+ (when remote
+ (magit-insert-unpulled-commits remote branch))
+ (when svn-enabled
+ (magit-insert-unpulled-svn-commits))
+ (let ((staged (or no-commit (magit-anything-staged-p))))
+ (magit-insert-unstaged-changes
+ (if staged "Unstaged changes:" "Changes:"))
+ (if staged
+ (magit-insert-staged-changes no-commit)))
+ (when remote
+ (magit-insert-unpushed-commits remote branch))
+ (when svn-enabled
+ (magit-insert-unpushed-svn-commits))))))
+
+(defun magit-init (dir)
+ "Initialize git repository in specified directory"
+ (interactive (list (read-directory-name "Directory for Git repository: ")))
+ (let ((topdir (magit-get-top-dir dir)))
+ (when (or (not topdir)
+ (yes-or-no-p
+ (format
+ (if (string-equal topdir (expand-file-name dir))
+ "There is already a Git repository in %s. Reinitialize? "
+ "There is a Git repository in %s. Create another in %s? ")
+ topdir dir)))
+ (unless (file-directory-p dir)
+ (and (y-or-n-p (format "Directory %s does not exists. Create it? " dir))
+ (make-directory dir)))
+ (let ((default-directory dir))
+ (magit-run* (list "git" "init"))))))
+
+;;;###autoload
+(defun magit-status (dir)
+ (interactive (list (or (and (not current-prefix-arg)
+ (magit-get-top-dir default-directory))
+ (magit-read-top-dir (and (consp current-prefix-arg)
+ (> (car current-prefix-arg) 4))))))
+ (if magit-save-some-buffers
+ (save-some-buffers (eq magit-save-some-buffers 'dontask)))
+ (let ((topdir (magit-get-top-dir dir)))
+ (unless topdir
+ (when (y-or-n-p (format "There is no Git repository in %S. Create one? "
+ dir))
+ (magit-init dir)
+ (setq topdir (magit-get-top-dir dir))))
+ (when topdir
+ (let ((buf (or (magit-find-buffer 'status topdir)
+ (switch-to-buffer
+ (get-buffer-create
+ (concat "*magit: "
+ (file-name-nondirectory
+ (directory-file-name topdir)) "*"))))))
+ (switch-to-buffer buf)
+ (magit-mode-init topdir 'status #'magit-refresh-status)))))
+
+;;; Staging and Unstaging
+
+(defun magit-stage-item ()
+ "Add the item at point to the staging area."
+ (interactive)
+ (magit-section-action (item info "stage")
+ ((untracked file)
+ (magit-run-git "add" info))
+ ((untracked)
+ (apply #'magit-run-git "add" "--"
+ (magit-git-lines "ls-files" "--other" "--exclude-standard")))
+ ((unstaged diff hunk)
+ (if (magit-hunk-item-is-conflict-p item)
+ (error (concat "Can't stage individual resolution hunks. "
+ "Please stage the whole file.")))
+ (magit-apply-hunk-item item "--cached"))
+ ((unstaged diff)
+ (magit-run-git "add" "-u" (magit-diff-item-file item)))
+ ((staged *)
+ (error "Already staged"))
+ ((hunk)
+ (error "Can't stage this hunk"))
+ ((diff)
+ (error "Can't stage this diff"))))
+
+(defun magit-unstage-item ()
+ "Remove the item at point from the staging area."
+ (interactive)
+ (magit-section-action (item info "unstage")
+ ((staged diff hunk)
+ (magit-apply-hunk-item-reverse item "--cached"))
+ ((staged diff)
+ (if (eq (car info) 'unmerged)
+ (error "Can't unstage a unmerged file. Resolve it first."))
+ (magit-run-git "reset" "-q" "HEAD" "--" (magit-diff-item-file item)))
+ ((unstaged *)
+ (error "Already unstaged"))
+ ((hunk)
+ (error "Can't unstage this hunk"))
+ ((diff)
+ (error "Can't unstage this diff"))))
+
+(defun magit-stage-all (&optional also-untracked-p)
+ (interactive "P")
+ (if also-untracked-p
+ (magit-run-git "add" ".")
+ (magit-run-git "add" "-u" ".")))
+
+(defun magit-unstage-all ()
+ (interactive)
+ (magit-run-git "reset" "HEAD"))
+
+;;; Branches
+
+(defun magit-maybe-create-local-tracking-branch (rev)
+ (if (string-match "^refs/remotes/\\([^/]+\\)/\\(.+\\)" rev)
+ (let ((remote (match-string 1 rev))
+ (branch (match-string 2 rev)))
+ (when (and (not (magit-ref-exists-p (concat "refs/heads/" branch)))
+ (yes-or-no-p
+ (format "Create local tracking branch for %s? " branch)))
+ (magit-run-git "checkout" "-b" branch rev)
+ t))
+ nil))
+
+(defun magit-checkout (rev)
+ (interactive (list (magit-read-rev "Switch to" (magit-default-rev))))
+ (if rev
+ (if (not (magit-maybe-create-local-tracking-branch rev))
+ (magit-run-git "checkout" (magit-rev-to-git rev)))))
+
+(defun magit-read-create-branch-args ()
+ (let* ((cur-branch (magit-get-current-branch))
+ (branch (read-string "Create branch: "))
+ (parent (magit-read-rev "Parent" cur-branch)))
+ (list branch parent)))
+
+(defun magit-create-branch (branch parent)
+ (interactive (magit-read-create-branch-args))
+ (if (and branch (not (string= branch ""))
+ parent)
+ (magit-run-git "checkout" "-b"
+ branch
+ (magit-rev-to-git parent))))
+
+;;; Merging
+
+(defun magit-guess-branch ()
+ (let ((sec (magit-current-section)))
+ (if (and sec (eq (magit-section-type sec) 'wazzup))
+ (magit-section-info sec))))
+
+(defun magit-manual-merge (rev)
+ (interactive (list (magit-read-rev "Manually merge" (magit-guess-branch))))
+ (if rev
+ (magit-run-git "merge" "--no-ff" "--no-commit"
+ (magit-rev-to-git rev))))
+
+(defun magit-automatic-merge (rev)
+ (interactive (list (magit-read-rev "Merge" (magit-guess-branch))))
+ (if rev
+ (magit-run-git "merge" (magit-rev-to-git rev))))
+
+;;; Rebasing
+
+(defun magit-rebase-info ()
+ (cond ((file-exists-p ".git/rebase-apply")
+ (list (magit-name-rev
+ (car (magit-file-lines ".git/rebase-apply/onto")))
+ (car (magit-file-lines ".git/rebase-apply/next"))
+ (car (magit-file-lines ".git/rebase-apply/last"))))
+ ((file-exists-p ".dotest")
+ (list (magit-name-rev (car (magit-file-lines ".dotest/onto")))
+ (car (magit-file-lines ".dotest/next"))
+ (car (magit-file-lines ".dotest/last"))))
+ ((file-exists-p ".git/.dotest-merge")
+ (list (car (magit-file-lines ".git/.dotest-merge/onto_name"))
+ (car (magit-file-lines ".git/.dotest-merge/msgnum"))
+ (car (magit-file-lines ".git/.dotest-merge/end"))))
+ (t
+ nil)))
+
+(defun magit-rebase-step ()
+ (interactive)
+ (let ((info (magit-rebase-info)))
+ (if (not info)
+ (let ((rev (magit-read-rev "Rebase to")))
+ (if rev
+ (magit-run-git "rebase" (magit-rev-to-git rev))))
+ (let ((cursor-in-echo-area t)
+ (message-log-max nil))
+ (message "Rebase in progress. Abort, Skip, or Continue? ")
+ (let ((reply (read-event)))
+ (case reply
+ ((?A ?a)
+ (magit-run-git "rebase" "--abort"))
+ ((?S ?s)
+ (magit-run-git "rebase" "--skip"))
+ ((?C ?c)
+ (magit-run-git "rebase" "--continue"))))))))
+
+;; git svn commands
+
+(defun magit-svn-rebase ()
+ (interactive)
+ (magit-run-git-async "svn" "rebase"))
+
+(defun magit-svn-dcommit ()
+ (interactive)
+ (magit-run-git-async "svn" "dcommit"))
+
+(defun magit-svn-enabled ()
+ (not (null (magit-get-svn-ref))))
+
+(defun magit-get-svn-ref ()
+ (cond ((magit-ref-exists-p "refs/remotes/git-svn")
+ "refs/remotes/git-svn")
+ ((magit-ref-exists-p "refs/remotes/trunk")
+ "refs/remotes/trunk")
+ (t
+ nil)))
+
+;;; Resetting
+
+(defun magit-reset-head (rev &optional hard)
+ (interactive (list (magit-read-rev (format "%s head to"
+ (if current-prefix-arg
+ "Hard reset"
+ "Reset"))
+ (or (magit-default-rev)
+ "HEAD^"))
+ current-prefix-arg))
+ (if rev
+ (magit-run-git "reset" (if hard "--hard" "--soft")
+ (magit-rev-to-git rev))))
+
+(defun magit-reset-working-tree ()
+ (interactive)
+ (if (yes-or-no-p "Discard all uncommitted changes? ")
+ (magit-run-git "reset" "--hard")))
+
+;;; Rewriting
+
+(defun magit-read-rewrite-info ()
+ (when (file-exists-p ".git/magit-rewrite-info")
+ (with-temp-buffer
+ (insert-file-contents ".git/magit-rewrite-info")
+ (goto-char (point-min))
+ (read (current-buffer)))))
+
+(defun magit-write-rewrite-info (info)
+ (with-temp-file ".git/magit-rewrite-info"
+ (prin1 info (current-buffer))
+ (princ "\n" (current-buffer))))
+
+(defun magit-insert-pending-commits ()
+ (let* ((info (magit-read-rewrite-info))
+ (pending (cdr (assq 'pending info))))
+ (when pending
+ (magit-with-section 'pending nil
+ (insert (propertize "Pending commits:\n"
+ 'face 'magit-section-title))
+ (dolist (p pending)
+ (let* ((commit (car p))
+ (properties (cdr p))
+ (used (plist-get properties 'used)))
+ (magit-with-section commit 'commit
+ (magit-set-section-info commit)
+ (insert (magit-git-string
+ "log" "--max-count=1"
+ (if used
+ "--pretty=format:. %s"
+ "--pretty=format:* %s")
+ commit "--")
+ "\n")))))
+ (insert "\n"))))
+
+(defun magit-rewrite-set-commit-property (commit prop value)
+ (let* ((info (magit-read-rewrite-info))
+ (pending (cdr (assq 'pending info)))
+ (p (assoc commit pending)))
+ (when p
+ (setf (cdr p) (plist-put (cdr p) prop value))
+ (magit-write-rewrite-info info)
+ (magit-need-refresh))))
+
+(defun magit-rewrite-set-used ()
+ (interactive)
+ (magit-section-action (item info)
+ ((pending commit)
+ (magit-rewrite-set-commit-property info 'used t))))
+
+(defun magit-rewrite-set-unused ()
+ (interactive)
+ (magit-section-action (item info)
+ ((pending commit)
+ (magit-rewrite-set-commit-property info 'used nil))))
+
+(defun magit-insert-pending-changes ()
+ (let* ((info (magit-read-rewrite-info))
+ (orig (cadr (assq 'orig info))))
+ (when orig
+ (let ((magit-hide-diffs t))
+ (magit-git-section 'pending-changes
+ "Pending changes"
+ 'magit-wash-diffs
+ "diff" (magit-diff-U-arg) "-R" orig)))))
+
+(defun magit-rewrite-start (from &optional onto)
+ (interactive (list (magit-read-rev "Rewrite from" (magit-default-rev))))
+ (or (magit-everything-clean-p)
+ (error "You have uncommitted changes."))
+ (or (not (magit-read-rewrite-info))
+ (error "Rewrite in progress."))
+ (let* ((orig (magit-git-string "rev-parse" "HEAD"))
+ (base (or (car (magit-commit-parents from))
+ (error "Can't rewrite a commit without a parent, sorry.")))
+ (pending (magit-git-lines "rev-list" (concat base ".."))))
+ (magit-write-rewrite-info `((orig ,orig)
+ (pending ,@(mapcar #'list pending))))
+ (magit-run-git "reset" "--hard" base)))
+
+(defun magit-rewrite-stop (&optional noconfirm)
+ (interactive)
+ (let* ((info (magit-read-rewrite-info)))
+ (or info
+ (error "No rewrite in progress."))
+ (when (or noconfirm
+ (yes-or-no-p "Stop rewrite? "))
+ (magit-write-rewrite-info nil)
+ (magit-need-refresh))))
+
+(defun magit-rewrite-abort ()
+ (interactive)
+ (let* ((info (magit-read-rewrite-info))
+ (orig (cadr (assq 'orig info))))
+ (or info
+ (error "No rewrite in progress."))
+ (or (magit-everything-clean-p)
+ (error "You have uncommitted changes."))
+ (when (yes-or-no-p "Abort rewrite? ")
+ (magit-write-rewrite-info nil)
+ (magit-run-git "reset" "--hard" orig))))
+
+(defun magit-rewrite-finish ()
+ (interactive)
+ (magit-with-refresh
+ (magit-rewrite-finish-step t)))
+
+(defun magit-rewrite-finish-step (first-p)
+ (let ((info (magit-read-rewrite-info)))
+ (or info
+ (error "No rewrite in progress."))
+ (let* ((pending (cdr (assq 'pending info)))
+ (first-unused (find-if (lambda (p)
+ (not (plist-get (cdr p) 'used)))
+ pending
+ :from-end t))
+ (commit (car first-unused)))
+ (cond ((not first-unused)
+ (magit-rewrite-stop t))
+ ((magit-apply-commit commit t (not first-p))
+ (magit-rewrite-set-commit-property commit 'used t)
+ (magit-rewrite-finish-step nil))))))
+
+;;; Updating, pull, and push
+
+(defun magit-remote-update ()
+ (interactive)
+ (if (magit-svn-enabled)
+ (magit-run-git-async "svn" "fetch")
+ (magit-run-git-async "remote" "update")))
+
+(defun magit-pull ()
+ (interactive)
+ (let* ((branch (magit-get-current-branch))
+ (config-branch (and branch (magit-get "branch" branch "merge")))
+ (merge-branch (or config-branch
+ (magit-read-rev (format "Pull from")))))
+ (if (and branch (not config-branch))
+ (magit-set merge-branch "branch" branch "merge"))
+ (magit-run-git-async "pull" "-v")))
+
+(defun magit-shell-command (command)
+ (interactive "sCommand: ")
+ (require 'pcomplete)
+ (let ((args (car (with-temp-buffer
+ (insert command)
+ (pcomplete-parse-buffer-arguments))))
+ (magit-process-popup-time 0))
+ (magit-run* args nil nil nil t)))
+
+(defun magit-read-remote (prompt def)
+ (completing-read (if def
+ (format "%s (default %s): " prompt def)
+ (format "%s: " prompt))
+ (magit-git-lines "remote")
+ nil nil nil nil def))
+
+(defun magit-push ()
+ (interactive)
+ (let* ((branch (or (magit-get-current-branch)
+ (error "Don't push a detached head. That's gross.")))
+ (branch-remote (magit-get "branch" branch "remote"))
+ (push-remote (if (or current-prefix-arg
+ (not branch-remote))
+ (magit-read-remote (format "Push %s to" branch)
+ branch-remote)
+ branch-remote)))
+ (if (and (not branch-remote)
+ (not current-prefix-arg))
+ (magit-set push-remote "branch" branch "remote"))
+ (magit-run-git-async "push" "-v" push-remote branch)))
+
+;;; Log edit mode
+
+(defvar magit-log-edit-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "C-c C-c") 'magit-log-edit-commit)
+ (define-key map (kbd "C-c C-a") 'magit-log-edit-toggle-amending)
+ (define-key map (kbd "C-c C-s") 'magit-log-edit-toggle-signoff)
+ (define-key map (kbd "M-p") 'log-edit-previous-comment)
+ (define-key map (kbd "M-n") 'log-edit-next-comment)
+ (define-key map (kbd "C-c C-k") 'magit-log-edit-cancel-log-message)
+ map))
+
+(defvar magit-pre-log-edit-window-configuration nil)
+
+(defun magit-log-fill-paragraph (&optional justify)
+ "Fill the paragraph, but preserve open parentheses at beginning of lines.
+Prefix arg means justify as well."
+ (interactive "P")
+ ;; Add lines starting with a left paren or an asterisk.
+ (let ((paragraph-start (concat paragraph-start "\\|*\\|(")))
+ (let ((end (progn (forward-paragraph) (point)))
+ (beg (progn (backward-paragraph) (point)))
+ (adaptive-fill-mode nil))
+ (fill-region beg end justify)
+ t)))
+
+(define-derived-mode magit-log-edit-mode text-mode "Magit Log Edit"
+ (set (make-local-variable 'fill-paragraph-function)
+ 'magit-log-fill-paragraph)
+ (use-local-map magit-log-edit-map))
+
+(defun magit-log-edit-cleanup ()
+ (save-excursion
+ (goto-char (point-min))
+ (flush-lines "^#")
+ (goto-char (point-min))
+ (if (re-search-forward "[ \t\n]*\\'" nil t)
+ (replace-match "\n" nil nil))))
+
+(defun magit-log-edit-append (str)
+ (save-excursion
+ (set-buffer (get-buffer-create "*magit-log-edit*"))
+ (goto-char (point-max))
+ (insert str "\n")))
+
+(defconst magit-log-header-end "-- End of Magit header --\n")
+
+(defun magit-log-edit-get-fields ()
+ (let ((buf (get-buffer "*magit-log-edit*"))
+ (result nil))
+ (if buf
+ (save-excursion
+ (set-buffer buf)
+ (goto-char (point-min))
+ (while (looking-at "^\\([A-Za-z0-9-_]+\\): *\\(.*\\)$")
+ (setq result (acons (intern (downcase (match-string 1)))
+ (match-string 2)
+ result))
+ (forward-line))
+ (if (not (looking-at (regexp-quote magit-log-header-end)))
+ (setq result nil))))
+ (nreverse result)))
+
+(defun magit-log-edit-set-fields (fields)
+ (let ((buf (get-buffer-create "*magit-log-edit*")))
+ (save-excursion
+ (set-buffer buf)
+ (goto-char (point-min))
+ (if (search-forward-regexp (format "^\\([A-Za-z0-9-_]+:.*\n\\)+%s"
+ (regexp-quote magit-log-header-end))
+ nil t)
+ (delete-region (match-beginning 0) (match-end 0)))
+ (goto-char (point-min))
+ (when fields
+ (while fields
+ (insert (capitalize (symbol-name (caar fields))) ": "
+ (cdar fields) "\n")
+ (setq fields (cdr fields)))
+ (insert magit-log-header-end)))))
+
+(defun magit-log-edit-set-field (name value)
+ (let* ((fields (magit-log-edit-get-fields))
+ (cell (assq name fields)))
+ (cond (cell
+ (if value
+ (rplacd cell value)
+ (setq fields (delq cell fields))))
+ (t
+ (if value
+ (setq fields (append fields (list (cons name value)))))))
+ (magit-log-edit-set-fields fields)))
+
+(defun magit-log-edit-get-field (name)
+ (cdr (assq name (magit-log-edit-get-fields))))
+
+(defun magit-log-edit-setup-author-env (author)
+ (cond (author
+ ;; XXX - this is a bit strict, probably.
+ (or (string-match "\\(.*\\) <\\(.*\\)>, \\(.*\\)" author)
+ (error "Can't parse author string."))
+ ;; Shucks, setenv destroys the match data.
+ (let ((name (match-string 1 author))
+ (email (match-string 2 author))
+ (date (match-string 3 author)))
+ (setenv "GIT_AUTHOR_NAME" name)
+ (setenv "GIT_AUTHOR_EMAIL" email)
+ (setenv "GIT_AUTHOR_DATE" date)))
+ (t
+ (setenv "GIT_AUTHOR_NAME")
+ (setenv "GIT_AUTHOR_EMAIL")
+ (setenv "GIT_AUTHOR_DATE"))))
+
+(defun magit-log-edit-push-to-comment-ring (comment)
+ (when (or (ring-empty-p log-edit-comment-ring)
+ (not (equal comment (ring-ref log-edit-comment-ring 0))))
+ (ring-insert log-edit-comment-ring comment)))
+
+(defun magit-log-edit-commit ()
+ (interactive)
+ (let* ((fields (magit-log-edit-get-fields))
+ (amend (equal (cdr (assq 'amend fields)) "yes"))
+ (commit-all (equal (cdr (assq 'commit-all fields)) "yes"))
+ (sign-off-field (assq 'sign-off fields))
+ (sign-off (if sign-off-field
+ (equal (cdr sign-off-field) "yes")
+ magit-commit-signoff))
+ (tag (cdr (assq 'tag fields)))
+ (author (cdr (assq 'author fields))))
+ (magit-log-edit-push-to-comment-ring (buffer-string))
+ (magit-log-edit-setup-author-env author)
+ (magit-log-edit-set-fields nil)
+ (magit-log-edit-cleanup)
+ (if (= (buffer-size) 0)
+ (insert "(Empty description)\n"))
+ (let ((commit-buf (current-buffer)))
+ (with-current-buffer (magit-find-buffer 'status default-directory)
+ (cond (tag
+ (magit-run-git-with-input commit-buf "tag" tag "-a" "-F" "-"))
+ (t
+ (apply #'magit-run-async-with-input commit-buf
+ magit-git-executable
+ (append magit-git-standard-options
+ (list "commit" "-F" "-")
+ (if commit-all '("--all") '())
+ (if amend '("--amend") '())
+ (if sign-off '("--signoff") '())))))))
+ (erase-buffer)
+ (bury-buffer)
+ (when (file-exists-p ".git/MERGE_MSG")
+ (delete-file ".git/MERGE_MSG"))
+ (when magit-pre-log-edit-window-configuration
+ (set-window-configuration magit-pre-log-edit-window-configuration)
+ (setq magit-pre-log-edit-window-configuration nil))))
+
+(defun magit-log-edit-cancel-log-message ()
+ (interactive)
+ (when (or (not magit-log-edit-confirm-cancellation)
+ (yes-or-no-p
+ "Really cancel editing the log (any changes will be lost)?"))
+ (erase-buffer)
+ (bury-buffer)
+ (when magit-pre-log-edit-window-configuration
+ (set-window-configuration magit-pre-log-edit-window-configuration)
+ (setq magit-pre-log-edit-window-configuration nil))))
+
+(defun magit-log-edit-toggle-amending ()
+ (interactive)
+ (let* ((fields (magit-log-edit-get-fields))
+ (cell (assq 'amend fields)))
+ (if cell
+ (rplacd cell (if (equal (cdr cell) "yes") "no" "yes"))
+ (setq fields (acons 'amend "yes" fields))
+ (magit-log-edit-append
+ (magit-format-commit "HEAD" "%s%n%n%b")))
+ (magit-log-edit-set-fields fields)))
+
+(defun magit-log-edit-toggle-signoff ()
+ (interactive)
+ (let* ((fields (magit-log-edit-get-fields))
+ (cell (assq 'sign-off fields)))
+ (if cell
+ (rplacd cell (if (equal (cdr cell) "yes") "no" "yes"))
+ (setq fields (acons 'sign-off (if magit-commit-signoff "no" "yes")
+ fields)))
+ (magit-log-edit-set-fields fields)))
+
+(defun magit-pop-to-log-edit (operation)
+ (let ((dir default-directory)
+ (buf (get-buffer-create "*magit-log-edit*")))
+ (setq magit-pre-log-edit-window-configuration
+ (current-window-configuration))
+ (pop-to-buffer buf)
+ (when (file-exists-p ".git/MERGE_MSG")
+ (insert-file-contents ".git/MERGE_MSG"))
+ (setq default-directory dir)
+ (magit-log-edit-mode)
+ (message "Type C-c C-c to %s (C-c C-k to cancel)." operation)))
+
+(defun magit-log-edit ()
+ (interactive)
+ (cond ((magit-rebase-info)
+ (if (y-or-n-p "Rebase in progress. Continue it? ")
+ (magit-run-git "rebase" "--continue")))
+ (t
+ (magit-log-edit-set-field 'tag nil)
+ (when (and magit-commit-all-when-nothing-staged
+ (not (magit-anything-staged-p)))
+ (cond ((eq magit-commit-all-when-nothing-staged 'ask-stage)
+ (if (and (not (magit-everything-clean-p))
+ (y-or-n-p "Nothing staged. Stage everything now? "))
+ (magit-stage-all)))
+ ((not (magit-log-edit-get-field 'commit-all))
+ (magit-log-edit-set-field
+ 'commit-all
+ (if (or (eq magit-commit-all-when-nothing-staged t)
+ (y-or-n-p
+ "Nothing staged. Commit all unstaged changes? "))
+ "yes" "no")))))
+ (magit-pop-to-log-edit "commit"))))
+
+(defun magit-add-log ()
+ (interactive)
+ (cond ((magit-rebase-info)
+ (if (y-or-n-p "Rebase in progress. Continue it? ")
+ (magit-run-git "rebase" "--continue")))
+ (t
+ (let ((section (magit-current-section)))
+ (let ((fun (if (eq (magit-section-type section) 'hunk)
+ (save-window-excursion
+ (save-excursion
+ (magit-visit-item)
+ (add-log-current-defun)))
+ nil))
+ (file (magit-diff-item-file
+ (cond ((eq (magit-section-type section) 'hunk)
+ (magit-hunk-item-diff section))
+ ((eq (magit-section-type section) 'diff)
+ section)
+ (t
+ (error "No change at point"))))))
+ (magit-log-edit)
+ (goto-char (point-min))
+ (cond ((not (search-forward-regexp
+ (format "^\\* %s" (regexp-quote file)) nil t))
+ ;; No entry for file, create it.
+ (goto-char (point-max))
+ (insert (format "\n* %s" file))
+ (if fun
+ (insert (format " (%s)" fun)))
+ (insert ": "))
+ (fun
+ ;; found entry for file, look for fun
+ (let ((limit (or (save-excursion
+ (and (search-forward-regexp "^\\* "
+ nil t)
+ (match-beginning 0)))
+ (point-max))))
+ (cond ((search-forward-regexp (format "(.*\\<%s\\>.*):"
+ (regexp-quote fun))
+ limit t)
+ ;; found it, goto end of current entry
+ (if (search-forward-regexp "^(" limit t)
+ (backward-char 2)
+ (goto-char limit)))
+ (t
+ ;; not found, insert new entry
+ (goto-char limit)
+ (if (bolp)
+ (open-line 1)
+ (newline))
+ (insert (format "(%s): " fun))))))))))))
+
+;;; Tags
+
+(defun magit-tag (name)
+ (interactive "sNew tag name: ")
+ (magit-run-git "tag" name))
+
+(defun magit-annotated-tag (name)
+ (interactive "sNew tag name: ")
+ (magit-log-edit-set-field 'tag name)
+ (magit-pop-to-log-edit "tag"))
+
+;;; Stashing
+
+(defun magit-wash-stash ()
+ (if (search-forward-regexp "stash@{\\(.*\\)}" (line-end-position) t)
+ (let ((stash (match-string-no-properties 0))
+ (name (match-string-no-properties 1)))
+ (delete-region (match-beginning 0) (match-end 0))
+ (goto-char (match-beginning 0))
+ (fixup-whitespace)
+ (goto-char (line-beginning-position))
+ (insert name)
+ (goto-char (line-beginning-position))
+ (magit-with-section stash 'stash
+ (magit-set-section-info stash)
+ (forward-line)))
+ (forward-line))
+ t)
+
+(defun magit-wash-stashes ()
+ (let ((magit-old-top-section nil))
+ (magit-wash-sequence #'magit-wash-stash)))
+
+(defun magit-insert-stashes ()
+ (magit-git-section 'stashes
+ "Stashes:" 'magit-wash-stashes
+ "stash" "list"))
+
+(defun magit-stash (description)
+ (interactive "sStash description: ")
+ (magit-run-git "stash" "save" description))
+
+(defun magit-stash-snapshot ()
+ (interactive)
+ (magit-with-refresh
+ (magit-run-git "stash" "save"
+ (format-time-string "Snapshot taken at %Y-%m-%d %H:%M:%S"
+ (current-time)))
+ (magit-run-git "stash" "apply" "stash@{0}")))
+
+(defvar magit-currently-shown-stash nil)
+
+(defun magit-show-stash (stash &optional scroll)
+ (when (magit-section-p stash)
+ (setq stash (magit-section-info stash)))
+ (let ((dir default-directory)
+ (buf (get-buffer-create "*magit-stash*")))
+ (cond ((equal magit-currently-shown-stash stash)
+ (let ((win (get-buffer-window buf)))
+ (cond ((not win)
+ (display-buffer buf))
+ (scroll
+ (with-selected-window win
+ (funcall scroll))))))
+ (t
+ (setq magit-currently-shown-stash stash)
+ (display-buffer buf)
+ (with-current-buffer buf
+ (set-buffer buf)
+ (goto-char (point-min))
+ (let* ((range (cons (concat stash "^2^") stash))
+ (args (magit-rev-range-to-git range)))
+ (magit-mode-init dir 'diff #'magit-refresh-diff-buffer
+ range args)))))))
+
+;;; Topic branches (using topgit)
+
+(defun magit-wash-topic ()
+ (if (search-forward-regexp "^..\\(t/\\S-+\\)\\s-+\\(\\S-+\\)\\s-+\\(\\S-+\\)"
+ (line-end-position) t)
+ (let ((topic (match-string 1)))
+ (delete-region (match-beginning 2) (match-end 2))
+ (goto-char (line-beginning-position))
+ (delete-char 4)
+ (insert "\t")
+ (goto-char (line-beginning-position))
+ (magit-with-section topic 'topic
+ (magit-set-section-info topic)
+ (forward-line)))
+ (delete-region (line-beginning-position) (1+ (line-end-position))))
+ t)
+
+(defun magit-wash-topics ()
+ (let ((magit-old-top-section nil))
+ (magit-wash-sequence #'magit-wash-topic)))
+
+(defun magit-insert-topics ()
+ (magit-git-section 'topics
+ "Topics:" 'magit-wash-topics
+ "branch" "-v"))
+
+;;; Commits
+
+(defun magit-commit-at-point (&optional nil-ok-p)
+ (let* ((section (magit-current-section))
+ (commit (and (eq (magit-section-type section) 'commit)
+ (magit-section-info section))))
+ (if nil-ok-p
+ commit
+ (or commit
+ (error "No commit at point.")))))
+
+(defun magit-apply-commit (commit &optional docommit noerase revert)
+ (let* ((parent-id (magit-choose-parent-id commit "cherry-pick"))
+ (success (magit-run* `(,magit-git-executable
+ ,@magit-git-standard-options
+ ,(if revert "revert" "cherry-pick")
+ ,@(if parent-id
+ (list "-m" (number-to-string parent-id)))
+ ,@(if (not docommit) (list "--no-commit"))
+ ,commit)
+ nil noerase)))
+ (when (or (not docommit) success)
+ (cond (revert
+ (magit-log-edit-append
+ (magit-format-commit commit "Reverting \"%s\"")))
+ (t
+ (magit-log-edit-append
+ (magit-format-commit commit "%s%n%n%b"))
+ (magit-log-edit-set-field
+ 'author
+ (magit-format-commit commit "%an <%ae>, %ai")))))
+ success))
+
+(defun magit-apply-item ()
+ (interactive)
+ (magit-section-action (item info "apply")
+ ((pending commit)
+ (magit-apply-commit info)
+ (magit-rewrite-set-commit-property info 'used t))
+ ((commit)
+ (magit-apply-commit info))
+ ((unstaged *)
+ (error "Change is already in your working tree"))
+ ((staged *)
+ (error "Change is already in your working tree"))
+ ((hunk)
+ (magit-apply-hunk-item item))
+ ((diff)
+ (magit-apply-diff-item item))
+ ((stash)
+ (magit-run-git "stash" "apply" info))))
+
+(defun magit-cherry-pick-item ()
+ (interactive)
+ (magit-section-action (item info "cherry-pick")
+ ((pending commit)
+ (magit-apply-commit info t)
+ (magit-rewrite-set-commit-property info 'used t))
+ ((commit)
+ (magit-apply-commit info t))
+ ((stash)
+ (magit-run-git "stash" "pop" info))))
+
+(defun magit-revert-item ()
+ (interactive)
+ (magit-section-action (item info "revert")
+ ((pending commit)
+ (magit-apply-commit info nil nil t)
+ (magit-rewrite-set-commit-property info 'used nil))
+ ((commit)
+ (magit-apply-commit info nil nil t))
+ ((hunk)
+ (magit-apply-hunk-item-reverse item))
+ ((diff)
+ (magit-apply-diff-item item "--reverse"))))
+
+(defvar magit-have-graph 'unset)
+(defvar magit-have-decorate '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)
+
+(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-refresh-log-buffer (range style args)
+ (magit-configure-have-graph)
+ (magit-configure-have-decorate)
+ (magit-create-buffer-sections
+ (apply #'magit-git-section nil
+ (magit-rev-range-describe range "Commits")
+ 'magit-wash-log
+ `("log"
+ ,(format "--max-count=%s" magit-log-cutoff-length)
+ ,style
+ ,@(if magit-have-decorate (list "--decorate=full"))
+ ,@(if magit-have-graph (list "--graph"))
+ ,args "--"))))
+
+(defun magit-log (&optional arg)
+ (interactive "P")
+ (let* ((range (if arg
+ (magit-read-rev-range "Log" "HEAD")
+ "HEAD"))
+ (topdir (magit-get-top-dir default-directory))
+ (args (magit-rev-range-to-git range)))
+ (switch-to-buffer "*magit-log*")
+ (magit-mode-init topdir 'log #'magit-refresh-log-buffer range
+ "--pretty=oneline" args)))
+
+(defun magit-log-long (&optional arg)
+ (interactive "P")
+ (let* ((range (if arg
+ (magit-read-rev-range "Long log" "HEAD")
+ "HEAD"))
+ (topdir (magit-get-top-dir default-directory))
+ (args (magit-rev-range-to-git range)))
+ (switch-to-buffer "*magit-log*")
+ (magit-mode-init topdir 'log #'magit-refresh-log-buffer range
+ "--stat" args)))
+
+;;; Reflog
+
+(defun magit-refresh-reflog-buffer (head args)
+ (magit-create-buffer-sections
+ (magit-git-section 'reflog
+ (format "Local history of head %s" head)
+ 'magit-wash-log
+ "log" "--walk-reflogs"
+ (format "--max-count=%s" magit-log-cutoff-length)
+ "--pretty=oneline"
+ args)))
+
+(defun magit-reflog (head)
+ (interactive (list (magit-read-rev "Reflog of" "HEAD")))
+ (if head
+ (let* ((topdir (magit-get-top-dir default-directory))
+ (args (magit-rev-to-git head)))
+ (switch-to-buffer "*magit-reflog*")
+ (magit-mode-init topdir 'reflog
+ #'magit-refresh-reflog-buffer head args))))
+
+(defun magit-reflog-head ()
+ (interactive)
+ (magit-reflog "HEAD"))
+
+;;; Diffing
+
+(defun magit-refresh-diff-buffer (range args)
+ (magit-create-buffer-sections
+ (magit-git-section 'diffbuf
+ (magit-rev-range-describe range "Changes")
+ 'magit-wash-diffs
+ "diff" (magit-diff-U-arg) args)))
+
+(defun magit-diff (range)
+ (interactive (list (magit-read-rev-range "Diff")))
+ (if range
+ (let* ((dir default-directory)
+ (args (magit-rev-range-to-git range))
+ (buf (get-buffer-create "*magit-diff*")))
+ (display-buffer buf)
+ (save-excursion
+ (set-buffer buf)
+ (magit-mode-init dir 'diff #'magit-refresh-diff-buffer range args)))))
+
+(defun magit-diff-working-tree (rev)
+ (interactive (list (magit-read-rev "Diff with (default HEAD)")))
+ (magit-diff (or rev "HEAD")))
+
+(defun magit-diff-with-mark ()
+ (interactive)
+ (magit-diff (cons (magit-marked-commit)
+ (magit-commit-at-point))))
+
+;;; Wazzup
+
+(defun magit-wazzup-toggle-ignore (branch edit)
+ (let ((ignore-file ".git/info/wazzup-exclude"))
+ (if edit
+ (setq branch (read-string "Branch to ignore for wazzup: " branch)))
+ (let ((ignored (magit-file-lines ignore-file)))
+ (cond ((member branch ignored)
+ (when (or (not edit)
+ (y-or-n-p "Branch %s is already ignored. Unignore?"))
+ (setq ignored (delete branch ignored))))
+ (t
+ (setq ignored (append ignored (list branch)))))
+ (magit-write-file-lines ignore-file ignored)
+ (magit-need-refresh))))
+
+(defun magit-refresh-wazzup-buffer (head all)
+ (magit-create-buffer-sections
+ (magit-with-section 'wazzupbuf nil
+ (insert (format "Wazzup, %s\n\n" head))
+ (let* ((excluded (magit-file-lines ".git/info/wazzup-exclude"))
+ (all-branches (magit-list-interesting-refs))
+ (branches (if all all-branches
+ (remove-if (lambda (b) (member (cdr b) excluded))
+ all-branches)))
+ (reported (make-hash-table :test #'equal)))
+ (dolist (branch branches)
+ (let* ((name (car branch))
+ (ref (cdr branch))
+ (hash (magit-git-string "rev-parse" ref))
+ (reported-branch (gethash hash reported)))
+ (unless (or (and reported-branch
+ (string= (file-name-nondirectory ref)
+ reported-branch))
+ (not (magit-git-string "merge-base" head ref)))
+ (puthash hash (file-name-nondirectory ref) reported)
+ (let* ((n (length (magit-git-lines "log" "--pretty=oneline"
+ (concat head ".." ref))))
+ (section
+ (let ((magit-section-hidden-default t))
+ (magit-git-section
+ (cons ref 'wazzup)
+ (format "%s unmerged commits in %s%s"
+ n name
+ (if (member ref excluded)
+ " (normally ignored)"
+ ""))
+ 'magit-wash-log
+ "log"
+ (format "--max-count=%s" magit-log-cutoff-length)
+ "--pretty=oneline"
+ (format "%s..%s" head ref)
+ "--"))))
+ (magit-set-section-info ref section)))))))))
+
+(defun magit-wazzup (&optional all)
+ (interactive "P")
+ (let* ((topdir (magit-get-top-dir default-directory)))
+ (switch-to-buffer "*magit-wazzup*")
+ (magit-mode-init topdir 'wazzup
+ #'magit-refresh-wazzup-buffer
+ (magit-get-current-branch) all)))
+
+;;; Miscellaneous
+
+(defun magit-ignore-file (file edit local)
+ (let ((ignore-file (if local ".git/info/exclude" ".gitignore")))
+ (if edit
+ (setq file (read-string "File to ignore: " file)))
+ (append-to-file (concat "/" file "\n") nil ignore-file)
+ (magit-need-refresh)))
+
+(defun magit-ignore-item ()
+ (interactive)
+ (magit-section-action (item info "ignore")
+ ((untracked file)
+ (magit-ignore-file info current-prefix-arg nil))
+ ((wazzup)
+ (magit-wazzup-toggle-ignore info current-prefix-arg))))
+
+(defun magit-ignore-item-locally ()
+ (interactive)
+ (magit-section-action (item info "ignore")
+ ((untracked file)
+ (magit-ignore-file info current-prefix-arg t))))
+
+(defun magit-discard-diff (diff stagedp)
+ (let ((kind (magit-diff-item-kind diff))
+ (file (magit-diff-item-file diff)))
+ (cond ((eq kind 'deleted)
+ (when (yes-or-no-p (format "Resurrect %s? " file))
+ (magit-run-git "reset" "-q" "--" file)
+ (magit-run-git "checkout" "--" file)))
+ ((eq kind 'new)
+ (if (yes-or-no-p (format "Delete %s? " file))
+ (magit-run-git "rm" "-f" "--" file)))
+ (t
+ (if (yes-or-no-p (format "Discard changes to %s? " file))
+ (if stagedp
+ (magit-run-git "checkout" "HEAD" "--" file)
+ (magit-run-git "checkout" "--" file)))))))
+
+(defun magit-discard-item ()
+ (interactive)
+ (magit-section-action (item info "discard")
+ ((untracked file)
+ (if (yes-or-no-p (format "Delete %s? " info))
+ (magit-run "rm" info)))
+ ((untracked)
+ (if (yes-or-no-p "Delete all untracked files and directories? ")
+ (magit-run "git" "clean" "-df")))
+ ((unstaged diff hunk)
+ (when (yes-or-no-p (if (magit-use-region-p)
+ "Discard changes in region? "
+ "Discard hunk? "))
+ (magit-apply-hunk-item-reverse item)))
+ ((staged diff hunk)
+ (if (magit-file-uptodate-p (magit-diff-item-file
+ (magit-hunk-item-diff item)))
+ (when (yes-or-no-p (if (magit-use-region-p)
+ "Discard changes in region? "
+ "Discard hunk? "))
+ (magit-apply-hunk-item-reverse item "--index"))
+ (error "Can't discard this hunk. Please unstage it first.")))
+ ((unstaged diff)
+ (magit-discard-diff item nil))
+ ((staged diff)
+ (if (magit-file-uptodate-p (magit-diff-item-file item))
+ (magit-discard-diff item t)
+ (error "Can't discard staged changes to this file. Please unstage it first.")))
+ ((hunk)
+ (error "Can't discard this hunk"))
+ ((diff)
+ (error "Can't discard this diff"))
+ ((stash)
+ (when (yes-or-no-p "Discard stash? ")
+ (magit-run-git "stash" "drop" info)))))
+
+(defun magit-visit-item ()
+ (interactive)
+ (magit-section-action (item info "visit")
+ ((untracked file)
+ (find-file info))
+ ((diff)
+ (find-file (magit-diff-item-file item)))
+ ((hunk)
+ (let ((file (magit-diff-item-file (magit-hunk-item-diff item)))
+ (line (magit-hunk-item-target-line item)))
+ (find-file file)
+ (goto-line line)))
+ ((commit)
+ (magit-show-commit info)
+ (pop-to-buffer "*magit-commit*"))
+ ((stash)
+ (magit-show-stash info)
+ (pop-to-buffer "*magit-diff*"))
+ ((topic)
+ (magit-checkout info))))
+
+(defun magit-show-item-or-scroll-up ()
+ (interactive)
+ (magit-section-action (item info)
+ ((commit)
+ (magit-show-commit info #'scroll-up))
+ ((stash)
+ (magit-show-stash info #'scroll-up))
+ (t
+ (scroll-up))))
+
+(defun magit-show-item-or-scroll-down ()
+ (interactive)
+ (magit-section-action (item info)
+ ((commit)
+ (magit-show-commit info #'scroll-down))
+ ((stash)
+ (magit-show-stash info #'scroll-down))
+ (t
+ (scroll-down))))
+
+(defun magit-mark-item (&optional unmark)
+ (interactive "P")
+ (if unmark
+ (magit-set-marked-commit nil)
+ (magit-section-action (item info "mark")
+ ((commit)
+ (magit-set-marked-commit (if (eq magit-marked-commit info)
+ nil
+ info))))))
+
+(defun magit-describe-item ()
+ (interactive)
+ (let ((section (magit-current-section)))
+ (message "Section: %s %s-%s %S %S %S"
+ (magit-section-type section)
+ (magit-section-beginning section)
+ (magit-section-end section)
+ (magit-section-title section)
+ (magit-section-info section)
+ (magit-section-context-type section))))
+
+(defun magit-copy-item-as-kill ()
+ "Copy sha1 of commit at point into kill ring."
+ (interactive)
+ (magit-section-action (item info "copy")
+ ((commit)
+ (kill-new info)
+ (message "%s" info))))
+
+(defun magit-interactive-rebase ()
+ "Start a git rebase -i session, old school-style."
+ (interactive)
+ (server-start)
+ (let* ((section (get-text-property (point) 'magit-section))
+ (commit (and (member 'commit (magit-section-context-type section))
+ (magit-section-info section)))
+ (old-editor (getenv "GIT_EDITOR")))
+ (setenv "GIT_EDITOR" (expand-file-name "emacsclient" exec-directory))
+ (unwind-protect
+ (magit-run-git-async "rebase" "-i"
+ (or (and commit (concat commit "^"))
+ (read-string "Interactively rebase to: ")))
+ (if old-editor
+ (setenv "GIT_EDITOR" old-editor)))))
+
+(defun magit-show-branches ()
+ "Show all of the current branches in other-window."
+ (interactive)
+ (save-selected-window
+ (switch-to-buffer-other-window "*magit-branches*")
+ (erase-buffer)
+ (insert (magit-git-string "branch" "-va"))
+ (insert "\n")))
+
+(defvar magit-ediff-file)
+(defvar magit-ediff-windows)
+
+(defun magit-interactive-resolve (file)
+ (let ((merge-status (magit-git-string "ls-files" "-u" "--" file))
+ (base-buffer (generate-new-buffer (concat file ".base")))
+ (our-buffer (generate-new-buffer (concat file ".current")))
+ (their-buffer (generate-new-buffer (concat file ".merged")))
+ (windows (current-window-configuration)))
+ (if (null merge-status)
+ (error "Cannot resolve %s" file))
+ (with-current-buffer base-buffer
+ (if (string-match "^[0-9]+ [0-9a-f]+ 1" merge-status)
+ (insert (magit-git-string "cat-file" "blob"
+ (concat ":1:" file)))))
+ (with-current-buffer our-buffer
+ (if (string-match "^[0-9]+ [0-9a-f]+ 2" merge-status)
+ (insert (magit-git-string "cat-file" "blob"
+ (concat ":2:" file)))))
+ (with-current-buffer their-buffer
+ (if (string-match "^[0-9]+ [0-9a-f]+ 3" merge-status)
+ (insert (magit-git-string "cat-file" "blob"
+ (concat ":3:" file)))))
+ ;; We have now created the 3 buffer with ours, theirs and the ancestor files
+ (with-current-buffer (ediff-merge-buffers-with-ancestor our-buffer their-buffer base-buffer)
+ (make-local-variable 'magit-ediff-file)
+ (setq magit-ediff-file file)
+ (make-local-variable 'magit-ediff-windows)
+ (setq magit-ediff-windows windows)
+ (make-local-variable 'ediff-quit-hook)
+ (add-hook 'ediff-quit-hook
+ (lambda ()
+ (let ((buffer-A ediff-buffer-A)
+ (buffer-B ediff-buffer-B)
+ (buffer-C ediff-buffer-C)
+ (buffer-Ancestor ediff-ancestor-buffer)
+ (file magit-ediff-file)
+ (windows magit-ediff-windows))
+ (ediff-cleanup-mess)
+ (find-file file)
+ (erase-buffer)
+ (insert-buffer-substring buffer-C)
+ (kill-buffer buffer-A)
+ (kill-buffer buffer-B)
+ (kill-buffer buffer-C)
+ (when (bufferp buffer-Ancestor) (kill-buffer buffer-Ancestor))
+ (set-window-configuration windows)
+ (message "Conflict resolution finished; you may save the buffer")))))))
+
+
+(defun magit-interactive-resolve-item ()
+ (interactive)
+ (magit-section-action (item info "resolv")
+ ((diff)
+ (magit-interactive-resolve (cadr info)))))
+
+
+(provide 'magit)