diff options
author | Alexander Sulfrian <alexander@sulfrian.net> | 2009-12-11 17:03:09 +0100 |
---|---|---|
committer | Alexander Sulfrian <alexander@sulfrian.net> | 2009-12-11 17:03:09 +0100 |
commit | a6f973f6c2ec6a518927cd42a9bf769b9a67b818 (patch) | |
tree | eba4e202b47deb495ff0fbe7383809dffc28327c /emacs.d/lisp/magit/magit.el | |
parent | bb0908ad0031fb070306a231d277a3c722890915 (diff) | |
download | dotfiles-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.el | 3244 |
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) |