all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#56227: 29.0.50; Eshell globs ending with '/' should match directories only
@ 2022-06-26  1:45 Jim Porter
  2022-06-26  1:52 ` Jim Porter
  0 siblings, 1 reply; 3+ messages in thread
From: Jim Porter @ 2022-06-26  1:45 UTC (permalink / raw)
  To: 56227

In regular shells, the pattern '*' matches any (non-dot) file, whereas 
'*/' only matches directories:

   $ echo *
   admin args config.log config.status doc etc leim lib lib-src lisp
   lwlib Makefile nextstep nt oldXMenu src test
   $ echo */
   admin/ doc/ etc/ leim/ lib/ lib-src/ lisp/ lwlib/ nextstep/ nt/
   oldXMenu/ src/ test/

However, in Eshell, both of these do the same thing:

   ~ $ echo *
   ("Makefile" "admin/" "args" "config.log" "config.status" "doc/" "etc/"
   "leim/" "lib-src/" "lib/" "lisp/" "lwlib/" "nextstep/" "nt/"
   "oldXMenu/" "src/" "test/")
   ~ $ echo */
   ("Makefile" "admin/" "args" "config.log" "config.status" "doc/" "etc/"
   "leim/" "lib-src/" "lib/" "lisp/" "lwlib/" "nextstep/" "nt/"
   "oldXMenu/" "src/" "test/")

For consistency with other shells, and to make Eshell's globbing more 
expressive, I think we should make the latter match directories only. 
While this technically changes an existing behavior, it should be a safe 
change to make, since there's currently no reason I'm aware of to add a 
trailing slash to an Eshell glob.

Patch forthcoming; just getting a bug number...





^ permalink raw reply	[flat|nested] 3+ messages in thread

* bug#56227: 29.0.50; Eshell globs ending with '/' should match directories only
  2022-06-26  1:45 bug#56227: 29.0.50; Eshell globs ending with '/' should match directories only Jim Porter
@ 2022-06-26  1:52 ` Jim Porter
  2022-06-26 14:52   ` Lars Ingebrigtsen
  0 siblings, 1 reply; 3+ messages in thread
From: Jim Porter @ 2022-06-26  1:52 UTC (permalink / raw)
  To: 56227

[-- Attachment #1: Type: text/plain, Size: 772 bytes --]

On 6/25/2022 6:45 PM, Jim Porter wrote:
> For consistency with other shells, and to make Eshell's globbing more 
> expressive, I think we should make the latter match directories only. 
> While this technically changes an existing behavior, it should be a safe 
> change to make, since there's currently no reason I'm aware of to add a 
> trailing slash to an Eshell glob.

Here's the patch. The first part refactors em-glob.el a bit, and 
converts globs into regexps ahead of time. This is partly to make the 
second patch simpler, but also improves performance by a few percent 
when `eshell-glob-entries' examines many directories: it no longer 
repeatedly converts the same glob component for each sibling directory.

The second part is the actual patch for this bug.

[-- Attachment #2: 0001-Convert-Eshell-globs-ahead-of-time-instead-of-doing-.patch --]
[-- Type: text/plain, Size: 11168 bytes --]

From 5b1095e56ff91e4889ea93fd9782f6a432aa4c61 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Fri, 24 Jun 2022 08:39:42 -0700
Subject: [PATCH 1/2] Convert Eshell globs ahead of time instead of doing it
 repeatedly

* lisp/eshell/em-glob.el (eshell-glob-recursive): New variable.
(eshell-glob-convert-1, eshell-glob-convert): New functions.
(eshell-extended-glob): Use 'eshell-glob-convert'.
(eshell-glob-entries): Adapt function to use pre-converted globs.

* test/lisp/eshell-em-glob-tests.el (em-glob-test/match-dot-files):
New test.
---
 lisp/eshell/em-glob.el            | 204 +++++++++++++++++-------------
 test/lisp/eshell/em-glob-tests.el |  15 +++
 2 files changed, 129 insertions(+), 90 deletions(-)

diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index 52531ff893..8acdaee233 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -183,6 +183,10 @@ eshell-glob-chars-regexp
 (defvar eshell-glob-matches)
 (defvar message-shown)
 
+(defvar eshell-glob-recursive-alist
+  '(("**/" . recurse)
+    ("***/" . recurse-symlink)))
+
 (defun eshell-glob-regexp (pattern)
   "Convert glob-pattern PATTERN to a regular expression.
 The basic syntax is:
@@ -232,6 +236,74 @@ eshell-glob-regexp
 	    (regexp-quote (substring pattern matched-in-pattern))
 	    "\\'")))
 
+(defun eshell-glob-convert-1 (glob &optional last)
+  "Convert a GLOB matching a single element of a file name to regexps.
+If LAST is non-nil, this glob is the last element of a file name.
+
+The result is a pair of regexps, the first for file names to
+include, and the second for ones to exclude."
+  (let ((len (length glob)) (index 1) (incl glob) excl)
+    ;; We can't use `directory-file-name' because it strips away text
+    ;; properties in the string.
+    (let ((last (1- (length incl))))
+      (when (eq (aref incl last) ?/)
+        (setq incl (substring incl 0 last))))
+    ;; Split the glob if it contains a negation like x~y.
+    (while (and (eq incl glob)
+                (setq index (string-search "~" glob index)))
+      (if (or (get-text-property index 'escaped glob)
+              (or (= (1+ index) len)))
+          (setq index (1+ index))
+        (setq incl (substring glob 0 index)
+              excl (substring glob (1+ index)))))
+    (setq incl (eshell-glob-regexp incl)
+          excl (and excl (eshell-glob-regexp excl)))
+    ;; Exclude dot files if requested.
+    (if (or eshell-glob-include-dot-files
+            (eq (aref glob 0) ?.))
+        (unless (or eshell-glob-include-dot-dot
+                    (not last))
+          (setq excl (if excl
+                         (concat "\\(\\`\\.\\.?\\'\\|" excl "\\)")
+                       "\\`\\.\\.?\\'")))
+      (setq excl (if excl
+                     (concat "\\(\\`\\.\\|" excl "\\)")
+                   "\\`\\.")))
+    (cons incl excl)))
+
+(defun eshell-glob-convert (glob)
+  "Convert an Eshell glob-pattern GLOB to regexps.
+The result is a list, where the first element is the base
+directory to search in, and the second is a list containing
+elements of the following forms:
+
+* Regexp pairs as generated by `eshell-glob-convert-1'.
+
+* `recurse', indicating that searches should recurse into
+  subdirectories.
+
+* `recurse-symlink', like `recurse', but also following symlinks."
+  (let ((globs (eshell-split-path glob))
+        start-dir result last-saw-recursion)
+    (if (and (cdr globs)
+             (file-name-absolute-p (car globs)))
+        (setq start-dir (car globs)
+              globs (cdr globs))
+      (setq start-dir "."))
+    (while globs
+      (if-let ((recurse (cdr (assoc (car globs)
+                                    eshell-glob-recursive-alist))))
+          (if last-saw-recursion
+              (setcar result recurse)
+            (push recurse result)
+            (setq last-saw-recursion t))
+        (push (eshell-glob-convert-1 (car globs) (null (cdr globs)))
+              result)
+        (setq last-saw-recursion nil))
+      (setq globs (cdr globs)))
+    (list (file-name-as-directory start-dir)
+          (nreverse result))))
+
 (defun eshell-extended-glob (glob)
   "Return a list of files matched by GLOB.
 If no files match, signal an error (if `eshell-error-if-no-glob'
@@ -247,14 +319,10 @@ eshell-extended-glob
 
 Mainly they are not supported because file matching is done with Emacs
 regular expressions, and these cannot support the above constructs."
-  (let ((paths (eshell-split-path glob))
+  (let ((globs (eshell-glob-convert glob))
         eshell-glob-matches message-shown)
     (unwind-protect
-	(if (and (cdr paths)
-		 (file-name-absolute-p (car paths)))
-	    (eshell-glob-entries (file-name-as-directory (car paths))
-				 (cdr paths))
-	  (eshell-glob-entries (file-name-as-directory ".") paths))
+        (apply #'eshell-glob-entries globs)
       (if message-shown
 	  (message nil)))
     (or (and eshell-glob-matches (sort eshell-glob-matches #'string<))
@@ -263,94 +331,50 @@ eshell-extended-glob
 	  glob))))
 
 ;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
-(defun eshell-glob-entries (path globs &optional recurse-p)
-  "Glob the entries in PATH, possibly recursing if RECURSE-P is non-nil."
+(defun eshell-glob-entries (path globs)
+  "Match the entries in PATH against GLOBS.
+GLOBS is a list of globs as converted by `eshell-glob-convert',
+which see."
   (let* ((entries (ignore-errors
-		    (file-name-all-completions "" path)))
-	 (case-fold-search eshell-glob-case-insensitive)
-	 (glob (car globs))
-	 (len (length glob))
-	 dirs rdirs
-	 incl excl
-	 name isdir pathname)
-    (while (cond
-	    ((and (= len 3) (equal glob "**/"))
-	     (setq recurse-p 2
-		   globs (cdr globs)
-		   glob (car globs)
-		   len (length glob)))
-	    ((and (= len 4) (equal glob "***/"))
-	     (setq recurse-p 3
-		   globs (cdr globs)
-		   glob (car globs)
-		   len (length glob)))))
-    (if (and recurse-p (not glob))
-	(error "`**/' cannot end a globbing pattern"))
-    (let ((index 1))
-      (setq incl glob)
-      (while (and (eq incl glob)
-		  (setq index (string-search "~" glob index)))
-	(if (or (get-text-property index 'escaped glob)
-		(or (= (1+ index) len)))
-	    (setq index (1+ index))
-	  (setq incl (substring glob 0 index)
-		excl (substring glob (1+ index))))))
-    ;; can't use `directory-file-name' because it strips away text
-    ;; properties in the string
-    (let ((len (1- (length incl))))
-      (if (eq (aref incl len) ?/)
-	  (setq incl (substring incl 0 len)))
-      (when excl
-	(setq len (1- (length excl)))
-	(if (eq (aref excl len) ?/)
-	    (setq excl (substring excl 0 len)))))
-    (setq incl (eshell-glob-regexp incl)
-	  excl (and excl (eshell-glob-regexp excl)))
-    (if (or eshell-glob-include-dot-files
-	    (eq (aref glob 0) ?.))
-	(unless (or eshell-glob-include-dot-dot
-		    (cdr globs))
-	  (setq excl (if excl
-			 (concat "\\(\\`\\.\\.?\\'\\|" excl "\\)")
-		       "\\`\\.\\.?\\'")))
-      (setq excl (if excl
-		     (concat "\\(\\`\\.\\|" excl "\\)")
-		   "\\`\\.")))
+                    (file-name-all-completions "" path)))
+         (case-fold-search eshell-glob-case-insensitive)
+         glob glob-remainder recurse-p)
+    (if (rassq (car globs) eshell-glob-recursive-alist)
+        (setq recurse-p (car globs)
+              glob (cadr globs)
+              glob-remainder (cddr globs))
+      (setq glob (car globs)
+            glob-remainder (cdr globs)))
     (when (and recurse-p eshell-glob-show-progress)
       (message "Building file list...%d so far: %s"
-	       (length eshell-glob-matches) path)
+               (length eshell-glob-matches) path)
       (setq message-shown t))
-    (if (equal path "./") (setq path ""))
-    (while entries
-      (setq name (car entries)
-	    len (length name)
-	    isdir (eq (aref name (1- len)) ?/))
-      (if (let ((fname (directory-file-name name)))
-	    (and (not (and excl (string-match excl fname)))
-		 (string-match incl fname)))
-	  (if (cdr globs)
-	      (if isdir
-		  (setq dirs (cons (concat path name) dirs)))
-	    (setq eshell-glob-matches
-		  (cons (concat path name) eshell-glob-matches))))
-      (if (and recurse-p isdir
-	       (or (> len 3)
-		   (not (or (and (= len 2) (equal name "./"))
-			    (and (= len 3) (equal name "../")))))
-	       (setq pathname (concat path name))
-	       (not (and (= recurse-p 2)
-			 (file-symlink-p
-			  (directory-file-name pathname)))))
-	  (setq rdirs (cons pathname rdirs)))
-      (setq entries (cdr entries)))
-    (setq dirs (nreverse dirs)
-	  rdirs (nreverse rdirs))
-    (while dirs
-      (eshell-glob-entries (car dirs) (cdr globs))
-      (setq dirs (cdr dirs)))
-    (while rdirs
-      (eshell-glob-entries (car rdirs) globs recurse-p)
-      (setq rdirs (cdr rdirs)))))
+    (when (equal path "./") (setq path ""))
+    (let ((incl (car glob))
+          (excl (cdr glob))
+          dirs rdirs)
+      (dolist (name entries)
+        (let* ((len (length name))
+               (isdir (eq (aref name (1- len)) ?/))
+               pathname)
+          (when (let ((fname (directory-file-name name)))
+                  (and (not (and excl (string-match excl fname)))
+                       (string-match incl fname)))
+            (if glob-remainder
+                (when isdir
+                  (push (concat path name) dirs))
+              (push (concat path name) eshell-glob-matches)))
+          (when (and recurse-p isdir
+                     (not (member name '("./" "../")))
+                     (setq pathname (concat path name))
+                     (not (and (eq recurse-p 'recurse)
+                               (file-symlink-p
+                                (directory-file-name pathname)))))
+            (push pathname rdirs))))
+      (dolist (dir (nreverse dirs))
+        (eshell-glob-entries dir glob-remainder))
+      (dolist (rdir (nreverse rdirs))
+        (eshell-glob-entries rdir globs)))))
 
 (provide 'em-glob)
 
diff --git a/test/lisp/eshell/em-glob-tests.el b/test/lisp/eshell/em-glob-tests.el
index 9976b32ffe..65f340a8da 100644
--- a/test/lisp/eshell/em-glob-tests.el
+++ b/test/lisp/eshell/em-glob-tests.el
@@ -160,6 +160,21 @@ em-glob-test/match-x-but-not-y
     (should (equal (eshell-extended-glob "[[:digit:]]##~4?")
                    '("1" "12" "123")))))
 
+(ert-deftest em-glob-test/match-dot-files ()
+  "Test that dot files are matched correctly."
+  (with-fake-files '("foo.el" ".emacs")
+    (should (equal (eshell-extended-glob ".*")
+                   '("../" "./" ".emacs")))
+    (let (eshell-glob-include-dot-dot)
+      (should (equal (eshell-extended-glob ".*")
+                     '(".emacs"))))
+    (let ((eshell-glob-include-dot-files t))
+      (should (equal (eshell-extended-glob "*")
+                     '("../" "./" ".emacs" "foo.el")))
+      (let (eshell-glob-include-dot-dot)
+        (should (equal (eshell-extended-glob "*")
+                       '(".emacs" "foo.el")))))))
+
 (ert-deftest em-glob-test/no-matches ()
   "Test behavior when a glob fails to match any files."
   (with-fake-files '("foo.el" "bar.el")
-- 
2.25.1


[-- Attachment #3: 0002-Make-Eshell-globs-ending-in-match-directories-only.patch --]
[-- Type: text/plain, Size: 7295 bytes --]

From 0247fc4187db5c15668deff1ca67a0566031014d Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sat, 25 Jun 2022 18:19:01 -0700
Subject: [PATCH 2/2] Make Eshell globs ending in "/" match directories only

* lisp/eshell/em-glob.el (eshell-glob-convert): Return whether to
match directories only.
(eshell-glob-entries): Add ONLY-DIRS argument.

* test/lisp/eshell/em-glob-tests.el
(em-glob-test/match-any-directory): New test.
(em-glob-test/match-recursive)
(em-glob-test/match-recursive-follow-symlinks): Add test cases for
when "**/" or "***/" are the last components in a glob.

* etc/NEWS: Announce this change (bug#56227).
---
 etc/NEWS                          |  6 +++++
 lisp/eshell/em-glob.el            | 45 +++++++++++++++++++++----------
 test/lisp/eshell/em-glob-tests.el | 15 +++++++++--
 3 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 40658559d7..32a780129c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1817,6 +1817,12 @@ values passed as a single token, such as '-oVALUE' or
 'eshell-eval-using-options' macro.  See "Defining new built-in
 commands" in the "(eshell) Built-ins" node of the Eshell manual.
 
+---
+*** Eshell globs ending with '/' now match only directories.
+Additionally, globs ending with '**/' or '***/' no longer raise an
+error, and now expand to all directories recursively (following
+symlinks in the latter case).
+
 ** Shell
 
 ---
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index 8acdaee233..58b7a83c09 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -273,17 +273,23 @@ eshell-glob-convert-1
 
 (defun eshell-glob-convert (glob)
   "Convert an Eshell glob-pattern GLOB to regexps.
-The result is a list, where the first element is the base
-directory to search in, and the second is a list containing
-elements of the following forms:
+The result is a list of three elements:
 
-* Regexp pairs as generated by `eshell-glob-convert-1'.
+1. The base directory to search in.
 
-* `recurse', indicating that searches should recurse into
-  subdirectories.
+2. A list containing elements of the following forms:
 
-* `recurse-symlink', like `recurse', but also following symlinks."
+   * Regexp pairs as generated by `eshell-glob-convert-1'.
+
+   * `recurse', indicating that searches should recurse into
+     subdirectories.
+
+   * `recurse-symlink', like `recurse', but also following
+     symlinks.
+
+3. A boolean indicating whether to match directories only."
   (let ((globs (eshell-split-path glob))
+        (isdir (eq (aref glob (1- (length glob))) ?/))
         start-dir result last-saw-recursion)
     (if (and (cdr globs)
              (file-name-absolute-p (car globs)))
@@ -302,7 +308,8 @@ eshell-glob-convert
         (setq last-saw-recursion nil))
       (setq globs (cdr globs)))
     (list (file-name-as-directory start-dir)
-          (nreverse result))))
+          (nreverse result)
+          isdir)))
 
 (defun eshell-extended-glob (glob)
   "Return a list of files matched by GLOB.
@@ -331,17 +338,21 @@ eshell-extended-glob
 	  glob))))
 
 ;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
-(defun eshell-glob-entries (path globs)
+(defun eshell-glob-entries (path globs only-dirs)
   "Match the entries in PATH against GLOBS.
 GLOBS is a list of globs as converted by `eshell-glob-convert',
-which see."
+which see.
+
+If ONLY-DIRS is non-nil, only match directories; otherwise, match
+directories and files."
   (let* ((entries (ignore-errors
                     (file-name-all-completions "" path)))
          (case-fold-search eshell-glob-case-insensitive)
          glob glob-remainder recurse-p)
     (if (rassq (car globs) eshell-glob-recursive-alist)
         (setq recurse-p (car globs)
-              glob (cadr globs)
+              glob (or (cadr globs)
+                       (eshell-glob-convert-1 "*" t))
               glob-remainder (cddr globs))
       (setq glob (car globs)
             glob-remainder (cdr globs)))
@@ -363,7 +374,13 @@ eshell-glob-entries
             (if glob-remainder
                 (when isdir
                   (push (concat path name) dirs))
-              (push (concat path name) eshell-glob-matches)))
+              (when (or (not only-dirs)
+                        (and isdir
+                             (not (and (eq recurse-p 'recurse)
+                                       (file-symlink-p
+                                        (directory-file-name
+                                         (concat path name)))))))
+                (push (concat path name) eshell-glob-matches))))
           (when (and recurse-p isdir
                      (not (member name '("./" "../")))
                      (setq pathname (concat path name))
@@ -372,9 +389,9 @@ eshell-glob-entries
                                 (directory-file-name pathname)))))
             (push pathname rdirs))))
       (dolist (dir (nreverse dirs))
-        (eshell-glob-entries dir glob-remainder))
+        (eshell-glob-entries dir glob-remainder only-dirs))
       (dolist (rdir (nreverse rdirs))
-        (eshell-glob-entries rdir globs)))))
+        (eshell-glob-entries rdir globs only-dirs)))))
 
 (provide 'em-glob)
 
diff --git a/test/lisp/eshell/em-glob-tests.el b/test/lisp/eshell/em-glob-tests.el
index 65f340a8da..b733be35d9 100644
--- a/test/lisp/eshell/em-glob-tests.el
+++ b/test/lisp/eshell/em-glob-tests.el
@@ -60,6 +60,12 @@ em-glob-test/match-any-string
     (should (equal (eshell-extended-glob "*.el")
                    '("a.el" "b.el")))))
 
+(ert-deftest em-glob-test/match-any-directory ()
+  "Test that \"*/\" pattern matches any directory."
+  (with-fake-files '("a.el" "b.el" "dir/a.el" "dir/sub/a.el" "symlink/")
+    (should (equal (eshell-extended-glob "*/")
+                   '("dir/" "symlink/")))))
+
 (ert-deftest em-glob-test/match-any-character ()
   "Test that \"?\" pattern matches any character."
   (with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el")
@@ -71,7 +77,9 @@ em-glob-test/match-recursive
   (with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el" "dir/sub/a.el"
                      "dir/symlink/a.el" "symlink/a.el" "symlink/sub/a.el")
     (should (equal (eshell-extended-glob "**/a.el")
-                   '("a.el" "dir/a.el" "dir/sub/a.el")))))
+                   '("a.el" "dir/a.el" "dir/sub/a.el")))
+    (should (equal (eshell-extended-glob "**/")
+                   '("dir/" "dir/sub/")))))
 
 (ert-deftest em-glob-test/match-recursive-follow-symlinks ()
   "Test that \"***/\" recursively matches directories, following symlinks."
@@ -79,7 +87,10 @@ em-glob-test/match-recursive-follow-symlinks
                      "dir/symlink/a.el" "symlink/a.el" "symlink/sub/a.el")
     (should (equal (eshell-extended-glob "***/a.el")
                    '("a.el" "dir/a.el" "dir/sub/a.el" "dir/symlink/a.el"
-                     "symlink/a.el" "symlink/sub/a.el")))))
+                     "symlink/a.el" "symlink/sub/a.el")))
+    (should (equal (eshell-extended-glob "***/")
+                   '("dir/" "dir/sub/" "dir/symlink/" "symlink/"
+                     "symlink/sub/")))))
 
 (ert-deftest em-glob-test/match-recursive-mixed ()
   "Test combination of \"**/\" and \"***/\"."
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* bug#56227: 29.0.50; Eshell globs ending with '/' should match directories only
  2022-06-26  1:52 ` Jim Porter
@ 2022-06-26 14:52   ` Lars Ingebrigtsen
  0 siblings, 0 replies; 3+ messages in thread
From: Lars Ingebrigtsen @ 2022-06-26 14:52 UTC (permalink / raw)
  To: Jim Porter; +Cc: 56227

Jim Porter <jporterbugs@gmail.com> writes:

> Here's the patch. The first part refactors em-glob.el a bit, and
> converts globs into regexps ahead of time. This is partly to make the
> second patch simpler, but also improves performance by a few percent
> when `eshell-glob-entries' examines many directories: it no longer
> repeatedly converts the same glob component for each sibling
> directory.
>
> The second part is the actual patch for this bug.

Thanks; pushed to Emacs 29.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2022-06-26 14:52 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-06-26  1:45 bug#56227: 29.0.50; Eshell globs ending with '/' should match directories only Jim Porter
2022-06-26  1:52 ` Jim Porter
2022-06-26 14:52   ` Lars Ingebrigtsen

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.