branch: externals/tramp
commit 01599cd23fff35b4f90f9125a02bb281fb141958
Author: Michael Albinus <michael.albi...@gmx.de>
Commit: Michael Albinus <michael.albi...@gmx.de>

    Tramp ELPA version 2.8.0.2 released
---
 README                      |   6 +-
 test/tramp-archive-tests.el |   6 +-
 test/tramp-tests.el         | 333 +++++++++++++++++++++++++++++---------
 texi/tramp.texi             | 128 ++++++++++++++-
 texi/trampver.texi          |   2 +-
 tramp-adb.el                |   4 +-
 tramp-cache.el              |  23 +--
 tramp-compat.el             |   4 +-
 tramp-gvfs.el               |   2 +-
 tramp-message.el            |  18 ++-
 tramp-sh.el                 |  74 ++++++---
 tramp-smb.el                |  11 +-
 tramp.el                    | 380 +++++++++++++++++++++++++++++++-------------
 trampver.el                 |   6 +-
 14 files changed, 760 insertions(+), 237 deletions(-)

diff --git a/README b/README
index 25aca1bb1b..e51a5c9d70 100644
--- a/README
+++ b/README
@@ -32,11 +32,11 @@ Emacs 28 or older
 
    • Remove all byte-compiled Tramp files
 
-          $ rm -f ~/.emacs.d/elpa/tramp-2.8.0.1/tramp*.elc
+          $ rm -f ~/.emacs.d/elpa/tramp-2.8.0.2/tramp*.elc
 
    • Start Emacs with Tramp's source files
 
-          $ emacs -L ~/.emacs.d/elpa/tramp-2.8.0.1 -l tramp
+          $ emacs -L ~/.emacs.d/elpa/tramp-2.8.0.2 -l tramp
 
      This should not give you the error.
 
@@ -50,7 +50,7 @@ Mitigation of a bug in Emacs 29.1
 ---------------------------------
 
 Due to a bug in Emacs 29.1, you must apply the following change prior
-installation or upgrading Tramp 2.8.0.1 from GNU ELPA:
+installation or upgrading Tramp 2.8.0.2 from GNU ELPA:
 
      (when (string-equal emacs-version "29.1")
        (with-current-buffer
diff --git a/test/tramp-archive-tests.el b/test/tramp-archive-tests.el
index 5846dafa7d..0ff0f3ddd4 100644
--- a/test/tramp-archive-tests.el
+++ b/test/tramp-archive-tests.el
@@ -856,7 +856,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
       (should (equal uid (with-no-warnings (file-user-uid))))
       (should (equal gid (with-no-warnings (file-group-gid)))))))
 
-(ert-deftest tramp-archive-test48-auto-load ()
+(ert-deftest tramp-archive-test50-auto-load ()
   "Check that `tramp-archive' autoloads properly."
   :tags '(:expensive-test)
   (skip-unless tramp-archive-enabled)
@@ -898,7 +898,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
               (format "(setq tramp-archive-enabled %s)" enabled))
              (shell-quote-argument (format code file)))))))))))
 
-(ert-deftest tramp-archive-test48-delay-load ()
+(ert-deftest tramp-archive-test50-delay-load ()
   "Check that `tramp-archive' is loaded lazily, only when needed."
   :tags '(:expensive-test)
   (skip-unless tramp-archive-enabled)
@@ -937,7 +937,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
             code tae tramp-archive-test-file-archive
             (concat tramp-archive-test-archive "foo"))))))))))
 
-(ert-deftest tramp-archive-test49-without-remote-files ()
+(ert-deftest tramp-archive-test51-without-remote-files ()
   "Check that Tramp can be suppressed."
   (skip-unless tramp-archive-enabled)
 
diff --git a/test/tramp-tests.el b/test/tramp-tests.el
index d0ce17d249..892e4ef519 100644
--- a/test/tramp-tests.el
+++ b/test/tramp-tests.el
@@ -68,6 +68,7 @@
 (require 'vc-git)
 (require 'vc-hg)
 
+(declare-function edebug-mode "edebug")
 (declare-function project-mode-line-format "project")
 (declare-function tramp-check-remote-uname "tramp-sh")
 (declare-function tramp-find-executable "tramp-sh")
@@ -215,7 +216,10 @@ is greater than 10.
           (kill-buffer buf))))))
 
 (defsubst tramp--test-message (fmt-string &rest arguments)
-  "Emit a message into ERT *Messages*."
+  "Emit a message into \"ERT *Messages*\" and the trace buffer."
+  (declare (tramp-suppress-trace t))
+  (when (get-buffer trace-buffer)
+    (trace-values (apply #'format fmt-string arguments)))
   (tramp--test-instrument-test-case 0
     (apply #'tramp-message tramp-test-vec 0 fmt-string arguments)))
 
@@ -4552,7 +4556,8 @@ This tests also `make-symbolic-link', `file-truename' and 
`add-name-to-file'."
   (skip-unless (tramp--test-enabled))
   (skip-unless
    (or (tramp--test-adb-p) (tramp--test-gvfs-p)
-       (tramp--test-sh-p) (tramp--test-sudoedit-p)))
+       (tramp--test-sh-p) (tramp--test-smb-p)
+       (tramp--test-sudoedit-p)))
 
   (dolist (quoted (if (tramp--test-expensive-test-p) '(nil t) '(nil)))
     (let ((tmp-name1 (tramp--test-make-temp-name nil quoted))
@@ -4565,9 +4570,10 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
            (should (consp (file-attribute-modification-time
                            (file-attributes tmp-name1))))
            ;; Skip the test, if the remote handler is not able to set
-           ;; the correct time.
-           ;; Some remote machines cannot resolve seconds.  So we use a minute.
-           (skip-unless (set-file-times tmp-name1 (seconds-to-time 60)))
+           ;; the correct time.  Some remote machines cannot resolve
+           ;; seconds.  tramp-adb.el needs at least a day.
+           (skip-unless
+            (set-file-times tmp-name1 (seconds-to-time (* 24 60 60))))
            ;; Dumb remote shells without perl(1) or stat(1) are not
            ;; able to return the date correctly.  They say "don't know".
            (unless (time-equal-p
@@ -4577,7 +4583,7 @@ This tests also `make-symbolic-link', `file-truename' and 
`add-name-to-file'."
              (should
               (time-equal-p
                 (file-attribute-modification-time (file-attributes tmp-name1))
-               (seconds-to-time 60)))
+               (seconds-to-time (* 24 60 60))))
              ;; Setting the time for not existing files shall fail.
              (should-error
               (set-file-times tmp-name2)
@@ -4855,6 +4861,8 @@ This tests also `make-symbolic-link', `file-truename' and 
`add-name-to-file'."
        (host (file-remote-p ert-remote-temporary-file-directory 'host))
         (orig-syntax tramp-syntax)
         (minibuffer-completing-file-name t))
+    ;; `file-remote-p' returns as host the string "host#port", which
+    ;; isn't useful.
     (when (and (stringp host)
               (string-match
                (rx (regexp tramp-prefix-port-regexp) (regexp 
tramp-port-regexp))
@@ -4866,7 +4874,8 @@ This tests also `make-symbolic-link', `file-truename' and 
`add-name-to-file'."
                            (tramp-syntax-values) `(,orig-syntax)))
           (tramp-change-syntax syntax)
          ;; This has cleaned up all connection data, which are used
-         ;; for completion.  We must refill the cache.
+         ;; for completion.  We must refill the cache in order to get
+         ;; at least one completion candidate.
          (tramp-set-connection-property tramp-test-vec "completion-use-cache" 
t)
 
           (let (;; This is needed for the `separate' syntax.
@@ -4881,6 +4890,14 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
             ;; Complete method name.
            (unless (or (tramp-string-empty-or-nil-p method)
                         (string-empty-p tramp-method-regexp))
+             ;; `read-directory-name' uses `file-directory-p'.
+             ;; `file-directory-p' works since Emacs 31.
+             ;; (Bug#79236)
+             (when (tramp--test-emacs31-p)
+               (should
+                (file-name-completion
+                 (concat prefix-format (substring method 0 1))
+                 "/" #'file-directory-p)))
              (should
               (member
                (concat prefix-format method tramp-postfix-method-format)
@@ -4890,6 +4907,14 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
            (unless (or (tramp-string-empty-or-nil-p method)
                         (string-empty-p tramp-method-regexp)
                         (tramp-string-empty-or-nil-p host))
+             ;; `read-directory-name' uses `file-directory-p'.
+             ;; `file-directory-p' works since Emacs 31.
+             ;; (Bug#79236)
+             (when (tramp--test-emacs31-p)
+               (should
+                (file-name-completion
+                 (concat prefix-format method tramp-postfix-method-format)
+                 "/" #'file-directory-p)))
              (should
               (member
                (concat
@@ -4981,6 +5006,17 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
 ;; and Bug#60505.
 (ert-deftest tramp-test26-interactive-file-name-completion ()
   "Check interactive completion with different `completion-styles'."
+  (skip-unless (tramp--test-enabled))
+
+  ;; (when (get-buffer trace-buffer) (kill-buffer trace-buffer))
+  ;; (dolist (elt (append
+  ;;           (mapcar
+  ;;            #'intern (all-completions "tramp-" obarray #'functionp))
+  ;;           tramp-trace-functions))
+  ;;   (unless (get elt 'tramp-suppress-trace)
+  ;;     (trace-function-background elt)))
+  ;; (trace-function-background #'completion-file-name-table)
+  ;; (trace-function-background #'read-file-name)
 
   ;; Method, user and host name in completion mode.
   (let ((method (file-remote-p ert-remote-temporary-file-directory 'method))
@@ -4989,39 +5025,54 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
        (hop (file-remote-p ert-remote-temporary-file-directory 'hop))
         (orig-syntax tramp-syntax)
         (non-essential t)
-       (inhibit-message t))
+       (inhibit-message (not (ignore-errors (edebug-mode)))))
+    ;; `file-remote-p' returns as host the string "host#port", which
+    ;; isn't useful.
     (when (and (stringp host)
               (string-match
                (rx (regexp tramp-prefix-port-regexp) (regexp 
tramp-port-regexp))
                host))
       (setq host (replace-match "" nil nil host)))
 
-    ;; (trace-function #'tramp-completion-file-name-handler)
-    ;; (trace-function #'completion-file-name-table)
     (unwind-protect
         (dolist (syntax (if (tramp--test-expensive-test-p)
                            (tramp-syntax-values) `(,orig-syntax)))
           (tramp-change-syntax syntax)
          ;; This has cleaned up all connection data, which are used
-         ;; for completion.  We must refill the cache.
+         ;; for completion.  We must refill the cache in order to get
+         ;; at least one completion candidate.
          (tramp-set-connection-property tramp-test-vec "completion-use-cache" 
t)
 
           (dolist
               (style
                (if (tramp--test-expensive-test-p)
-                   ;; It doesn't work for `initials' and `shorthand'
-                   ;; completion styles.  Should it?
+                   ;; FIXME: It doesn't work for `initials' and
+                   ;; `shorthand' completion styles.  Should it?
                   ;; `orderless' passes the tests, but it is an ELPA package.
-                   '(emacs21 emacs22 basic partial-completion substring flex)
+                  ;; What about `company' backends, `consult', `cider', `helm'?
+                   `(emacs21 emacs22 basic partial-completion substring
+                    ;; FIXME: `flex' is not compatible with IPv6 hosts.
+                    ,@(unless (string-match-p tramp-ipv6-regexp host) '(flex)))
                 '(basic)))
 
            (when (assoc style completion-styles-alist)
              (let* (;; Force the real minibuffer in batch mode.
                      (executing-kbd-macro noninteractive)
+                    ;; FIXME: Is this TRT for test?
+                    (minibuffer-completing-file-name t)
+                    (confirm-nonexistent-file-or-buffer nil)
                      (completion-styles `(,style))
                      completion-category-defaults
                      completion-category-overrides
-                     ;; This is needed for the `simplified' syntax,
+                    ;; FIXME: Is this TRT for test?
+                    (completion-pcm--delim-wild-regex
+                     ;; "::1" is a complete word.  ":" isn't a
+                     ;; delimiter, therefore.
+                     (rx-to-string
+                      `(any
+                        ,(string-replace
+                          ":" "" completion-pcm-word-delimiters))))
+                    ;; This is needed for the `simplified' syntax.
                      (tramp-default-method method)
                      (method-string
                      (unless (string-empty-p tramp-method-regexp)
@@ -5099,60 +5150,78 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
                             user-string host-string)
                           ,host-string)))))
 
-                  (ignore-errors (kill-buffer "*Completions*"))
-                  ;; (and (bufferp trace-buffer) (kill-buffer trace-buffer))
-                  (discard-input)
-                  (setq test (car test-and-result)
-                        unread-command-events
-                        (mapcar #'identity (concat test "\t\t\n"))
-                        completions nil
-                        result (read-file-name "Prompt: "))
-
-                  (if (or (not (get-buffer "*Completions*"))
-                         (string-match-p
-                          (if (string-empty-p tramp-method-regexp)
+                 (dolist
+                     (predicate
+                      (if (and (tramp--test-expensive-test-p)
+                               (tramp--test-emacs31-p))
+                          ;; `nil' will be expanded to `file-exists-p'.
+                          ;; `read-directory-name' uses `file-directory-p'.
+                          ;; `file-directory-p' works since Emacs 31.
+                          ;; (Bug#79236)
+                          '(file-exists-p file-directory-p) '(nil)))
+
+                    (ignore-errors (kill-buffer "*Completions*"))
+                   ;; (when (get-buffer trace-buffer)
+                   ;;   (kill-buffer trace-buffer))
+                    (discard-input)
+                    (setq test (car test-and-result)
+                          unread-command-events
+                         (append test '(tab tab return return))
+                          completions nil
+                          result
+                         (read-file-name
+                          "Prompt: " nil nil 'confirm nil predicate))
+
+                    (if (or (not (get-buffer "*Completions*"))
+                           (string-match-p
+                            (if (string-empty-p tramp-method-regexp)
+                                (rx
+                                 (| (regexp tramp-postfix-user-regexp)
+                                    (regexp tramp-postfix-host-regexp))
+                                 eos)
                               (rx
-                               (| (regexp tramp-postfix-user-regexp)
+                               (| (regexp tramp-postfix-method-regexp)
+                                  (regexp tramp-postfix-user-regexp)
                                   (regexp tramp-postfix-host-regexp))
-                               eos)
-                            (rx
-                             (| (regexp tramp-postfix-method-regexp)
-                                (regexp tramp-postfix-user-regexp)
-                                (regexp tramp-postfix-host-regexp))
-                             eos))
-                          result))
-                     (progn
-                        ;; (tramp--test-message
-                        ;;  "syntax: %s style: %s test: %s result: %s"
-                        ;;  syntax style test result)
-                       (should (string-prefix-p (cadr test-and-result) 
result)))
-
-                    (with-current-buffer "*Completions*"
-                     ;; We must remove leading `default-directory'.
-                     (goto-char (point-min))
-                     (let ((inhibit-read-only t))
-                       (while (search-forward-regexp "//" nil 'noerror)
-                         (delete-region (line-beginning-position) (point))))
-                     (goto-char (point-min))
-                     (search-forward-regexp
-                      (rx bol (0+ nonl)
-                          (any "Pp") "ossible completions"
-                          (0+ nonl) eol))
-                     (forward-line 1)
-                     (setq completions
-                            (split-string
-                             (buffer-substring-no-properties (point) 
(point-max))
-                             (rx (any "\r\n\t ")) 'omit)))
-
-                    ;; (tramp--test-message
-                    ;;  "syntax: %s style: %s test: %s result: %s completions: 
%S"
-                    ;;  syntax style test result completions)
-                    (should (member (caddr test-and-result) completions))))))))
+                               eos))
+                            result))
+                       (progn
+                          ;; (tramp--test-message
+                         ;;  (concat
+                          ;;   "syntax: %s style: %s predicate: %s "
+                         ;;   "test: %s result: %s")
+                          ;;  syntax style predicate test result)
+                         (should
+                          (string-prefix-p (cadr test-and-result) result)))
+
+                      (with-current-buffer "*Completions*"
+                       ;; We must remove leading `default-directory'.
+                       (goto-char (point-min))
+                       (let ((inhibit-read-only t))
+                         (while (search-forward-regexp "//" nil 'noerror)
+                           (delete-region (line-beginning-position) (point))))
+                       (goto-char (point-min))
+                       (search-forward-regexp
+                        (rx bol (0+ nonl)
+                            (any "Pp") "ossible completions"
+                            (0+ nonl) eol))
+                       (forward-line 1)
+                       (setq completions
+                              (split-string
+                               (buffer-substring-no-properties
+                               (point) (point-max))
+                               (rx (any "\r\n\t ")) 'omit)))
+
+                      ;; (tramp--test-message
+                     ;;  (concat
+                      ;;   "syntax: %s style: %s predicate: %s test: %s "
+                     ;;   "result: %s completions: %S")
+                      ;;  syntax style predicate test result completions)
+                      (should
+                      (member (caddr test-and-result) completions)))))))))
 
       ;; Cleanup.
-      ;; (tramp--test-message "%s" (tramp-get-buffer-string trace-buffer))
-      ;; (untrace-function #'tramp-completion-file-name-handler)
-      ;; (untrace-function #'completion-file-name-table)
+      ;; (untrace-all)
       (tramp-change-syntax orig-syntax)
       (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password))))
 
@@ -8469,13 +8538,127 @@ process sentinels.  They shall not disturb each other."
                    (cl-letf (((symbol-function #'ask-user-about-lock) 
#'always))
                      (save-buffer)))
                  (should-not
-                  (string-match-p "File is missing:" captured-messages))))))
+                  (string-match-p "File is missing:" captured-messages)))))
+
+           ;; A modified buffer suppresses session timeout.
+           (with-temp-buffer
+             (set-visited-file-name tmp-name)
+             (insert "foo")
+             (should (buffer-modified-p))
+             (tramp-timeout-session tramp-test-vec)
+             (should
+              (process-live-p (tramp-get-connection-process tramp-test-vec)))
+             ;; Steal the file lock.
+             (cl-letf (((symbol-function #'ask-user-about-lock) #'always))
+               (save-buffer))
+             (tramp-timeout-session tramp-test-vec)
+             (should-not
+              (process-live-p (tramp-get-connection-process tramp-test-vec))))
+
+           ;; An auto-reverted buffer suppresses session timeout.
+           (with-temp-buffer
+             (set-visited-file-name tmp-name)
+             (auto-revert-mode 1)
+             ;; Steal the file lock.
+             (cl-letf (((symbol-function #'ask-user-about-lock) #'always))
+               (save-buffer))
+             (tramp-timeout-session tramp-test-vec)
+             (should
+              (process-live-p (tramp-get-connection-process tramp-test-vec)))
+             (auto-revert-mode -1)
+             (tramp-timeout-session tramp-test-vec)
+             (should-not
+              (process-live-p (tramp-get-connection-process tramp-test-vec)))))
 
        ;; Cleanup.
        (ignore-errors (delete-file tmp-name))))))
 
+(defun tramp--test-operation (&optional _file)
+  "Test operation."
+  "Test operation")
+
+(defun tramp--handler-for-test-operation (&optional _file)
+  "Test operation handler."
+  "Test operation handler")
+
+(ert-deftest tramp-test49-external-backend-function ()
+  "Check that Tramp handles external functions for a given backend."
+  (skip-unless (tramp--test-enabled))
+  (skip-unless (not (tramp--test-ange-ftp-p)))
+
+  (let* ((file-name-handler
+         (tramp-find-foreign-file-name-handler tramp-test-vec))
+        (backend
+         (intern
+          (string-remove-suffix
+           "-file-name-handler" (symbol-name file-name-handler)))))
+
+    ;; There is no backend specific code.
+    (should-not
+     (string-equal (tramp--test-operation ert-remote-temporary-file-directory)
+                  (tramp--handler-for-test-operation
+                   ert-remote-temporary-file-directory)))
+    (should-not
+     (string-equal (tramp--test-operation temporary-file-directory)
+                  (tramp--handler-for-test-operation
+                   temporary-file-directory)))
+    (let ((default-directory ert-remote-temporary-file-directory))
+      (should-not
+       (string-equal (tramp--test-operation)
+                    (tramp--handler-for-test-operation))))
+    (let ((default-directory temporary-file-directory))
+      (should-not
+       (string-equal (tramp--test-operation)
+                    (tramp--handler-for-test-operation))))
+
+    (should-error
+     (tramp-add-external-operation
+      #'tramp--test-operation
+      #'tramp--handler-for-test-operation 'foo)
+     :type 'file-missing)
+    (tramp-add-external-operation
+     #'tramp--test-operation
+     #'tramp--handler-for-test-operation backend)
+    ;; The backend specific function is called.
+    (should
+     (string-equal (tramp--test-operation ert-remote-temporary-file-directory)
+                  (tramp--handler-for-test-operation
+                   ert-remote-temporary-file-directory)))
+    (should-not
+     (string-equal (tramp--test-operation temporary-file-directory)
+                  (tramp--handler-for-test-operation
+                   temporary-file-directory)))
+    (let ((default-directory ert-remote-temporary-file-directory))
+      (should
+       (string-equal (tramp--test-operation)
+                    (tramp--handler-for-test-operation))))
+    (let ((default-directory temporary-file-directory))
+      (should-not
+       (string-equal (tramp--test-operation)
+                    (tramp--handler-for-test-operation))))
+
+    (tramp-remove-external-operation
+     #'tramp--test-operation backend)
+    ;; There is no backend specific code.
+    (should-not
+     (string-equal (tramp--test-operation ert-remote-temporary-file-directory)
+                  (tramp--handler-for-test-operation
+                   ert-remote-temporary-file-directory)))
+    (should-not
+     (string-equal (tramp--test-operation temporary-file-directory)
+                  (tramp--handler-for-test-operation
+                   temporary-file-directory)))
+    (let ((default-directory ert-remote-temporary-file-directory))
+      (should-not
+       (string-equal (tramp--test-operation)
+                    (tramp--handler-for-test-operation))))
+    (let ((default-directory temporary-file-directory))
+      (should-not
+       (string-equal (tramp--test-operation)
+                    (tramp--handler-for-test-operation))))))
+
 ;; This test is inspired by Bug#29163.
-(ert-deftest tramp-test49-auto-load ()
+(ert-deftest tramp-test50-auto-load ()
   "Check that Tramp autoloads properly."
   ;; If we use another syntax but `default', Tramp is already loaded
   ;; due to the `tramp-change-syntax' call.
@@ -8500,7 +8683,7 @@ process sentinels.  They shall not disturb each other."
        (mapconcat #'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test49-delay-load ()
+(ert-deftest tramp-test50-delay-load ()
   "Check that Tramp is loaded lazily, only when needed."
   ;; Tramp is neither loaded at Emacs startup, nor when completing a
   ;; non-Tramp file name like "/foo".  Completing a Tramp-alike file
@@ -8530,7 +8713,7 @@ process sentinels.  They shall not disturb each other."
          (mapconcat #'shell-quote-argument load-path " -L ")
          (shell-quote-argument (format code tm)))))))))
 
-(ert-deftest tramp-test49-recursive-load ()
+(ert-deftest tramp-test50-recursive-load ()
   "Check that Tramp does not fail due to recursive load."
   (skip-unless (tramp--test-enabled))
 
@@ -8554,7 +8737,7 @@ process sentinels.  They shall not disturb each other."
          (mapconcat #'shell-quote-argument load-path " -L ")
          (shell-quote-argument code))))))))
 
-(ert-deftest tramp-test49-remote-load-path ()
+(ert-deftest tramp-test50-remote-load-path ()
   "Check that Tramp autoloads its packages with remote `load-path'."
   ;; `tramp-cleanup-all-connections' is autoloaded from tramp-cmds.el.
   ;; It shall still work, when a remote file name is in the
@@ -8579,7 +8762,7 @@ process sentinels.  They shall not disturb each other."
        (mapconcat #'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test50-without-remote-files ()
+(ert-deftest tramp-test51-without-remote-files ()
   "Check that Tramp can be suppressed."
   (skip-unless (tramp--test-enabled))
 
@@ -8594,7 +8777,7 @@ process sentinels.  They shall not disturb each other."
   (setq tramp-mode t)
   (should (file-remote-p ert-remote-temporary-file-directory)))
 
-(ert-deftest tramp-test51-unload ()
+(ert-deftest tramp-test52-unload ()
   "Check that Tramp and its subpackages unload completely.
 Since it unloads Tramp, it shall be the last test to run."
   :tags '(:expensive-test)
@@ -8699,6 +8882,12 @@ If INTERACTIVE is non-nil, the tests are run 
interactively."
 ;; * Use `skip-when' starting with Emacs 30.1.
 ;; * Revisit expensive tests, once problems in `tramp-error' are solved.
 ;; * Fix `tramp-test06-directory-file-name' for "ftp".
+;; * In `tramp-test26-file-name-completion', check also user, domain,
+;;   port and hop.
+;; * In `tramp-test26-interactive-file-name-completion', check `flex',
+;;   `initials' and `shorthand' completion styles.  Should
+;;   `minibuffer-completing-file-name' and `completion-pcm--delim-wild-regex'
+;;   be bound?  Check also domain, port and hop.
 ;; * Check, why a process filter t doesn't work in
 ;;   `tramp-test29-start-file-process' and
 ;;   `tramp-test30-make-process'.
diff --git a/texi/tramp.texi b/texi/tramp.texi
index 4170a35ad4..23e2311659 100644
--- a/texi/tramp.texi
+++ b/texi/tramp.texi
@@ -169,6 +169,7 @@ How file names, directories and localnames are mangled and 
managed
 * Localname deconstruction::    Breaking a localname into its components.
 * External packages::           Integration with external Lisp packages.
 * Extension packages::          Adding new methods to @value{tramp}.
+* New operations::              Handling further operations in @value{tramp}.
 
 @end detailmenu
 @end menu
@@ -2375,9 +2376,11 @@ value is @t{"-l"}, but some shells, like @command{ksh}, 
prefer
 All @file{tramp-sh.el} based methods accept the property
 @t{"session-timeout"}.  This is the time (in seconds) after a
 connection is disabled for security reasons, and must be
-reestablished.  A value of @code{nil} disables this feature.  Most of
-the methods do not set this property except the @option{sudo},
-@option{doas} and @option{run0} methods, which use predefined values.
+reestablished@footnote{If there is a modified buffer, or a buffer
+under @code{auto-revert}, this is suppressed.}.  A value of @code{nil}
+disables this feature.  Most of the methods do not set this property
+except the @option{sudo}, @option{doas} and @option{run0} methods,
+which use predefined values.
 
 @item @t{"~"}@*
 @t{"~user"}
@@ -3658,6 +3661,8 @@ behavior: @file{@trampfn{method,user@@host,path/to/file}}.
 
 For specifying port numbers, affix @file{#<port>} to the host
 name.  For example: @file{@trampfn{ssh,daniel@@melancholia#42,.emacs}}.
+If the host is an IPv6 address, the port is appended like this:
+@file{@trampfn{ssh,@value{ipv6prefix}::1@value{ipv6postfix}#42,.emacs}}.
 
 All method, user name, host name, port number and local name parts are
 optional, @xref{Default Method}, @xref{Default User}, @xref{Default Host}.
@@ -5472,6 +5477,13 @@ Suppress reading the remote history file in 
@code{shell}.  Set
 Disable excessive traces.  Set @code{tramp-verbose} to 3 or lower,
 default being 3.  Increase trace levels temporarily when hunting for
 bugs.
+
+@c @item
+@c Use a package with @value{tramp} specific implementation of high-level
+@c operations.  For example, the GNU ELPA package @file{tramp-hlo}
+@c implements specialized versions of @code{dir-locals--all-files},
+@c @code{locate-dominating-file} and @code{dir-locals-find-file} for
+@c @value{tramp}'s @code{tramp-sh} backend (@pxref{New operations}).
 @end itemize
 
 
@@ -6457,6 +6469,7 @@ programs.
 * Localname deconstruction::    Splitting a localname into its component parts.
 * External packages::           Integrating with external Lisp packages.
 * Extension packages::          Adding new methods to @value{tramp}.
+* New operations::              Handling further operations in @value{tramp}.
 @end menu
 
 
@@ -6721,15 +6734,112 @@ The trick is to wrap the function definition of
 @code{;;;###autoload} cookie.
 
 
+@node New operations
+@section Handling further operations in @value{tramp}
+
+By default, @value{tramp} handles the basic operations listed in
+@ref{Magic File Names, , Magic File Name Operations, elisp}.
+Sometimes, it is desired to support more complex operations directly,
+mainly for performance reasons.
+
+An external package package could add an own implementation of an
+operation to @value{tramp}, which avoids the performance overhead
+caused by using the basic operations which are aware of remote files.
+For example, it could implement this by using an own shell script
+which collects the information on the remote host for this very
+special purpose with one round-trip per-call.
+
+@defun tramp-add-external-operation operation function backend
+This adds an implementation of @var{operation} to @value{tramp}'s
+backend @var{backend}.  @var{function} is the new implementation.
+
+Both @var{operation} and @var{function} shall be function symbols.
+They must have the same argument list.  The first argument is used to
+determine, whether @value{tramp} is invoked (check for remote file
+name syntax).  It must be a string or nil, in the latter case
+@code{default-directory} is used for the check.
+
+@var{backend}, also a symbol, is the feature name of a @value{tramp}
+backend (except @code{tramp-ftp}).  The new implementation will be
+applied only for this backend.  Example:
+
+@lisp
+@group
+(defun test-operation (file)
+  (message "Original implementation for %s" file))
+@end group
+
+@group
+(defun handle-test-operation (file)
+  (message "Handler implementation for %s" file))
+@end group
+
+@group
+(tramp-add-external-operation
+ #'test-operation #'handle-test-operation 'tramp-sh)
+@end group
+@end lisp
+
+Then we have the different use cases:
+
+@lisp
+@group
+;; Local file name.
+(test-operation "/a/b")
+@result{} "Original implementation for /a/b"
+@end group
+
+@group
+;; Remote file name, handled by `tramp-sh'.
+(test-operation "/ssh::/a/b")
+@result{} "Handler implementation for /ssh::/a/b"
+@end group
+
+@group
+;; Remote file name, handled by `tramp-gvfs'.
+(test-operation "/sftp::/a/b")
+@result{} "Original implementation for /sftp::/a/b"
+@end group
+@end lisp
+
+@var{function} is implemented like an ordinary @value{tramp} backend
+handler, see the examples in @code{tramp-<backend>-handle-*} and
+@code{tramp-handle-*}.  It can expect, that the first argument (or
+@code{default-directory}, if that is @code{nil}) has remote file name
+syntax.  It shall use @value{tramp} internal macros and functions like
+@code{with-parsed-tramp-file-name} and the different cache functions.
+
+If the same @var{function} shall be used for different @value{tramp}
+backends, @code{tramp-add-external-operation} must be called for every
+backend, respectively.
+@end defun
+
+@defun tramp-remove-external-operation operation backend
+The handler for @var{operation}, added by
+@code{tramp-add-external-operation}, is removed from @var{backend}.
+If there are handlers of @var{operation} for other @var{backend}s,
+they are kept.  Example:
+
+@lisp
+@group
+(tramp-remove-external-operation
+ #'test-operation 'tramp-sh)
+@end group
+@end lisp
+@end defun
+
+
 @node Traces and Profiles
 @chapter How to Customize Traces
 @vindex tramp-verbose
 @vindex tramp-debug-to-file
 @vindex tramp-debug-command-messages
+@vindex tramp-debug-buffer-limit
 
 @value{tramp} messages are raised with verbosity levels ranging from 0
 to 10.  @value{tramp} does not display all messages; only those with a
-verbosity level less than or equal to @code{tramp-verbose}.
+verbosity level less than or equal to 3, when @code{tramp-verbose}
+permits.
 
 @noindent
 The verbosity levels are
@@ -6814,6 +6924,12 @@ strings), and the entry and exit messages for the 
function
 @code{tramp-file-name-handler}.  This is intended for @value{tramp}
 maintainers, analyzing the remote commands for performance analysis.
 
+The debug buffer can be very large, if @code{tramp-verbose} is high,
+and @value{tramp} runs for a long time.  If the buffer size exceeds
+@code{tramp-debug-buffer-limit} (100MB by default), a warning will be
+raised.  This user option can be adapted to your needs; a value of 0
+means that there is no limit (no warning).
+
 
 @node GNU Free Documentation License
 @appendix GNU Free Documentation License
@@ -6841,7 +6957,7 @@ maintainers, analyzing the remote commands for 
performance analysis.
 
 @bye
 
-@c TODO
+@c TODO:
 @c
 @c * Say something about the .login and .profile files of the remote
 @c   shells.
@@ -6850,3 +6966,5 @@ maintainers, analyzing the remote commands for 
performance analysis.
 @c   host and then send commands to it.
 @c
 @c * Consistent small or capitalized words especially in menus.
+@c
+@c * Starting with Emacs 29, use 'setopt' in the examples.
diff --git a/texi/trampver.texi b/texi/trampver.texi
index 5f2b2fad36..fedbca9a5b 100644
--- a/texi/trampver.texi
+++ b/texi/trampver.texi
@@ -7,7 +7,7 @@
 
 @c In the  Tramp GIT, the version number and the bug report address
 @c are auto-frobbed from configure.ac.
-@set trampver 2.8.0.1
+@set trampver 2.8.0.2
 @set trampurl https://www.gnu.org/software/tramp/
 @set tramp-bug-report-address tramp-devel@@gnu.org
 @set emacsver 28.1
diff --git a/tramp-adb.el b/tramp-adb.el
index 2d240e8756..5d6c053868 100644
--- a/tramp-adb.el
+++ b/tramp-adb.el
@@ -573,9 +573,9 @@ Emacs dired can't find files."
                  "touch -t %s %s %s")
          (format-time-string "%Y-%m-%dT%H:%M:%S.%NZ" time t)
          nofollow quoted-name (tramp-get-remote-null-device v)
-         (format-time-string "%Y-%m-%dT%H:%M:%S" time t)
+         (format-time-string "%Y-%m-%dT%H:%M:%S" time)
          nofollow quoted-name (tramp-get-remote-null-device v)
-         (format-time-string "%Y%m%d%H%M.%S" time t)
+         (format-time-string "%Y%m%d%H%M.%S" time)
          nofollow quoted-name)))))
 
 (defun tramp-adb-handle-copy-file
diff --git a/tramp-cache.el b/tramp-cache.el
index 721b7be123..4ecc804bf2 100644
--- a/tramp-cache.el
+++ b/tramp-cache.el
@@ -647,17 +647,18 @@ your laptop to different networks frequently."
   "Return a list of (user host) tuples allowed to access for METHOD.
 This function is added always in `tramp-get-completion-function'
 for all methods.  Resulting data are derived from connection history."
-  (mapcar
-   (lambda (key)
-     (let ((tramp-verbose 0))
-       (and (tramp-file-name-p key)
-           (string-equal method (tramp-file-name-method key))
-           (not (tramp-file-name-localname key))
-           (tramp-get-method-parameter
-            key 'tramp-completion-use-cache tramp-completion-use-cache)
-           (list (tramp-file-name-user key)
-                 (tramp-file-name-host key)))))
-   (hash-table-keys tramp-cache-data)))
+  (delete-dups
+   (tramp-compat-seq-keep
+    (lambda (key)
+      (let ((tramp-verbose 0))
+       (and (tramp-file-name-p key)
+            (string-equal method (tramp-file-name-method key))
+            (not (tramp-file-name-localname key))
+            (tramp-get-method-parameter
+             key 'tramp-completion-use-cache tramp-completion-use-cache)
+            (list (tramp-file-name-user key)
+                  (tramp-file-name-host key)))))
+    (hash-table-keys tramp-cache-data))))
 
 ;; When "emacs -Q" has been called, both variables are nil.  We do not
 ;; load the persistency file then, in order to have a clean test environment.
diff --git a/tramp-compat.el b/tramp-compat.el
index 9787e3a655..feda8943be 100644
--- a/tramp-compat.el
+++ b/tramp-compat.el
@@ -29,7 +29,7 @@
 
 ;;; Code:
 
-(require 'tramp-loaddefs)
+(require 'tramp-loaddefs nil t)  ; guard against load during autoload gen
 (require 'ansi-color)
 (require 'auth-source)
 (require 'format-spec)
@@ -251,7 +251,7 @@ value is the default binding of the variable."
 ;;
 ;; * Use `ensure-list'.
 ;;
-;; * Starting with Emacs 29.1, use `buffer-match-p'.
+;; * Starting with Emacs 29.1, use `buffer-match-p' and `match-buffers'.
 ;;
 ;; * Starting with Emacs 29.1, use `string-split'.
 ;;
diff --git a/tramp-gvfs.el b/tramp-gvfs.el
index b5f1135a60..7f3ac945bb 100644
--- a/tramp-gvfs.el
+++ b/tramp-gvfs.el
@@ -2557,7 +2557,7 @@ This uses \"avahi-browse\" in case D-Bus is not enabled 
in Avahi."
            (shell-command-to-string (format "avahi-browse -trkp %s" service))
            (rx (+ (any "\r\n"))) 'omit (rx bol "+;" (* nonl) eol)))))
     (delete-dups
-     (mapcar
+     (tramp-compat-seq-keep
       (lambda (x)
        (ignore-errors
          (let* ((list (split-string x ";"))
diff --git a/tramp-message.el b/tramp-message.el
index 5131e5fced..a328183e18 100644
--- a/tramp-message.el
+++ b/tramp-message.el
@@ -94,6 +94,15 @@ This increases `tramp-verbose' to 6 if necessary."
   :type 'boolean
   :link '(info-link :tag "Tramp manual" "(tramp) Traces and Profiles"))
 
+(defcustom tramp-debug-buffer-limit (* 100 1024 1024) ;100MB
+  "The upper limit of a Tramp debug buffer.
+If the size of a debug buffer exceeds this limit, a warning is raised.
+Set it to 0 if there is no limit."
+  :group 'tramp
+  :version "31.1"
+  :type 'natnum
+  :link '(info-link :tag "Tramp manual" "(tramp) Traces and Profiles"))
+
 (defconst tramp-debug-outline-regexp
   (rx ;; Timestamp.
       (+ digit) ":" (+ digit) ":" (+ digit) "." (+ digit) blank
@@ -281,7 +290,14 @@ ARGUMENTS to actually emit the message (if applicable)."
          (when tramp-debug-to-file
            (ignore-errors
              (write-region
-              point (point-max) (tramp-get-debug-file-name vec) 'append))))))))
+              point (point-max) (tramp-get-debug-file-name vec) 'append))))
+       (when (and (natnump tramp-debug-buffer-limit)
+                  (not (zerop tramp-debug-buffer-limit))
+                  (> (point-max) tramp-debug-buffer-limit))
+         (setq-local tramp-debug-buffer-limit nil)
+         (lwarn
+          'tramp :warning
+          "Tramp debug buffer %S exceeds the limit" (current-buffer)))))))
 
 ;;;###tramp-autoload
 (defun tramp-message (vec-or-proc level fmt-string &rest arguments)
diff --git a/tramp-sh.el b/tramp-sh.el
index 576f09b764..9d13cdc3a2 100644
--- a/tramp-sh.el
+++ b/tramp-sh.el
@@ -1633,7 +1633,10 @@ of."
               "-t %s"
               (format-time-string "%Y%m%d%H%M.%S" (tramp-defined-time time) t))
            "")
-         (if (eq flag 'nofollow) "-h" "")
+         (if (and (eq flag 'nofollow)
+                  (tramp-get-connection-property v "touch-h"))
+             "-h"
+           "")
          (tramp-shell-quote-argument localname))))))
 
 (defun tramp-sh-handle-get-home-directory (vec &optional user)
@@ -3669,6 +3672,8 @@ are \"file-exists-p\", \"file-readable-p\", 
\"file-directory-p\" and
 (defun tramp-sh-handle-vc-registered (file)
   "Like `vc-registered' for Tramp files."
   (when vc-handled-backends
+    ;; Starting with Emacs 31, use `revert-buffer-in-progress'.
+    (with-suppressed-warnings ((obsolete revert-buffer-in-progress-p))
     (let ((inhibit-message (or revert-buffer-in-progress-p inhibit-message))
          (temp-message (unless revert-buffer-in-progress-p "")))
       (with-temp-message temp-message
@@ -3728,7 +3733,7 @@ are \"file-exists-p\", \"file-readable-p\", 
\"file-directory-p\" and
              ;; Run.
              (tramp-with-demoted-errors
                  v "Error in 2nd pass of `vc-registered': %s"
-               (tramp-run-real-handler #'vc-registered (list file))))))))))
+               (tramp-run-real-handler #'vc-registered (list file)))))))))))
 
 ;;;###tramp-autoload
 (defun tramp-sh-file-name-handler (operation &rest args)
@@ -5149,17 +5154,41 @@ Goes through the list `tramp-inline-compress-commands'."
 ;;;###tramp-autoload
 (defun tramp-timeout-session (vec)
   "Close the connection VEC after a session timeout.
-If there is just some editing, retry it after 5 seconds."
-  (if (and (tramp-get-connection-property
-           (tramp-get-connection-process vec) "locked")
-          (tramp-file-name-equal-p vec (car tramp-current-connection)))
-      (progn
-       (tramp-message
-        vec 5 "Cannot timeout session, trying it again in %s seconds." 5)
-       (run-at-time 5 nil #'tramp-timeout-session vec))
+If there is just some editing, retry it after 5 seconds.
+If there is a modified buffer, retry it after 60 seconds."
+  (cond
+   ;; Tramp is locked.  Try it, again.
+   ((and (tramp-get-connection-property
+         (tramp-get-connection-process vec) "locked")
+        (tramp-file-name-equal-p vec (car tramp-current-connection)))
+    (tramp-message
+     vec 5 "Cannot timeout session, trying it again in %s seconds." 5)
+    (run-at-time 5 nil #'tramp-timeout-session vec))
+   ;; There's a modified buffer.  Try it, again.
+   ((seq-some
+     (lambda (buf)
+       (and-let* (((or (buffer-modified-p buf)
+                      (with-current-buffer buf
+                        ;; We don't know whether autorevert.el has
+                        ;; been loaded alreaddy.
+                        (tramp-compat-funcall 'auto-revert-active-p))))
+                 (bfn (buffer-file-name buf))
+                 (v (tramp-ensure-dissected-file-name bfn))
+                 ((tramp-file-name-equal-p vec v)))))
+     (tramp-list-remote-buffers))
     (tramp-message
-     vec 3 "Timeout session %s" (tramp-make-tramp-file-name vec 'noloc))
-    (tramp-cleanup-connection vec 'keep-debug nil 'keep-processes)))
+     vec 5
+     (concat
+      "Cannot timeout session (modified buffer), "
+      "trying it again in %s seconds.")
+     (tramp-get-method-parameter vec 'tramp-session-timeout))
+    (run-at-time
+     (tramp-get-method-parameter vec 'tramp-session-timeout) nil
+     #'tramp-timeout-session vec))
+   ;; Do it.
+   (t (tramp-message
+       vec 3 "Timeout session %s" (tramp-make-tramp-file-name vec 'noloc))
+      (tramp-cleanup-connection vec 'keep-debug nil 'keep-processes))))
 
 (defun tramp-maybe-open-connection (vec)
   "Maybe open a connection VEC.
@@ -5887,12 +5916,12 @@ Nonexistent directories are removed from spec."
   "Determine remote `touch' command."
   (with-tramp-connection-property vec "touch"
     (tramp-message vec 5 "Finding a suitable `touch' command")
-    (let ((result (tramp-find-executable
-                  vec "touch" (tramp-get-remote-path vec)))
-         (tmpfile (tramp-make-tramp-temp-name vec)))
-      ;; Busyboxes do support the "-t" option only when they have been
-      ;; built with the DESKTOP config option.  Let's check it.
-      (when result
+    (when-let* ((result (tramp-find-executable
+                        vec "touch" (tramp-get-remote-path vec)))
+               (tmpfile (tramp-make-tramp-temp-name vec)))
+      (prog1 result
+       ;; Busyboxes do support the "-t" option only when they have
+       ;; been built with the DESKTOP config option.  Let's check it.
        (tramp-set-connection-property
         vec "touch-t"
         (tramp-send-command-and-check
@@ -5902,8 +5931,13 @@ Nonexistent directories are removed from spec."
           result
           (format-time-string "%Y%m%d%H%M.%S")
           (tramp-file-local-name tmpfile))))
-       (delete-file tmpfile))
-      result)))
+       ;; The touch command included in busybox (version 1.30.1-6) on
+       ;; OpenWrt does not have the option "-h".
+       (tramp-set-connection-property
+        vec "touch-h"
+        (tramp-send-command-and-check
+         vec (format "%s -h %s" result (tramp-file-local-name tmpfile))))
+       (delete-file tmpfile)))))
 
 (defun tramp-get-remote-df (vec)
   "Determine remote `df' command."
diff --git a/tramp-smb.el b/tramp-smb.el
index 28364de83f..fdda945f12 100644
--- a/tramp-smb.el
+++ b/tramp-smb.el
@@ -295,7 +295,7 @@ See `tramp-actions-before-shell' for more info.")
     (set-file-acl . tramp-smb-handle-set-file-acl)
     (set-file-modes . tramp-smb-handle-set-file-modes)
     (set-file-selinux-context . ignore)
-    (set-file-times . ignore)
+    (set-file-times . tramp-smb-handle-set-file-times)
     (set-visited-file-modtime . tramp-handle-set-visited-file-modtime)
     (shell-command . tramp-handle-shell-command)
     (start-file-process . tramp-smb-handle-start-file-process)
@@ -1534,6 +1534,15 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are 
completely ignored."
          (tramp-error
           v 'file-error "Error while changing file's mode %s" filename))))))
 
+(defun tramp-smb-handle-set-file-times (filename &optional time _flag)
+  "Like `set-file-times' for Tramp files."
+  (tramp-skeleton-set-file-modes-times-uid-gid filename
+    (tramp-smb-send-command
+     v (format
+        "utimes %s -1 -1 %s -1"
+        (tramp-smb-shell-quote-localname v)
+        (format-time-string "%Y:%m:%d-%H:%M:%S" (tramp-defined-time time))))))
+
 ;; We use BUFFER also as connection buffer during setup.  Because of
 ;; this, its original contents must be saved, and restored once
 ;; connection has been setup.
diff --git a/tramp.el b/tramp.el
index b58acea229..b63c5d97a0 100644
--- a/tramp.el
+++ b/tramp.el
@@ -7,7 +7,7 @@
 ;; Maintainer: Michael Albinus <michael.albi...@gmx.de>
 ;; Keywords: comm, processes
 ;; Package: tramp
-;; Version: 2.8.0.1
+;; Version: 2.8.0.2
 ;; Package-Requires: ((emacs "28.1"))
 ;; Package-Type: multi
 ;; URL: https://www.gnu.org/software/tramp/
@@ -103,8 +103,15 @@
 
   (put 'tramp--startup-hook 'tramp-suppress-trace t)
 
+  ;; TODO: Once (autoload-macro expand) is available in all supported
+  ;; Emacs versions (Emacs 31.1+), this can be eliminated:
+  ;; Backward compatibility for autoload-macro declare form.
+  (unless (assq 'autoload-macro macro-declarations-alist)
+    (push '(autoload-macro ignore) macro-declarations-alist))
+
   (defmacro tramp--with-startup (&rest body)
     "Schedule BODY to be executed at the end of tramp.el."
+    (declare (autoload-macro expand))
     `(add-hook 'tramp--startup-hook (lambda () ,@body)))
 
   (eval-and-compile
@@ -1040,7 +1047,7 @@ Used in `tramp-make-tramp-file-name'.")
   "Regexp matching delimiter between method and user or host names.
 Derived from `tramp-postfix-method-format'.")
 
-(defconst tramp-user-regexp (rx (+ (not (any "/:|" blank))))
+(defconst tramp-user-regexp (rx (+ (not (any "/:|[]" blank))))
   "Regexp matching user names.")
 
 (defconst tramp-prefix-domain-format "%"
@@ -1994,10 +2001,21 @@ necessary only.  This function will be used in file 
name completion."
            (concat user tramp-postfix-user-format))
          (unless (tramp-string-empty-or-nil-p host)
            (concat
-            (if (string-match-p tramp-ipv6-regexp host)
-                (concat
-                 tramp-prefix-ipv6-format host tramp-postfix-ipv6-format)
-              host)
+            (cond
+             (;; ipv6#port -> [ipv6]#port
+              (string-match
+               (rx (group (regexp tramp-ipv6-regexp))
+                   (group (regexp tramp-prefix-port-regexp)
+                          (regexp tramp-port-regexp)))
+               host)
+              (concat
+               tramp-prefix-ipv6-format (match-string 1 host)
+               tramp-postfix-ipv6-format (match-string 2 host)))
+             (;; ipv6 -> [ipv6]
+              (string-match-p tramp-ipv6-regexp host)
+              (concat
+               tramp-prefix-ipv6-format host tramp-postfix-ipv6-format))
+             (t host))
             tramp-postfix-host-format))
          localname))
 
@@ -2380,6 +2398,9 @@ arguments to pass to the OPERATION."
         signal-hook-function)
     (apply operation args)))
 
+(defvar tramp-file-name-for-operation-external nil
+  "List of operations added by external packages.")
+
 ;; We handle here all file primitives.  Most of them have the file
 ;; name as first parameter; nevertheless we check for them explicitly
 ;; in order to be signaled if a new primitive appears.  This
@@ -2387,6 +2408,10 @@ arguments to pass to the OPERATION."
 ;; syntactical means whether a foreign method must be called.  It would
 ;; ease the life if `file-name-handler-alist' would support a decision
 ;; function as well but regexp only.
+;; Operations added by external packages are kept in
+;; `tramp-file-name-for-operation-external'.  They expect the file
+;; name to be checked as first argument or, if there isn't any
+;; argument, `default-directory'.
 (defun tramp-file-name-for-operation (operation &rest args)
   "Return file name related to OPERATION file primitive.
 ARGS are the arguments OPERATION has been called with.
@@ -2396,40 +2421,40 @@ first argument of `expand-file-name' is absolute and 
not remote.
 Must be handled by the callers."
   (cond
    ;; FILE resp DIRECTORY.
-   ((member operation
-           '(access-file byte-compiler-base-file-name delete-directory
-             delete-file diff-latest-backup-file directory-file-name
-             directory-files directory-files-and-attributes dired-compress-file
-             dired-uncache file-acl file-accessible-directory-p file-attributes
-             file-directory-p file-executable-p file-exists-p file-local-copy
-             file-locked-p file-modes file-name-as-directory
-             file-name-case-insensitive-p file-name-directory
-             file-name-nondirectory file-name-sans-versions
-             file-notify-add-watch file-ownership-preserved-p file-readable-p
-             file-regular-p file-remote-p file-selinux-context file-symlink-p
-             file-system-info file-truename file-writable-p
-             find-backup-file-name get-file-buffer
-             insert-directory insert-file-contents load lock-file 
make-directory
-             make-lock-file-name set-file-acl set-file-modes
-             set-file-selinux-context set-file-times substitute-in-file-name
-             unhandled-file-name-directory unlock-file vc-registered
-             ;; Emacs 28- only.
-             make-directory-internal
-             ;; Emacs 29+ only.
-             abbreviate-file-name
-             ;; Tramp internal magic file name function.
-             tramp-set-file-uid-gid))
+   ((memq operation
+         '(access-file byte-compiler-base-file-name delete-directory
+           delete-file diff-latest-backup-file directory-file-name
+           directory-files directory-files-and-attributes dired-compress-file
+           dired-uncache file-acl file-accessible-directory-p file-attributes
+           file-directory-p file-executable-p file-exists-p file-local-copy
+           file-locked-p file-modes file-name-as-directory
+           file-name-case-insensitive-p file-name-directory
+           file-name-nondirectory file-name-sans-versions
+           file-notify-add-watch file-ownership-preserved-p file-readable-p
+           file-regular-p file-remote-p file-selinux-context file-symlink-p
+           file-system-info file-truename file-writable-p
+           find-backup-file-name get-file-buffer
+           insert-directory insert-file-contents load lock-file make-directory
+           make-lock-file-name set-file-acl set-file-modes
+           set-file-selinux-context set-file-times substitute-in-file-name
+           unhandled-file-name-directory unlock-file vc-registered
+           ;; Emacs 28- only.
+           make-directory-internal
+           ;; Emacs 29+ only.
+           abbreviate-file-name
+           ;; Tramp internal magic file name function.
+           tramp-set-file-uid-gid))
     (if (file-name-absolute-p (nth 0 args))
        (nth 0 args)
       default-directory))
    ;; STRING FILE.
    ((eq operation 'make-symbolic-link) (nth 1 args))
    ;; FILE DIRECTORY resp FILE1 FILE2.
-   ((member operation
-           '(add-name-to-file copy-directory copy-file
-             file-equal-p file-in-directory-p
-             file-name-all-completions file-name-completion
-             file-newer-than-file-p rename-file))
+   ((memq operation
+         '(add-name-to-file copy-directory copy-file
+           file-equal-p file-in-directory-p
+           file-name-all-completions file-name-completion
+           file-newer-than-file-p rename-file))
     (cond
      ((tramp-tramp-file-p (nth 0 args)) (nth 0 args))
      ((file-name-absolute-p (nth 1 args)) (nth 1 args))
@@ -2446,31 +2471,39 @@ Must be handled by the callers."
        (nth 2 args)
       default-directory))
    ;; BUFFER.
-   ((member operation
-           '(make-auto-save-file-name
-             set-visited-file-modtime verify-visited-file-modtime))
+   ((memq operation
+         '(make-auto-save-file-name
+           set-visited-file-modtime verify-visited-file-modtime))
     (buffer-file-name
      (if (bufferp (nth 0 args)) (nth 0 args) (current-buffer))))
    ;; COMMAND.
-   ((member operation
-           '(exec-path make-nearby-temp-file make-process process-file
-             shell-command start-file-process temporary-file-directory
-             ;; Emacs 29+ only.
-              list-system-processes memory-info process-attributes
-              ;; Emacs 30+ only.
-             file-group-gid file-user-uid))
+   ((memq operation
+         '(exec-path make-nearby-temp-file make-process process-file
+           shell-command start-file-process temporary-file-directory
+           ;; Emacs 29+ only.
+            list-system-processes memory-info process-attributes
+            ;; Emacs 30+ only.
+           file-group-gid file-user-uid))
     default-directory)
    ;; PROC.
-   ((member operation '(file-notify-rm-watch file-notify-valid-p))
+   ((memq operation '(file-notify-rm-watch file-notify-valid-p))
     (when (processp (nth 0 args))
       (tramp-get-default-directory (process-buffer (nth 0 args)))))
    ;; VEC.
-   ((member operation
-           '(tramp-get-home-directory tramp-get-remote-gid
-             tramp-get-remote-groups tramp-get-remote-uid))
+   ((memq operation
+         '(tramp-get-home-directory tramp-get-remote-gid
+           tramp-get-remote-groups tramp-get-remote-uid))
     (tramp-make-tramp-file-name (nth 0 args)))
+   ;; FILE resp DIRECTORY.
+   ((and (memq operation tramp-file-name-for-operation-external)
+        (or (stringp (nth 0 args)) (null (nth 0 args))))
+    (if (and (stringp (nth 0 args)) (file-name-absolute-p (nth 0 args)))
+       (nth 0 args)
+      default-directory))
    ;; Unknown file primitive.
-   (t (error "Unknown file I/O primitive: %s" operation))))
+   (t (unless (member 'remote-file-error debug-ignored-errors)
+       (tramp-error
+        nil 'remote-file-error "Unknown file I/O primitive: %s" operation)))))
 
 (defun tramp-find-foreign-file-name-handler (vec &optional _operation)
   "Return foreign file name handler if exists."
@@ -2494,6 +2527,63 @@ Must be handled by the callers."
                res (cdr elt))))
       res)))
 
+(defun tramp-add-external-operation (operation function backend)
+  "Add FUNTION to Tramp BACKEND as handler for OPERATION.
+OPERATION must not be one of the magic operations listed in Info
+node `(elisp) Magic File Names'.  FUNCTION must have the same argument
+list as OPERATION.  BACKEND, a symbol, must be one of the Tramp backend
+packages like `tramp-sh' (except `tramp-ftp')."
+  (require backend)
+  (when-let* ((fnha
+              (intern-soft
+               (concat (symbol-name backend) "-file-name-handler-alist")))
+             ((boundp fnha)))
+    ;; Make BACKEND aware of the new operation.
+    (add-to-list fnha (cons operation function))
+    (unless (memq operation tramp-file-name-for-operation-external)
+      ;; Make Tramp aware of the new operation.
+      (add-to-list 'tramp-file-name-for-operation-external operation)
+      (put #'tramp-file-name-handler
+          'operations
+           (cons operation (get 'tramp-file-name-handler 'operations)))
+      ;; Add an advice for OPERATION, in order to invoke the handler FUNCTION.
+      (advice-add
+       operation :around
+       `(lambda (orig-fun &rest args)
+         (if-let* ((handler
+                    (find-file-name-handler
+                     (or (car args) default-directory) #',operation)))
+             (apply handler #',operation args)
+           (apply orig-fun args)))
+       `((name . ,(concat "tramp-advice-" (symbol-name operation))))))))
+
+(defun tramp-remove-external-operation (operation backend)
+  "Remove OPERATION from Tramp BACKEND as handler for OPERATION.
+OPERATION must not be one of the magic operations listed in Info
+node `(elisp) Magic File Names'. BACKEND, a symbol, must be one of the
+Tramp backend packages like `tramp-sh'."
+  ;; Remove OPERATION from BACKEND.
+  (when-let* ((fnha
+              (intern-soft
+               (concat (symbol-name backend) "-file-name-handler-alist")))
+             ((boundp fnha)))
+    (setf (alist-get operation (symbol-value fnha) nil 'remove) nil))
+  ;; Check, whether OPERATION is still used in any backend.
+  (unless (seq-some
+          (lambda (item)
+            (when-let*
+                ((fnha (intern-soft (concat (symbol-name (cdr item)) 
"-alist")))
+                 ((boundp fnha)))
+              (alist-get operation (symbol-value fnha))))
+          tramp-foreign-file-name-handler-alist)
+    ;; Make Tramp unaware of OPERATION.
+    (setq tramp-file-name-for-operation-external
+         (delq operation tramp-file-name-for-operation-external))
+    (put #'tramp-file-name-handler
+        'operations (delq operation (get 'tramp-file-name-handler 
'operations)))
+    ;; Remove the advice for OPERATION.
+    (advice-remove operation (concat "tramp-advice-" (symbol-name 
operation)))))
+
 ;; Main function.
 ;;;###autoload
 (defun tramp-file-name-handler (operation &rest args)
@@ -2831,19 +2921,23 @@ not in completion mode."
   ;; We need special handling only when a method is needed.  Then we
   ;; regard all files "/method:" or "/[method/" as existent, if
   ;; "method" is a valid Tramp method.
-  (or (string-equal filename "/")
-      (and ;; Is it a valid method?
-           (not (string-empty-p tramp-postfix-method-format))
-           (string-match
-           (rx
-            (regexp tramp-prefix-regexp)
-            (* (regexp tramp-remote-file-name-spec-regexp)
-               (regexp tramp-postfix-hop-regexp))
-            (group-n 9 (regexp tramp-method-regexp))
-            (? (regexp tramp-postfix-method-regexp))
-             eos)
-            filename)
-          (assoc (match-string 9 filename) tramp-methods)
+  (or (and (cond
+            ;; Completion styles like `flex' and `substring' check for
+            ;; the file name "/".  This does exist.
+            ((string-equal filename "/"))
+            ;; Is it a valid method?
+            ((and (not (string-empty-p tramp-postfix-method-format))
+                  (string-match
+                  (rx
+                   (regexp tramp-prefix-regexp)
+                   (* (regexp tramp-remote-file-name-spec-regexp)
+                      (regexp tramp-postfix-hop-regexp))
+                   (group-n 9 (regexp tramp-method-regexp))
+                   (| (regexp tramp-postfix-method-regexp) eos))
+                   filename))
+             (assoc (match-string 9 filename) tramp-methods))
+            ;; Is it a completion file name?
+            ((string-match-p tramp-completion-file-name-regexp filename)))
           t)
 
       (tramp-run-real-handler #'file-directory-p (list filename))))
@@ -2852,10 +2946,7 @@ not in completion mode."
   "Like `file-exists-p' for partial Tramp files."
   ;; We need special handling only when a method is needed.  Then we
   ;; regard all files "/method:" or "/[method/" as existent, if
-  ;; "method" is a valid Tramp method.  And we regard all files
-  ;; "/method:user@", "/user@" or "/[method/user@" as existent, if
-  ;; "user@" is a valid file name completion.  Host completion is
-  ;; performed in the respective backend operation.
+  ;; "method" is a valid Tramp method.
   (or (and (cond
             ;; Completion styles like `flex' and `substring' check for
             ;; the file name "/".  This does exist.
@@ -2868,28 +2959,11 @@ not in completion mode."
                    (* (regexp tramp-remote-file-name-spec-regexp)
                       (regexp tramp-postfix-hop-regexp))
                    (group-n 9 (regexp tramp-method-regexp))
-                   (? (regexp tramp-postfix-method-regexp))
-                    eos)
+                   (| (regexp tramp-postfix-method-regexp) eos))
                    filename))
              (assoc (match-string 9 filename) tramp-methods))
-            ;; Is it a valid user?
-            ((string-match
-             (rx
-               (regexp tramp-prefix-regexp)
-              (* (regexp tramp-remote-file-name-spec-regexp)
-                 (regexp tramp-postfix-hop-regexp))
-               (group-n 10
-                (regexp tramp-method-regexp)
-                (regexp tramp-postfix-method-regexp))
-              (group-n 11
-                (regexp tramp-user-regexp)
-                (regexp tramp-postfix-user-regexp))
-               eos)
-              filename)
-            (member
-             (match-string 11 filename)
-             (file-name-all-completions
-              "" (concat tramp-prefix-format (match-string 10 filename))))))
+            ;; Is it a completion file name?
+            ((string-match-p tramp-completion-file-name-regexp filename)))
           t)
 
       (tramp-run-real-handler #'file-exists-p (list filename))))
@@ -2900,7 +2974,7 @@ not in completion mode."
 BODY is the backend specific code."
   (declare (indent 2) (debug t))
   `(ignore-error file-missing
-     (delete-dups (delq nil
+     (delete-dups (delq nil (delete ""
        (let* ((case-fold-search read-file-name-completion-ignore-case)
              (result (progn ,@body)))
         ;; Some storage systems do not return "." and "..".
@@ -2917,7 +2991,7 @@ BODY is the backend specific code."
                    (dolist (elt completion-regexp-list x)
                      (unless (string-match-p elt x) (throw 'match nil))))))
              result)
-          result))))))
+          result)))))))
 
 (defvar tramp--last-hop-directory nil
   "Tracks the directory from which to run login programs.")
@@ -3004,15 +3078,14 @@ BODY is the backend specific code."
 
 ;; Method, host name and user name completion for a file.
 (defun tramp-completion-handle-file-name-completion
-  (filename directory &optional predicate)
-  "Like `file-name-completion' for partial Tramp files."
+  (filename directory &optional _predicate)
+  "Like `file-name-completion' for partial Tramp files.
+It ignores PREDICATE, because there's no meaningful result."
   ;; Suppress eager completion on not connected hosts.
   (let ((non-essential t))
     (try-completion
      filename
-     (mapcar #'list (file-name-all-completions filename directory))
-     (when (and predicate (tramp-connectable-p directory))
-       (lambda (x) (funcall predicate (expand-file-name (car x) 
directory)))))))
+     (mapcar #'list (file-name-all-completions filename directory)))))
 
 ;; I misuse a little bit the `tramp-file-name' structure in order to
 ;; handle completion possibilities for partial methods / user names /
@@ -3034,7 +3107,15 @@ BODY is the backend specific code."
 (defun tramp-completion-dissect-file-name (name)
   "Return a list of `tramp-file-name' structures for NAME.
 They are collected by `tramp-completion-dissect-file-name1'."
-  (let (;; "/method" "/[method"
+  ;; We don't need a special handling for "user%domain", because "%"
+  ;; is also hit by `tramp-user-regexp'.  "host#port" is normalized
+  ;; for IPv6 hosts.
+  (let ((internal-name
+        (replace-regexp-in-string
+         (rx (regexp tramp-postfix-ipv6-regexp)
+             (regexp tramp-prefix-port-regexp))
+         tramp-prefix-port-format name))
+       ;; "/method" "/[method"
        (tramp-completion-file-name-structure1
         (list
          (rx
@@ -3091,16 +3172,75 @@ They are collected by 
`tramp-completion-dissect-file-name1'."
           (regexp tramp-postfix-user-regexp)
           (regexp tramp-prefix-ipv6-regexp)
           (group (? (regexp tramp-ipv6-regexp))) eol)
+         1 2 3 nil))
+       ;; "/method:host#port" "/[method/host#port"
+       (tramp-completion-file-name-structure7
+        (list
+         (rx
+          (regexp tramp-prefix-regexp)
+          (group (regexp tramp-method-regexp))
+          (regexp tramp-postfix-method-regexp)
+          (group (regexp tramp-host-regexp)
+                 (regexp tramp-prefix-port-regexp)
+                 (? (regexp tramp-port-regexp)))
+          eol)
+         1 nil 2 nil))
+       ;; "/method:[ipv6]#port" "/[method/ipv6#port"
+       (tramp-completion-file-name-structure8
+        (list
+         (rx
+          (regexp tramp-prefix-regexp)
+          (group (regexp tramp-method-regexp))
+          (regexp tramp-postfix-method-regexp)
+          (regexp tramp-prefix-ipv6-regexp)
+          (group (regexp tramp-ipv6-regexp)
+                 (regexp tramp-prefix-port-regexp)
+                 (? (regexp tramp-port-regexp)))
+          eol)
+         1 nil 2 nil))
+       ;; "/method:user@host#port" "/[method/user@host#port"
+       (tramp-completion-file-name-structure9
+        (list
+         (rx
+          (regexp tramp-prefix-regexp)
+          (group (regexp tramp-method-regexp))
+          (regexp tramp-postfix-method-regexp)
+          (group (regexp tramp-user-regexp))
+          (regexp tramp-postfix-user-regexp)
+          (group (regexp tramp-host-regexp)
+                 (regexp tramp-prefix-port-regexp)
+                 (? (regexp tramp-port-regexp)))
+          eol)
+         1 2 3 nil))
+       ;; "/method:user@[ipv6]#port" "/[method/user@ipv6#port"
+       (tramp-completion-file-name-structure10
+        (list
+         (rx
+          (regexp tramp-prefix-regexp)
+          (group (regexp tramp-method-regexp))
+          (regexp tramp-postfix-method-regexp)
+          (group (regexp tramp-user-regexp))
+          (regexp tramp-postfix-user-regexp)
+          (regexp tramp-prefix-ipv6-regexp)
+          (group (regexp tramp-ipv6-regexp)
+                 (regexp tramp-prefix-port-regexp)
+                 (? (regexp tramp-port-regexp)))
+          eol)
          1 2 3 nil)))
     (tramp-compat-seq-keep
-     (lambda (structure) (tramp-completion-dissect-file-name1 structure name))
+     (lambda (structure)
+       (tramp-completion-dissect-file-name1 structure internal-name))
      (list
       tramp-completion-file-name-structure1
       tramp-completion-file-name-structure2
       tramp-completion-file-name-structure3
       tramp-completion-file-name-structure4
       tramp-completion-file-name-structure5
-      tramp-completion-file-name-structure6))))
+      tramp-completion-file-name-structure6
+      tramp-completion-file-name-structure7
+      tramp-completion-file-name-structure8
+      tramp-completion-file-name-structure9
+      tramp-completion-file-name-structure10))))
 
 (defun tramp-completion-dissect-file-name1 (structure name)
   "Return a `tramp-file-name' structure for NAME matching STRUCTURE.
@@ -3202,7 +3342,10 @@ PARTIAL-USER must match USER, PARTIAL-HOST must match 
HOST."
   "Return a list of (user host) tuples allowed to access for METHOD.
 This function is added always in `tramp-get-completion-function'
 for all methods.  Resulting data are derived from default settings."
-  `((,(tramp-find-user method nil nil) ,(tramp-find-host method nil nil))))
+  (let ((user (tramp-find-user method nil nil))
+       (host (tramp-find-host method nil nil)))
+    (when (or user host)
+      `((,user ,host)))))
 
 ;;;###tramp-autoload
 (defcustom tramp-completion-multi-hop-methods nil
@@ -3224,10 +3367,11 @@ as for \"~/.authinfo.gpg\"."
 This function is added always in `tramp-get-completion-function'
 for all methods.  Resulting data are derived from default settings."
   (and tramp-completion-use-auth-sources
-       (mapcar
-       (lambda (x) `(,(plist-get x :user) ,(plist-get x :host)))
-       (auth-source-search
-        :port method :require '(:port) :max most-positive-fixnum))))
+       (delete-dups
+       (tramp-compat-seq-keep
+        (lambda (x) `(,(plist-get x :user) ,(plist-get x :host)))
+        (auth-source-search
+         :port method :require '(:port) :max most-positive-fixnum)))))
 
 ;; Generic function.
 (defun tramp-parse-group (regexp match-level skip-chars)
@@ -3252,7 +3396,8 @@ User is always nil."
       (with-temp-buffer
        (insert-file-contents-literally filename)
        (goto-char (point-min))
-        (cl-loop while (not (eobp)) collect (funcall function))))))
+       (delete-dups (delq nil
+         (cl-loop while (not (eobp)) collect (funcall function))))))))
 
 (defun tramp-parse-rhosts (filename)
   "Return a list of (user host) tuples allowed to access.
@@ -3280,7 +3425,9 @@ User is always nil."
 (defun tramp-parse-shosts-group ()
    "Return a (user host) tuple allowed to access.
 User is always nil."
-   (tramp-parse-group (rx bol (group (regexp tramp-host-regexp))) 1 ","))
+   (tramp-parse-group
+    (rx bol (group (| (regexp tramp-ipv6-regexp) (regexp tramp-host-regexp))))
+    1 ","))
 
 (defun tramp-parse-sconfig (filename)
   "Return a list of (user host) tuples allowed to access.
@@ -3386,11 +3533,12 @@ Host is always \"localhost\"."
 (defun tramp-parse-netrc (filename)
   "Return a list of (user host) tuples allowed to access.
 User may be nil."
-  (mapcar
-   (lambda (item)
-     (and (assoc "machine" item)
-         `(,(cdr (assoc "login" item)) ,(cdr (assoc "machine" item)))))
-   (tramp-compat-auth-source-netrc-parse-all filename)))
+  (delete-dups
+   (tramp-compat-seq-keep
+    (lambda (item)
+      (and (assoc "machine" item)
+          `(,(cdr (assoc "login" item)) ,(cdr (assoc "machine" item)))))
+    (tramp-compat-auth-source-netrc-parse-all filename))))
 
 (defun tramp-parse-putty (registry-or-dirname)
   "Return a list of (user host) tuples allowed to access.
@@ -4191,10 +4339,18 @@ Let-bind it when necessary.")
 
 (defun tramp-handle-file-directory-p (filename)
   "Like `file-directory-p' for Tramp files."
-  ;; `file-truename' could raise an error, for example due to a cyclic
-  ;; symlink.
-  (ignore-errors
-    (eq (file-attribute-type (file-attributes (file-truename filename))) t)))
+  (or
+   ;; `file-directory-p' is used as predicate for file name
+   ;; completion.  Sometimes, when a connection is not established
+   ;; yet, it is desirable to return t immediately for "/method:foo:"
+   ;; or "/method:foo:/".  It can be expected that this is always a
+   ;; directory.
+   (tramp-string-empty-or-nil-p (tramp-file-local-name filename))
+   (string-equal (tramp-file-local-name filename) "/")
+   ;; `file-truename' could raise an error, for example due to a
+   ;; cyclic symlink.
+   (ignore-errors
+     (eq (file-attribute-type (file-attributes (file-truename filename))) t))))
 
 (defun tramp-handle-file-equal-p (filename1 filename2)
   "Like `file-equal-p' for Tramp files."
diff --git a/trampver.el b/trampver.el
index 9ee964613c..c3f7cc4966 100644
--- a/trampver.el
+++ b/trampver.el
@@ -7,7 +7,7 @@
 ;; Maintainer: Michael Albinus <michael.albi...@gmx.de>
 ;; Keywords: comm, processes
 ;; Package: tramp
-;; Version: 2.8.0.1
+;; Version: 2.8.0.2
 ;; Package-Requires: ((emacs "28.1"))
 ;; Package-Type: multi
 ;; URL: https://www.gnu.org/software/tramp/
@@ -40,7 +40,7 @@
 ;; ./configure" to change them.
 
 ;;;###tramp-autoload
-(defconst tramp-version "2.8.0.1"
+(defconst tramp-version "2.8.0.2"
   "This version of Tramp.")
 
 ;;;###tramp-autoload
@@ -76,7 +76,7 @@
 ;; Check for Emacs version.
 (let ((x   (if (not (string-version-lessp emacs-version "28.1"))
       "ok"
-    (format "Tramp 2.8.0.1 is not fit for %s"
+    (format "Tramp 2.8.0.2 is not fit for %s"
             (replace-regexp-in-string "\n" "" (emacs-version))))))
   (unless (string-equal "ok" x) (error "%s" x)))
 

Reply via email to