aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md1
-rw-r--r--doc/index.md1
-rw-r--r--doc/usage.md1
-rw-r--r--projectile.el82
5 files changed, 86 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9dba9f..bb02744 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,6 +46,7 @@
* New command `projectile-run-term` (<kbd>C-c p x t</kbd>).
* Let user unignore files in `.projectile` with the ! prefix.
* Add a command to add all projects in a directory to the cache (`projectile-discover-projects-in-directory`).
+* Add a command to list dirty version controlled projects (`projectile-browse-dirty-projects`).
### Changes
diff --git a/README.md b/README.md
index ada9875..5fb65ad 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,7 @@ it. Some of Projectile's features:
* regenerate project etags or gtags (requires [ggtags](https://github.com/leoliu/ggtags)).
* visit project in dired
* run make in a project with a single key chord
+* check for dirty repositories
Here's a glimpse of Projectile in action:
diff --git a/doc/index.md b/doc/index.md
index e2d786d..2bb8198 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -34,6 +34,7 @@ it. Some of Projectile's features:
* regenerate project etags or gtags (requires [ggtags](https://github.com/leoliu/ggtags)).
* visit project in dired
* run make in a project with a single key chord
+* browse dirty version controlled projects
Here's a glimpse of Projectile in action:
diff --git a/doc/usage.md b/doc/usage.md
index 9afa9dd..8b3ede1 100644
--- a/doc/usage.md
+++ b/doc/usage.md
@@ -34,6 +34,7 @@ Keybinding | Description
<kbd>C-c p s g</kbd> | Run grep on the files in the project.
<kbd>M-- C-c p s g</kbd> | Run grep on `projectile-grep-default-files` in the project.
<kbd>C-c p v</kbd> | Run `vc-dir` on the root directory of the project.
+<kbd>C-c p V</kbd> | Browse dirty version controlled projects.
<kbd>C-c p b</kbd> | Display a list of all project buffers currently open.
<kbd>C-c p 4 b</kbd> | Switch to a project buffer and show it in another window.
<kbd>C-c p 4 C-o</kbd> | Display a project buffer in another window without selecting it.
diff --git a/projectile.el b/projectile.el
index 05ae86c..92d1732 100644
--- a/projectile.el
+++ b/projectile.el
@@ -984,6 +984,14 @@ Files are returned as relative paths to the project root."
:group 'projectile
:type 'string)
+(defcustom projectile-vcs-dirty-state '("edited" "unregistered" "needs-update" "needs-merge" "unlocked-changes" "conflict")
+ "List of states checked by `projectile-browse-dirty-projects'.
+Possible checked states are:
+\"edited\", \"unregistered\", \"needs-update\", \"needs-merge\", unlocked-changes\" and \"conflict\",
+as defined in `vc.el'."
+ :group 'projectile
+ :type '(repeat (string)))
+
(defun projectile-get-ext-command ()
"Determine which external command to invoke based on the project's VCS."
(let ((vcs (projectile-project-vcs)))
@@ -1084,6 +1092,74 @@ they are excluded from the results of this function."
(when cmd
(projectile-files-via-ext-command cmd))))
+(defun projectile-call-process-to-string (program &rest args)
+ "Invoke the executable PROGRAM with ARGS and return the output as a string."
+ (with-temp-buffer
+ (apply 'call-process program nil (current-buffer) nil args)
+ (buffer-string)))
+
+(defun projectile-shell-command-to-string (command)
+ "Try to run COMMAND without actually using a shell and return the output.
+
+The function `eshell-search-path' will be used to search the PATH
+environment variable for an appropriate executable using the text
+occuring before the first space. If no executable is found,
+fallback to `shell-command-to-string'."
+ (cl-destructuring-bind
+ (the-command . args) (split-string command " ")
+ (let ((binary-path (eshell-search-path the-command)))
+ (if binary-path
+ (apply 'projectile-call-process-to-string binary-path args)
+ (shell-command-to-string command)))))
+
+(defun projectile-check-vcs-status (&optional PROJECT-PATH)
+ "Check the status of the current project.
+If PROJECT-PATH is a project, check this one instead."
+ (let* ((PROJECT-PATH (or PROJECT-PATH (projectile-project-root)))
+ (project-status nil))
+ (save-excursion
+ (vc-dir PROJECT-PATH)
+ ;; wait until vc-dir is done
+ (while (vc-dir-busy) (sleep-for 0 100))
+ ;; check for status
+ (save-excursion
+ (save-match-data
+ (dolist (check projectile-vcs-dirty-state)
+ (goto-char (point-min))
+ (when (search-forward check nil t)
+ (setq project-status (cons check project-status))))))
+ (kill-buffer)
+ project-status)))
+
+(defun projectile-check-vcs-status-of-known-projects ()
+ "Return the list of dirty projects.
+The list is composed of sublists~: (project-path, project-status).
+Raise an error if their is no dirty project."
+ (let ((projects projectile-known-projects)
+ (status ())
+ (tmp-status nil))
+ (dolist (project projects)
+ (condition-case nil
+ (setq tmp-status (projectile-check-vcs-status project))
+ (error nil))
+ (when tmp-status
+ (setq status (cons (list project tmp-status) status))))
+ (when (= (length status) 0)
+ (message "No dirty projects has been found"))
+ status))
+
+(defun projectile-browse-dirty-projects ()
+ "Browse dirty version controlled projects."
+ (interactive)
+ (let ((status nil)
+ (mod-proj nil))
+ (message "Checking for modifications in known projects...")
+ (setq status (projectile-check-vcs-status-of-known-projects))
+ (while (not (= (length status) 0))
+ (setq mod-proj (cons (car (pop status)) mod-proj)))
+ (projectile-vc
+ (projectile-completing-read "Select project: " mod-proj))))
+
(defun projectile-files-via-ext-command (command)
"Get a list of relative file names in the project root by executing COMMAND."
(split-string (shell-command-to-string command) "\0" t))
@@ -3148,6 +3224,10 @@ is chosen."
"Open project root in vc-dir or magit."
(projectile-vc))
+ (def-projectile-commander-method ?V
+ "Browse dirty projects"
+ (projectile-browse-dirty-projects))
+
(def-projectile-commander-method ?r
"Replace a string in the project."
(projectile-replace))
@@ -3259,6 +3339,7 @@ is chosen."
(define-key map (kbd "T") #'projectile-find-test-file)
(define-key map (kbd "u") #'projectile-run-project)
(define-key map (kbd "v") #'projectile-vc)
+ (define-key map (kbd "V") #'projectile-browse-dirty-projects)
(define-key map (kbd "x e") #'projectile-run-eshell)
(define-key map (kbd "x t") #'projectile-run-term)
(define-key map (kbd "x s") #'projectile-run-shell)
@@ -3296,6 +3377,7 @@ is chosen."
["Search in project (ag)" projectile-ag]
["Replace in project" projectile-replace]
["Multi-occur in project" projectile-multi-occur]
+ ["Browse dirty projects" projectile-browse-dirty-projects]
"--"
["Run shell" projectile-run-shell]
["Run eshell" projectile-run-eshell]