-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprojtree-git.el
109 lines (103 loc) · 4.77 KB
/
projtree-git.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
;;; projtree.el --- Display project directory tree of visited file -*- lexical-binding: t -*-
;;
;; Copyright © 2023 Peter Gardfjäll <[email protected]>
;;
;; Author: Peter Gardfjäll <[email protected]>
;; URL: https://github.com/petergardfjall/emacs-projtree
;; Keywords: workspace, project
;; Package-Requires: ((emacs "27.0"))
;; Version: 0.0.2
;; Homepage: https://github.com/petergardfjall/emacs-projtree
;;
;;
;; This program 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 of the License, or
;; (at your option) any later version.
;;
;; This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
;;
;;
;;; Commentary:
;;
;; `projtree-mode' is an Emacs minor mode that shows a file explorer for the
;; currently visited file in a side window with a `*projtree*' buffer. The
;; project of the visited file is determined by `project.el'. `projtree-mode'
;; follows the active buffer, meaning that it always displays the file tree
;; rooted at the directory root folder of the currently visited file.
;;
;;; Code:
(defvar projtree-git--cmd (executable-find "git")
"Full system path to a git executable.")
(defun projtree-git--status (root-dir)
"Run git status in the given git ROOT-DIR.
The status is returned as a hash table where the keys are
absolute file paths and values are status codes following the git
status porcelain format. Note that up-to-date files do not have
entries in the resulting hash table."
(with-temp-buffer
(setq-local default-directory root-dir)
(let* ((buf (current-buffer))
(statuses (make-hash-table :test 'equal))
(exit-code (call-process projtree-git--cmd nil buf nil "status" "--porcelain" "--untracked-files=normal" "--ignored=matching")))
(if (> exit-code 0)
(progn
(message "projtree-git: git status for %s gave non-zero exit code: %d" root-dir exit-code)
statuses)
(projtree-git--parse-git-status-output buf)))))
(defun projtree-git--parse-git-status-output (buffer)
"Parse git status output from BUFFER and return a status hash table.
BUFFER is assumed to hold git status in porcelain format and
nothing else. The status hash table keys are absolute file paths
and values are status codes in the git status porcelain format."
(with-current-buffer buffer
(let ((git-root default-directory)
(statuses (make-hash-table :test 'equal)))
(goto-char (point-min))
(while (not (eobp))
(let* ((line (buffer-substring (line-beginning-position) (line-end-position)))
(tokens (split-string line))
(status (nth 0 tokens))
;; Normally the file follows the status code, but for renames the
;; new filename comes later: "R prior/path -> new/path"
(file (if (equal status "R") (nth 3 tokens) (nth 1 tokens)))
(path (projtree--abspath (expand-file-name file))))
(puthash path status statuses)
;; Mark modification states for any ancestor directories.
(projtree-git--mark-ancestor-status statuses git-root path status)
;; Next line of output.
(forward-line 1)))
statuses)))
(defun projtree-git--mark-ancestor-status (statuses git-root path status)
"Add modification STATUS for parent directories of PATH under GIT-ROOT.
The ancestor directories' statuses are added to the STATUSES hash table."
(let ((parent-dir (string-trim-right (file-name-directory path) "/")))
(when (and
(projtree--descendant-p git-root parent-dir)
(not (gethash parent-dir statuses)))
(pcase (string-to-char status)
(?M (puthash parent-dir "M" statuses))
(?A (puthash parent-dir "M" statuses))
(?D (puthash parent-dir "M" statuses))
(?U (puthash parent-dir "U" statuses)))
;; move further up directory tree ...
(projtree-git--mark-ancestor-status statuses git-root parent-dir status))))
(defun projtree-git--status-face (code)
"Return the face corresponding to the given git status CODE.
The status CODE is expected to be on the git status porcelain format."
(pcase (string-to-char code)
(?M 'projtree-git-modified)
(?U 'projtree-git-conflict)
(?A 'projtree-git-added)
(?R 'projtree-git-renamed)
(?? 'projtree-git-untracked)
(?! 'projtree-git-ignored)
(_ nil)))
(provide 'projtree-git)
;;; projtree-git.el ends here