summaryrefslogtreecommitdiff
path: root/elpa/magit-20200728.45/magit-repos.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/magit-20200728.45/magit-repos.el')
-rw-r--r--elpa/magit-20200728.45/magit-repos.el338
1 files changed, 338 insertions, 0 deletions
diff --git a/elpa/magit-20200728.45/magit-repos.el b/elpa/magit-20200728.45/magit-repos.el
new file mode 100644
index 0000000..b59de62
--- /dev/null
+++ b/elpa/magit-20200728.45/magit-repos.el
@@ -0,0 +1,338 @@
+;;; magit-repos.el --- listing repositories -*- lexical-binding: t -*-
+
+;; Copyright (C) 2010-2020 The Magit Project Contributors
+;;
+;; You should have received a copy of the AUTHORS.md file which
+;; lists all contributors. If not, see http://magit.vc/authors.
+
+;; Author: Jonas Bernoulli <jonas@bernoul.li>
+;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
+
+;; Magit 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, or (at your option)
+;; any later version.
+;;
+;; Magit 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 Magit. If not, see http://www.gnu.org/licenses.
+
+;;; Commentary:
+
+;; This library implements support for listing repositories. This
+;; includes getting a Lisp list of known repositories as well as a
+;; mode for listing repositories in a buffer.
+
+;;; Code:
+
+(eval-when-compile
+ (require 'subr-x))
+
+(require 'magit-core)
+
+(declare-function magit-status-setup-buffer "magit-status" (directory))
+
+(defvar x-stretch-cursor)
+
+;;; Options
+
+(defcustom magit-repository-directories nil
+ "List of directories that are or contain Git repositories.
+
+Each element has the form (DIRECTORY . DEPTH). DIRECTORY has
+to be a directory or a directory file-name, a string. DEPTH,
+an integer, specifies the maximum depth to look for Git
+repositories. If it is 0, then only add DIRECTORY itself.
+
+This option controls which repositories are being listed by
+`magit-list-repositories'. It also affects `magit-status'
+\(which see) in potentially surprising ways."
+ :package-version '(magit . "3.0.0")
+ :group 'magit-essentials
+ :type '(repeat (cons directory (integer :tag "Depth"))))
+
+(defgroup magit-repolist nil
+ "List repositories in a buffer."
+ :link '(info-link "(magit)Repository List")
+ :group 'magit-modes)
+
+(defcustom magit-repolist-mode-hook '(hl-line-mode)
+ "Hook run after entering Magit-Repolist mode."
+ :package-version '(magit . "2.9.0")
+ :group 'magit-repolist
+ :type 'hook
+ :get 'magit-hook-custom-get
+ :options '(hl-line-mode))
+
+(defcustom magit-repolist-columns
+ '(("Name" 25 magit-repolist-column-ident nil)
+ ("Version" 25 magit-repolist-column-version nil)
+ ("B<U" 3 magit-repolist-column-unpulled-from-upstream
+ ((:right-align t)
+ (:help-echo "Upstream changes not in branch")))
+ ("B>U" 3 magit-repolist-column-unpushed-to-upstream
+ ((:right-align t)
+ (:help-echo "Local changes not in upstream")))
+ ("Path" 99 magit-repolist-column-path nil))
+ "List of columns displayed by `magit-list-repositories'.
+
+Each element has the form (HEADER WIDTH FORMAT PROPS).
+
+HEADER is the string displayed in the header. WIDTH is the width
+of the column. FORMAT is a function that is called with one
+argument, the repository identification (usually its basename),
+and with `default-directory' bound to the toplevel of its working
+tree. It has to return a string to be inserted or nil. PROPS is
+an alist that supports the keys `:right-align' and `:pad-right'.
+Some entries also use `:help-echo', but `tabulated-list' does not
+actually support that yet."
+ :package-version '(magit . "2.12.0")
+ :group 'magit-repolist
+ :type `(repeat (list :tag "Column"
+ (string :tag "Header Label")
+ (integer :tag "Column Width")
+ (function :tag "Inserter Function")
+ (repeat :tag "Properties"
+ (list (choice :tag "Property"
+ (const :right-align)
+ (const :pad-right)
+ (symbol))
+ (sexp :tag "Value"))))))
+
+(defcustom magit-repolist-column-flag-alist
+ '((magit-untracked-files . "N")
+ (magit-unstaged-files . "U")
+ (magit-staged-files . "S"))
+ "Association list of predicates and flags for `magit-repolist-column-flag'.
+
+Each element is of the form (FUNCTION . FLAG). Each FUNCTION is
+called with no arguments, with `default-directory' bound to the
+top level of a repository working tree, until one of them returns
+a non-nil value. FLAG corresponding to that function is returned
+as the value of `magit-repolist-column-flag'."
+ :package-version '(magit . "3.0.0")
+ :group 'magit-repolist
+ :type '(alist :key-type (function :tag "Predicate Function")
+ :value-type (string :tag "Flag")))
+
+;;; List Repositories
+;;;; Command
+;;;###autoload
+(defun magit-list-repositories ()
+ "Display a list of repositories.
+
+Use the options `magit-repository-directories' to control which
+repositories are displayed."
+ (interactive)
+ (if magit-repository-directories
+ (with-current-buffer (get-buffer-create "*Magit Repositories*")
+ (magit-repolist-mode)
+ (magit-repolist-refresh)
+ (tabulated-list-print)
+ (switch-to-buffer (current-buffer)))
+ (message "You need to customize `magit-repository-directories' %s"
+ "before you can list repositories")))
+
+;;;; Mode
+
+(defvar magit-repolist-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map tabulated-list-mode-map)
+ (define-key map (kbd "C-m") 'magit-repolist-status)
+ map)
+ "Local keymap for Magit-Repolist mode buffers.")
+
+(defun magit-repolist-status (&optional _button)
+ "Show the status for the repository at point."
+ (interactive)
+ (--if-let (tabulated-list-get-id)
+ (magit-status-setup-buffer (expand-file-name it))
+ (user-error "There is no repository at point")))
+
+(define-derived-mode magit-repolist-mode tabulated-list-mode "Repos"
+ "Major mode for browsing a list of Git repositories."
+ (setq-local x-stretch-cursor nil)
+ (setq tabulated-list-padding 0)
+ (setq tabulated-list-sort-key (cons "Path" nil))
+ (setq tabulated-list-format
+ (vconcat (mapcar (pcase-lambda (`(,title ,width ,_fn ,props))
+ (nconc (list title width t)
+ (-flatten props)))
+ magit-repolist-columns)))
+ (tabulated-list-init-header)
+ (add-hook 'tabulated-list-revert-hook 'magit-repolist-refresh nil t)
+ (setq imenu-prev-index-position-function
+ 'magit-imenu--repolist-prev-index-position-function)
+ (setq imenu-extract-index-name-function
+ 'magit-imenu--repolist-extract-index-name-function))
+
+(defun magit-repolist-refresh ()
+ (setq tabulated-list-entries
+ (mapcar (pcase-lambda (`(,id . ,path))
+ (let ((default-directory path))
+ (list path
+ (vconcat (--map (or (funcall (nth 2 it) id) "")
+ magit-repolist-columns)))))
+ (magit-list-repos-uniquify
+ (--map (cons (file-name-nondirectory (directory-file-name it))
+ it)
+ (magit-list-repos))))))
+
+;;;; Columns
+
+(defun magit-repolist-column-ident (id)
+ "Insert the identification of the repository.
+Usually this is just its basename."
+ id)
+
+(defun magit-repolist-column-path (_id)
+ "Insert the absolute path of the repository."
+ (abbreviate-file-name default-directory))
+
+(defun magit-repolist-column-version (_id)
+ "Insert a description of the repository's `HEAD' revision."
+ (when-let ((v (or (magit-git-string "describe" "--tags" "--dirty")
+ ;; If there are no tags, use the date in MELPA format.
+ (magit-git-string "show" "--no-patch" "--format=%cd-g%h"
+ "--date=format:%Y%m%d.%H%M"))))
+ (save-match-data
+ (when (string-match "-dirty\\'" v)
+ (magit--put-face (1+ (match-beginning 0)) (length v) 'error v))
+ (if (and v (string-match "\\`[0-9]" v))
+ (concat " " v)
+ v))))
+
+(defun magit-repolist-column-branch (_id)
+ "Insert the current branch."
+ (magit-get-current-branch))
+
+(defun magit-repolist-column-upstream (_id)
+ "Insert the upstream branch of the current branch."
+ (magit-get-upstream-branch))
+
+(defun magit-repolist-column-flag (_id)
+ "Insert a flag as specified by `magit-repolist-column-flag-alist'.
+
+By default this indicates whether there are uncommitted changes.
+- N if there is at least one untracked file.
+- U if there is at least one unstaged file.
+- S if there is at least one staged file.
+Only one letter is shown, the first that applies."
+ (-some (pcase-lambda (`(,fun . ,flag))
+ (and (funcall fun) flag))
+ magit-repolist-column-flag-alist))
+
+(defun magit-repolist-column-unpulled-from-upstream (_id)
+ "Insert number of upstream commits not in the current branch."
+ (--when-let (magit-get-upstream-branch)
+ (let ((n (cadr (magit-rev-diff-count "HEAD" it))))
+ (magit--propertize-face
+ (number-to-string n) (if (> n 0) 'bold 'shadow)))))
+
+(defun magit-repolist-column-unpulled-from-pushremote (_id)
+ "Insert number of commits in the push branch but not the current branch."
+ (--when-let (magit-get-push-branch nil t)
+ (let ((n (cadr (magit-rev-diff-count "HEAD" it))))
+ (magit--propertize-face
+ (number-to-string n) (if (> n 0) 'bold 'shadow)))))
+
+(defun magit-repolist-column-unpushed-to-upstream (_id)
+ "Insert number of commits in the current branch but not its upstream."
+ (--when-let (magit-get-upstream-branch)
+ (let ((n (car (magit-rev-diff-count "HEAD" it))))
+ (magit--propertize-face
+ (number-to-string n) (if (> n 0) 'bold 'shadow)))))
+
+(defun magit-repolist-column-unpushed-to-pushremote (_id)
+ "Insert number of commits in the current branch but not its push branch."
+ (--when-let (magit-get-push-branch nil t)
+ (let ((n (car (magit-rev-diff-count "HEAD" it))))
+ (magit--propertize-face
+ (number-to-string n) (if (> n 0) 'bold 'shadow)))))
+
+(defun magit-repolist-column-branches (_id)
+ "Insert number of branches."
+ (let ((n (length (magit-list-local-branches))))
+ (magit--propertize-face (number-to-string n) (if (> n 1) 'bold 'shadow))))
+
+(defun magit-repolist-column-stashes (_id)
+ "Insert number of stashes."
+ (let ((n (length (magit-list-stashes))))
+ (magit--propertize-face (number-to-string n) (if (> n 0) 'bold 'shadow))))
+
+;;; Read Repository
+
+(defun magit-read-repository (&optional read-directory-name)
+ "Read a Git repository in the minibuffer, with completion.
+
+The completion choices are the basenames of top-levels of
+repositories found in the directories specified by option
+`magit-repository-directories'. In case of name conflicts
+the basenames are prefixed with the name of the respective
+parent directories. The returned value is the actual path
+to the selected repository.
+
+If READ-DIRECTORY-NAME is non-nil or no repositories can be
+found based on the value of `magit-repository-directories',
+then read an arbitrary directory using `read-directory-name'
+instead."
+ (if-let ((repos (and (not read-directory-name)
+ magit-repository-directories
+ (magit-repos-alist))))
+ (let ((reply (magit-completing-read "Git repository" repos)))
+ (file-name-as-directory
+ (or (cdr (assoc reply repos))
+ (if (file-directory-p reply)
+ (expand-file-name reply)
+ (user-error "Not a repository or a directory: %s" reply)))))
+ (file-name-as-directory
+ (read-directory-name "Git repository: "
+ (or (magit-toplevel) default-directory)))))
+
+(defun magit-list-repos ()
+ (cl-mapcan (pcase-lambda (`(,dir . ,depth))
+ (magit-list-repos-1 dir depth))
+ magit-repository-directories))
+
+(defun magit-list-repos-1 (directory depth)
+ (cond ((file-readable-p (expand-file-name ".git" directory))
+ (list (file-name-as-directory directory)))
+ ((and (> depth 0) (magit-file-accessible-directory-p directory))
+ (--mapcat (and (file-directory-p it)
+ (magit-list-repos-1 it (1- depth)))
+ (directory-files directory t
+ directory-files-no-dot-files-regexp t)))))
+
+(defun magit-list-repos-uniquify (alist)
+ (let (result (dict (make-hash-table :test 'equal)))
+ (dolist (a (delete-dups alist))
+ (puthash (car a) (cons (cdr a) (gethash (car a) dict)) dict))
+ (maphash
+ (lambda (key value)
+ (if (= (length value) 1)
+ (push (cons key (car value)) result)
+ (setq result
+ (append result
+ (magit-list-repos-uniquify
+ (--map (cons (concat
+ key "\\"
+ (file-name-nondirectory
+ (directory-file-name
+ (substring it 0 (- (1+ (length key)))))))
+ it)
+ value))))))
+ dict)
+ result))
+
+(defun magit-repos-alist ()
+ (magit-list-repos-uniquify
+ (--map (cons (file-name-nondirectory (directory-file-name it)) it)
+ (magit-list-repos))))
+
+;;; _
+(provide 'magit-repos)
+;;; magit-repos.el ends here
Copyright 2019--2024 Marius PETER