Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Merged
merged 4 commits into from

3 participants

@cinsk

When a commit has lots of files, it would be difficult to get the overview of the commit, although we can control the output of the magit-commit and magit-diff buffer by using magit-show-level-1 and so on.

This branch will includes support for git log --stat and git diff --patch-with-stat commands. so the either commit log or diff log will contains section like:

7 files changed, 87 insertions(+), 136 deletions(-)
 pom.xml                                         |    9 +--
 src/main/java/com/mycompany/rest/Follow.java    |    6 -
 src/main/java/com/mycompany/rest/Pins.java      |  107 +++++++++-----------
 src/main/java/com/mycompany/rest/Upload.java    |   11 +--
 .../java/com/mycompany/rest/UserBoards.java     |   54 ++++------
 src/main/java/com/mycompany/rest/UserPins.java  |    6 -
 src/main/java/com/mycompany/rest/Users.java     |   30 ++----
  • Section nagivation commands works properly (e.g. p, n',M-p,M-n`, etc.)
  • RET will visit the file using magit-visit-item,
  • e will invoke magit-ediff.
  • M-g will move points to the diffstat headers.

There is minor problem (on restoring window-configuration); when the user press e at the diffstat line, the code will move the point to the actual diff section line, and will invoke magit-ediff. When the user quit ediff, the point is at the actual diff section line, not the diffstat line.

Tested on

cinsk added some commits
@cinsk cinsk add diffstat section on `git log` and `git diff`.
log and diff related functions will display *diffstat* if `magit-show-diffstat` is non-nil.

On *diffstat* entry, `e` will launch `magit-ediff`.  However, the window configuration will not be restored properly yet.
1c1434f
@cinsk cinsk fix a bug that can't handle abbreviated filename in diffstat
- better washing mechanism for diffstat sections
293a780
@cinsk cinsk Merge remote-tracking branch 'refs/remotes/github/master' 27f454a
@cinsk cinsk Merge remote-tracking branch 'refs/remotes/github/master'
Conflicts:
	magit.el
3ac696b
@lelit

This would be very handy: I still use egg-commit just because it shows me one final overview of what is going on, plus a chance of directly re-adjust the staged stuff...

@sigma sigma merged commit 3ac696b into magit:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 24, 2012
  1. @cinsk

    add diffstat section on `git log` and `git diff`.

    cinsk authored
    log and diff related functions will display *diffstat* if `magit-show-diffstat` is non-nil.
    
    On *diffstat* entry, `e` will launch `magit-ediff`.  However, the window configuration will not be restored properly yet.
Commits on Nov 28, 2012
  1. @cinsk

    fix a bug that can't handle abbreviated filename in diffstat

    cinsk authored
    - better washing mechanism for diffstat sections
Commits on Feb 3, 2013
  1. @cinsk
Commits on Feb 19, 2013
  1. @cinsk

    Merge remote-tracking branch 'refs/remotes/github/master'

    cinsk authored
    Conflicts:
    	magit.el
This page is out of date. Refresh to see the latest.
Showing with 209 additions and 9 deletions.
  1. +209 −9 magit.el
View
218 magit.el
@@ -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
@@ -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.
@@ -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.
@@ -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'"
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)))
@@ -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))))
@@ -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.
@@ -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
@@ -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))
@@ -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)
Something went wrong with that request. Please try again.