Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add diffstat on magit-commit-mode and magit-diff-mode #511

Merged
merged 4 commits into from Apr 2, 2013
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
218 changes: 209 additions & 9 deletions magit.el
Expand Up @@ -741,6 +741,7 @@ This is calculated from `magit-highlight-indentation'.")
(define-key map (kbd "+") 'magit-diff-larger-hunks)
(define-key map (kbd "0") 'magit-diff-default-hunks)
(define-key map (kbd "h") 'magit-toggle-diff-refine-hunk)
(define-key map (kbd "M-g") 'magit-goto-diffstats)
map))

(defvar magit-commit-mode-map
Expand Down Expand Up @@ -791,7 +792,11 @@ This is calculated from `magit-highlight-indentation'.")
(define-key map (kbd "T") 'magit-change-what-branch-tracks)
map))


(defvar magit-diffstat-keymap
(let ((map (make-sparse-keymap)))
(define-key map (kbd "e") 'magit-diffstat-ediff)
map))

(defun magit-bug-report (str)
"Ask the user to submit a bug report about the error described in STR."
;; XXX - should propose more information to be included.
Expand Down Expand Up @@ -1447,6 +1452,14 @@ DEF is the default value."

(defvar magit-section-hidden-default nil)

(defvar magit-show-diffstat t
"If non-nil, diff and commit log will display diffstat.")

(defvar magit-diffstat-cached-sections)
(make-variable-buffer-local 'magit-diffstat-cached-sections)
(put 'magit-diffstat-cached-sections 'permanent-local t)


(defun magit-new-section (title type)
"Create a new section with title TITLE and type TYPE in current buffer.

Expand Down Expand Up @@ -1719,6 +1732,31 @@ See `magit-insert-section' for meaning of the arguments"
(goto-char (magit-section-beginning sec))
(message "No such section"))))


(defun magit-goto-diff-section-at-file (file)
"Go to the section containing by the pathname, FILE"
(let ((pos (catch 'diff-section-found
(dolist (sec (magit-section-children magit-top-section))
(when (and (eq (magit-section-type sec) 'diff)
(string-equal (magit-diff-item-file sec) file))
(throw 'diff-section-found
(magit-section-beginning sec)))))))
(when pos
(goto-char pos))))

(defun magit-goto-diffstats ()
"Go to the diffstats section if exists"
(interactive)
(let ((pos (catch 'section-found
(dolist (sec (magit-section-children magit-top-section))
(when (eq (magit-section-type sec) 'diffstats)
(throw 'section-found
(magit-section-beginning sec)))))))
(if pos
(goto-char pos)
(if (called-interactively-p 'interactive)
(message "No diffstats section found")))))

(defun magit-for-all-sections (func &optional top)
"Run FUNC on TOP and recursively on all its children.
Default value for TOP is `magit-top-section'"
Expand Down Expand Up @@ -2732,15 +2770,135 @@ Customize `magit-diff-refine-hunk' to change the default mode."
nil)))

(defun magit-wash-diffs ()
(magit-wash-diffstats)
(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-diffstat-ediff ()
(interactive)
(magit-goto-diff-section-at-file
(magit-diff-item-file (magit-current-section)))
(call-interactively 'magit-ediff))

(defun magit-wash-diffstat (&optional guess)
(let ((entry-regexp "^ ?\\(.*?\\)\\( +| +.*\\)$"))
(when (looking-at entry-regexp)
(let ((file (match-string-no-properties 1))
(remaining (match-string-no-properties 2)))
(delete-region (point) (+ (line-end-position) 1))
(magit-with-section "diffstat" 'diffstat
;;(magit-set-section-info 'incomplete)

;; diffstat entries will look like
;;
;; ' PSEUDO-FILE-NAME | +++---'
;;
;; Since PSEUDO-FILE-NAME is not always real pathname, we
;; don't know the pathname yet. Thus, section info will be
;; (diffstat FILE incomplete MARKER-BEGIN MARKER-END) for
;; now, where MARKER-BEGIN points the beginning of the
;; PSEUDO-FILE-NAME, MARKER-END points the end of the
;; PSEUDO-FILE-NAME, and FILE will be abbreviated filename.
;; Later in `magit-wash-diff-or-other-file`, the section
;; info will be updated.

;; Note that FILE is the 2nd element of the section-info;
;; this is intentional, so that `magit-diff-item-file` can
;; return the FILE part.

;; (list 'diffstat
;; (or (and (> (length file) 3)
;; (string-equal (substring file 0 3) "...")
;; (message "got the invalid file here")
;; 'truncated)
;; file)))

(insert " ")
(let ((f-begin (point-marker)) f-end)
(insert file)
(setq f-end (point-marker))

(magit-set-section-info (list 'diffstat
file 'incomplete f-begin f-end))
(insert remaining)
(magit-put-line-property 'keymap magit-diffstat-keymap)

(insert "\n")
(add-to-list 'magit-diffstat-cached-sections
magit-top-section))

;; (insert (propertize (concat " "
;; (propertize file
;; 'face
;; 'magit-diff-file-header)
;; remaining)
;; 'keymap
;; magit-diffstat-keymap)
;; "\n")
)))))

(defun magit-wash-diffstats ()
(let ((entry-regexp "^ ?\\(.*?\\)\\( +| +.*\\)$")
(title-regexp "^ ?\\([0-9]+ +files? change.*\\)\n+")
(pos (point)))
(save-restriction
(save-match-data
(when (and (looking-at entry-regexp)
(re-search-forward title-regexp nil t))
(let ((title-line (match-string-no-properties 1))
(stat-end (point-marker)))
(delete-region (match-beginning 0) (match-end 0))
(narrow-to-region pos stat-end)
(goto-char (point-min))
(magit-with-section "diffstats" 'diffstats
(insert title-line)
;;(magit-put-line-property 'face 'magit-section-title)
(insert "\n")

(set (make-local-variable 'magit-diffstat-cached-sections)
nil)

(magit-wash-sequence #'magit-wash-diffstat))
(setq magit-diffstat-cached-sections
(nreverse magit-diffstat-cached-sections))
(insert "\n")))))))

(defun magit-wash-diffstats-postwork (file &optional section)
(let ((sec (or section
(and (boundp 'magit-diffstat-cached-sections)
(pop magit-diffstat-cached-sections)))))
(when sec
(let* ((info (magit-section-info sec))
(begin (nth 3 info))
(end (nth 4 info)))
(put-text-property begin end
'face 'magit-diff-file-header)
(magit-set-section-info (list 'diffstat file 'completed)
sec)))))

(defun magit-diffstat-item-kind (diffstat)
(car (magit-section-info diffstat)))

(defun magit-diffstat-item-file (diffstat)
(let ((file (cadr (magit-section-info diffstat))))
;; Git diffstat may shorten long pathname with the prefix "..."
;; (e.g. ".../long/sub/dir/file" or "...longfilename")
(save-match-data
(if (string-match "\\`\\.\\.\\." file)
nil
file))))

(defun magit-diffstat-item-status (diffstat)
"Return 'completed or 'incomplete depending on the processed status"
(caddr (magit-section-info diffstat)))

(defun magit-wash-other-file ()
(if (looking-at "^? \\(.*\\)$")
(let ((file (match-string-no-properties 1)))
(magit-wash-diffstats-postwork file)
(delete-region (point) (+ (line-end-position) 1))
(magit-with-section file 'file
(magit-set-section-info file)
Expand Down Expand Up @@ -2808,6 +2966,8 @@ Customize `magit-diff-refine-hunk' to change the default mode."
(goto-char (match-beginning 0))
(goto-char (point-max)))
(point-marker))))
(magit-wash-diffstats-postwork file)

(let* ((status (cond
((looking-at "^diff --cc")
'unmerged)
Expand Down Expand Up @@ -3426,7 +3586,8 @@ insert a line to tell how to insert more of them"
(defvar magit-currently-shown-commit nil)

(defun magit-wash-commit ()
(let ((magit-current-diff-range))
(let ((magit-current-diff-range)
(merge-commit))
(when (looking-at "^commit \\([0-9a-fA-F]\\{40\\}\\)")
(setq magit-current-diff-range (match-string 1))
(add-text-properties (match-beginning 1) (match-end 1)
Expand All @@ -3435,7 +3596,8 @@ insert a line to tell how to insert more of them"
((search-forward-regexp "^Merge: \\([0-9a-fA-F]+\\) \\([0-9a-fA-F]+\\)$" nil t)
(setq magit-current-diff-range (cons (cons (match-string 1)
(match-string 2))
magit-current-diff-range))
magit-current-diff-range)
merge-commit t)
(let ((first (magit-set-section nil 'commit (match-beginning 1) (match-end 1)))
(second (magit-set-section nil 'commit (match-beginning 2) (match-end 2))))
(magit-set-section-info (match-string 1) first)
Expand All @@ -3444,8 +3606,23 @@ insert a line to tell how to insert more of them"
(make-commit-button (match-beginning 2) (match-end 2)))
(t
(setq magit-current-diff-range (cons (concat magit-current-diff-range "^")
magit-current-diff-range))))
(search-forward-regexp "^$")
magit-current-diff-range)
merge-commit nil)))

(search-forward-regexp "^$") ; point at the beginning of log msgs

(when magit-show-diffstat
(let ((pos (point)))
(save-excursion
(forward-char)
(when (search-forward-regexp (if merge-commit "^$" "^---$")
nil t)
(delete-region (match-beginning 0)
(+ (match-end 0) 1))
(insert "\n")

(magit-wash-diffstats)))))

(while (and
(search-forward-regexp "\\(\\b[0-9a-fA-F]\\{4,40\\}\\b\\)\\|\\(^diff\\)" nil 'noerror)
(not (match-string 2)))
Expand Down Expand Up @@ -3499,6 +3676,7 @@ insert a line to tell how to insert more of them"
"--pretty=medium"
`(,@(if magit-have-abbrev (list "--no-abbrev-commit"))
,@(if magit-have-decorate (list "--decorate=full"))
,@(if magit-show-diffstat (list "--stat"))
"--cc"
"-p" ,commit))))

Expand Down Expand Up @@ -5275,10 +5453,13 @@ restore the window state that was saved before ediff was called."
range))))
(setq magit-current-range range)
(magit-create-buffer-sections
(magit-git-section 'diffbuf
(magit-rev-range-describe range "Changes")
'magit-wash-diffs
"diff" (magit-diff-U-arg) args "--"))))
(apply #'magit-git-section
'diffbuf
(magit-rev-range-describe range "Changes")
'magit-wash-diffs
"diff" (magit-diff-U-arg)
`(,@(if magit-show-diffstat (list "--patch-with-stat"))
,args "--")))))

(define-derived-mode magit-diff-mode magit-mode "Magit Diff"
"Mode for looking at a git diff.
Expand Down Expand Up @@ -5648,6 +5829,11 @@ With a prefix argument, visit in other window."
(dired-jump other-window (file-truename info)))
((diff)
(dired-jump other-window (file-truename (magit-diff-item-file item))))
((diffstat)
(let ((file (magit-diffstat-item-file item)))
(if file
(dired-jump other-window (file-truename file))
(error "Can't get the pathname for this file"))))
((hunk)
(dired-jump other-window
(file-truename (magit-diff-item-file
Expand All @@ -5672,6 +5858,18 @@ With a prefix argument, visit in other window."
(funcall
(if other-window 'find-file-other-window 'find-file)
file)))))
((diffstat)
(let ((file (magit-diffstat-item-file item)))
(cond ((null file)
(error "Can't get pathname for this file"))
((not (file-exists-p file))
(error "Can't visit deleted file: %s" file))
((file-directory-p file)
(magit-status file))
(t
(funcall
(if other-window 'find-file-other-window 'find-file)
file)))))
((hunk)
(let ((file (magit-diff-item-file (magit-hunk-item-diff item)))
(line (magit-hunk-item-target-line item))
Expand All @@ -5695,6 +5893,8 @@ With a prefix argument, visit in other window."
(call-interactively 'magit-visit-file-item))
((diff)
(call-interactively 'magit-visit-file-item))
((diffstat)
(call-interactively 'magit-visit-file-item))
((hunk)
(call-interactively 'magit-visit-file-item))
((commit)
Expand Down