(with-current-buffer git-status-buffer
(git-refresh-status))))))
-(defun switch-to-stgit-buffer (dir)
- "Switch to a (possibly new) buffer displaying StGit patches for DIR."
+(defun stgit-find-buffer (dir)
+ "Return the buffer displaying StGit patches for DIR, or nil if none."
(setq dir (file-name-as-directory dir))
(let ((buffers (buffer-list)))
(while (and buffers
(and (eq major-mode 'stgit-mode)
(string= default-directory dir)))))
(setq buffers (cdr buffers)))
- (switch-to-buffer (if buffers
- (car buffers)
- (create-stgit-buffer dir)))))
+ (and buffers (car buffers))))
+
+(defun switch-to-stgit-buffer (dir)
+ "Switch to a (possibly new) buffer displaying StGit patches for DIR."
+ (setq dir (file-name-as-directory dir))
+ (let ((buffer (stgit-find-buffer dir)))
+ (switch-to-buffer (or buffer
+ (create-stgit-buffer dir)))))
+
(defstruct (stgit-patch)
- status name desc empty)
+ status name desc empty files-ewoc)
(defun stgit-patch-pp (patch)
(let ((status (stgit-patch-status patch))
(start (point))
(name (stgit-patch-name patch)))
- (insert (case status
- ('applied "+")
- ('top ">")
- ('unapplied "-")
- (t "ยท"))
- (if (memq name stgit-marked-patches)
- "*" " ")
- (propertize (format "%-30s" (symbol-name name))
- 'face (case status
- ('applied 'stgit-applied-patch-face)
- ('top 'stgit-top-patch-face)
- ('unapplied 'stgit-unapplied-patch-face)))
- " "
- (if (stgit-patch-empty patch) "(empty) " "")
- (propertize (or (stgit-patch-desc patch) "")
- 'face 'stgit-description-face))
- (add-text-properties start (point) (list 'entry-type 'patch
- 'stgit-patchsym name))
+ (case name
+ (:index (insert (propertize " Index" 'face 'italic)))
+ (:work (insert (propertize " Work tree" 'face 'italic)))
+ (t (insert (case status
+ ('applied "+")
+ ('top ">")
+ ('unapplied "-"))
+ (if (memq name stgit-marked-patches)
+ "*" " ")
+ (propertize (format "%-30s"
+ (symbol-name name))
+ 'face (case status
+ ('applied 'stgit-applied-patch-face)
+ ('top 'stgit-top-patch-face)
+ ('unapplied 'stgit-unapplied-patch-face)
+ ('index nil)
+ ('work nil)))
+ " "
+ (if (stgit-patch-empty patch) "(empty) " "")
+ (propertize (or (stgit-patch-desc patch) "")
+ 'face 'stgit-description-face))))
+ (put-text-property start (point) 'entry-type 'patch)
(when (memq name stgit-expanded-patches)
- (stgit-insert-patch-files name))
+ (stgit-insert-patch-files patch))
(put-text-property start (point) 'patch-data patch)))
(defun create-stgit-buffer (dir)
(setq args (stgit-make-run-args args))
(apply 'call-process "git" nil standard-output nil args))
+(defun stgit-index-empty-p ()
+ "Returns non-nil if the index contains no changes from HEAD."
+ (zerop (stgit-run-git-silent "diff-index" "--cached" "--quiet" "HEAD")))
+
(defun stgit-run-series (ewoc)
(let ((first-line t))
(with-temp-buffer
:desc (match-string 5)
:empty (string= (match-string 1) "0"))))
(setq first-line nil)
- (forward-line 1)))))))
+ (forward-line 1))
+ (ewoc-enter-last ewoc
+ (make-stgit-patch
+ :status 'index
+ :name :index
+ :desc nil
+ :empty nil))
+ (ewoc-enter-last ewoc
+ (make-stgit-patch
+ :status 'work
+ :name :work
+ :desc nil
+ :empty nil)))))))
(defun stgit-reload ()
(unknown "Unknown" stgit-unknown-file-face)))
"Alist of code symbols to description strings")
-(defun stgit-file-status-code-as-string (code)
- "Return stgit status code as string"
- (let ((str (assq (if (consp code) (car code) code)
- stgit-file-status-code-strings)))
- (when str
+(defun stgit-file-status-code-as-string (file)
+ "Return stgit status code for FILE as a string"
+ (let* ((code (assq (stgit-file-status file)
+ stgit-file-status-code-strings))
+ (score (stgit-file-cr-score file)))
+ (when code
(format "%-11s "
- (if (and str (consp code) (/= (cdr code) 100))
- (format "%s %s" (cdr str)
- (propertize (format "%d%%" (cdr code))
+ (if (and score (/= score 100))
+ (format "%s %s" (cdr code)
+ (propertize (format "%d%%" score)
'face 'stgit-description-face))
- (cdr str))))))
+ (cdr code))))))
(defun stgit-file-status-code (str &optional score)
"Return stgit status code from git status string"
(propertize (format "%o" new-perm)
'face 'stgit-file-permission-face)))))))
-(defun stgit-insert-patch-files (patchsym)
- (let* ((start (point))
- (result (with-output-to-string
- (stgit-run-git "diff-tree" "-r" "-z"
- (if stgit-expand-find-copies-harder
- "--find-copies-harder"
- "-C")
- (stgit-id patchsym)))))
- (let (mstart)
- (while (string-match "\0:\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} \\(\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\\|\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\\)"
- result mstart)
- (let ((copy-or-rename (match-string 4 result))
- (old-perm (read (format "#o%s" (match-string 1 result))))
- (new-perm (read (format "#o%s" (match-string 2 result))))
- (line-start (point))
- status
- change
- (properties '(entry-type file)))
- (if copy-or-rename
- (let ((cr-score (match-string 5 result))
- (cr-from-file (match-string 6 result))
- (cr-to-file (match-string 7 result)))
- (setq status (stgit-file-status-code copy-or-rename
- cr-score)
- properties (list* 'stgit-old-file cr-from-file
- 'stgit-new-file cr-to-file
- properties)
- change (concat
- cr-from-file
- (propertize " -> "
- 'face 'stgit-description-face)
- cr-to-file)))
- (setq status (stgit-file-status-code (match-string 8 result))
- properties (list* 'stgit-file (match-string 9 result)
- properties)
- change (match-string 9 result)))
-
- (let ((mode-change (stgit-file-mode-change-string old-perm
- new-perm)))
- (insert "\n "
- (format "%-12s" (stgit-file-status-code-as-string
- status))
+(defstruct (stgit-file)
+ old-perm new-perm copy-or-rename cr-score cr-from cr-to status file)
+
+(defun stgit-file-pp (file)
+ (let ((status (stgit-file-status file))
+ (name (if (stgit-file-copy-or-rename file)
+ (concat (stgit-file-cr-from file)
+ (propertize " -> "
+ 'face 'stgit-description-face)
+ (stgit-file-cr-to file))
+ (stgit-file-file file)))
+ (mode-change (stgit-file-mode-change-string
+ (stgit-file-old-perm file)
+ (stgit-file-new-perm file)))
+ (start (point)))
+ (insert (format " %-12s%1s%s%s\n"
+ (stgit-file-status-code-as-string file)
mode-change
- (if (> (length mode-change) 0) " " "")
- change
- (propertize (stgit-file-type-change-string old-perm
- new-perm)
+ name
+ (propertize (stgit-file-type-change-string
+ (stgit-file-old-perm file)
+ (stgit-file-new-perm file))
'face 'stgit-description-face)))
- (add-text-properties line-start (point) properties))
- (setq mstart (match-end 0))))
- (when (= start (point))
- (insert " <no files>\n"))
- (put-text-property start (point) 'stgit-file-patchsym patchsym)))
+ (add-text-properties start (point)
+ (list 'entry-type 'file
+ 'file-data file))))
+
+(defun stgit-insert-patch-files (patch)
+ "Expand (show modification of) the patch with name PATCHSYM (a
+symbol) after the line at point.
+`stgit-expand-find-copies-harder' controls how hard to try to
+find copied files."
+ (insert "\n")
+ (let* ((patchsym (stgit-patch-name patch))
+ (end (progn (insert "#") (prog1 (point-marker) (forward-char -1))))
+ (args (list "-z" (if stgit-expand-find-copies-harder
+ "--find-copies-harder"
+ "-C")))
+ (ewoc (ewoc-create #'stgit-file-pp nil nil t)))
+ (setf (stgit-patch-files-ewoc patch) ewoc)
+ (with-temp-buffer
+ (apply 'stgit-run-git
+ (cond ((eq patchsym :work)
+ `("diff-files" ,@args))
+ ((eq patchsym :index)
+ `("diff-index" ,@args "--cached" "HEAD"))
+ (t
+ `("diff-tree" ,@args "-r" ,(stgit-id patchsym)))))
+ (goto-char (point-min))
+ (unless (or (eobp) (memq patchsym '(:work :index)))
+ (forward-char 41))
+ (while (looking-at ":\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} ")
+ (let ((old-perm (string-to-number (match-string 1) 8))
+ (new-perm (string-to-number (match-string 2) 8)))
+ (goto-char (match-end 0))
+ (let ((file
+ (cond ((looking-at
+ "\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0")
+ (make-stgit-file
+ :old-perm old-perm
+ :new-perm new-perm
+ :copy-or-rename t
+ :cr-score (string-to-number (match-string 2))
+ :cr-from (match-string 3)
+ :cr-to (match-string 4)
+ :status (stgit-file-status-code (match-string 1))
+ :file (match-string 3)))
+ ((looking-at "\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\0")
+ (make-stgit-file
+ :old-perm old-perm
+ :new-perm new-perm
+ :copy-or-rename nil
+ :cr-score nil
+ :cr-from nil
+ :cr-to nil
+ :status (stgit-file-status-code (match-string 1))
+ :file (match-string 2))))))
+ (ewoc-enter-last ewoc file))
+ (goto-char (match-end 0))))
+ (unless (ewoc-nth ewoc 0)
+ (ewoc-set-hf ewoc "" (propertize " <no files>\n"
+ 'face 'stgit-description-face))))
+ (goto-char end)
+ (delete-char -2)))
(defun stgit-select-file ()
- (let ((patched-file (stgit-patched-file-at-point)))
- (unless patched-file
- (error "No patch or file on the current line"))
- (let ((filename (expand-file-name (cdr patched-file))))
- (unless (file-exists-p filename)
- (error "File does not exist"))
- (find-file filename))))
+ (let ((filename (expand-file-name
+ (stgit-file-file (stgit-patched-file-at-point)))))
+ (unless (file-exists-p filename)
+ (error "File does not exist"))
+ (find-file filename)))
(defun stgit-select-patch ()
(let ((patchname (stgit-patch-name-at-point)))
(let ((patched-file (stgit-patched-file-at-point)))
(unless patched-file
(error "No file on the current line"))
- (let ((filename (expand-file-name (cdr patched-file))))
+ (let ((filename (expand-file-name (stgit-file-file patched-file))))
(unless (file-exists-p filename)
(error "File does not exist"))
(find-file-other-window filename))))
(set (make-local-variable 'stgit-marked-patches) nil)
(set (make-local-variable 'stgit-expanded-patches) nil)
(set-variable 'truncate-lines 't)
+ (add-hook 'after-save-hook 'stgit-update-saved-file)
(run-hooks 'stgit-mode-hook))
+(defun stgit-update-saved-file ()
+ (let* ((file (expand-file-name buffer-file-name))
+ (dir (file-name-directory file))
+ (gitdir (condition-case nil (git-get-top-dir dir)
+ (error nil)))
+ (buffer (and gitdir (stgit-find-buffer gitdir))))
+ (when buffer
+ (with-current-buffer buffer
+ ;; FIXME: just invalidate ewoc node
+ (stgit-reload)))))
+
(defun stgit-add-mark (patchsym)
"Mark the patch PATCHSYM."
(setq stgit-marked-patches (cons patchsym stgit-marked-patches)))
(cause-error
(error "No patch on this line")))))
-(defun stgit-patched-file-at-point (&optional both-files)
- "Returns a cons of the patchsym and file name at point. For
-copies and renames, return the new file if the patch is either
-applied. If BOTH-FILES is non-nil, return a cons of the old and
-the new file names instead of just one name."
- (let ((patchsym (get-text-property (point) 'stgit-file-patchsym))
- (file (get-text-property (point) 'stgit-file)))
- (cond ((not patchsym) nil)
- (file (cons patchsym file))
- (both-files
- (cons patchsym (cons (get-text-property (point) 'stgit-old-file)
- (get-text-property (point) 'stgit-new-file))))
- (t
- (let ((file-sym (save-excursion
- (stgit-previous-patch)
- (unless (eq (stgit-patch-name-at-point)
- patchsym)
- (error "Cannot find the %s patch" patchsym))
- (beginning-of-line)
- (if (= (char-after) ?-)
- 'stgit-old-file
- 'stgit-new-file))))
- (cons patchsym (get-text-property (point) file-sym)))))))
+(defun stgit-patched-file-at-point ()
+ (get-text-property (point) 'file-data))
(defun stgit-patches-marked-or-at-point ()
"Return the symbols of the marked patches, or the patch on the current line."
(defun stgit-goto-patch (patchsym)
"Move point to the line containing patch PATCHSYM.
-If that patch cannot be found, return nil."
- (let ((p (text-property-any (point-min) (point-max)
- 'stgit-patchsym patchsym)))
- (when p
- (goto-char p)
+If that patch cannot be found, do nothing."
+ (let ((node (ewoc-nth stgit-ewoc 0)))
+ (while (and node (not (eq (stgit-patch-name (ewoc-data node))
+ patchsym)))
+ (setq node (ewoc-next stgit-ewoc node)))
+ (when node
+ (ewoc-goto-node stgit-ewoc node)
(move-to-column goal-column))))
(defun stgit-init ()
(stgit-capture-output "*StGit patch*"
(case (get-text-property (point) 'entry-type)
('file
- (let ((patchsym (stgit-patch-name-at-point))
- (patched-file (stgit-patched-file-at-point t)))
- (let ((id (stgit-id (car patched-file))))
- (if (consp (cdr patched-file))
- ;; two files (copy or rename)
- (stgit-run-git "diff" "-C" "-C" (concat id "^") id "--"
- (cadr patched-file) (cddr patched-file))
- ;; just one file
- (stgit-run-git "diff" (concat id "^") id "--"
- (cdr patched-file))))))
+ (let* ((patched-file (stgit-patched-file-at-point))
+ (patch-name (stgit-patch-name-at-point))
+ (patch-id (stgit-id patch-name))
+ (args (append (and (stgit-file-cr-from patched-file)
+ (if stgit-expand-find-copies-harder
+ '("--find-copies-harder")
+ '("-C")))
+ (cond ((eq patch-id :index)
+ '("--cached"))
+ ((eq patch-id :work)
+ nil)
+ (t
+ (list (concat patch-id "^") patch-id)))
+ '("--")
+ (if (stgit-file-copy-or-rename patched-file)
+ (list (stgit-file-cr-from patched-file)
+ (stgit-file-cr-to patched-file))
+ (list (stgit-file-file patched-file))))))
+ (apply 'stgit-run-git "diff" args)))
('patch
(stgit-run "show" "-O" "--patch-with-stat" "-O" "-M"
(stgit-patch-name-at-point)))