Subject: Bug+fix for eshell-hist-ignore-dups 'erase

From: Alexander Kozhevnikov <[email-masked]>
Date: Sun, 7 May 2023 17:31:01 +0000

Bug:

In these two lines in em-hist.el (in the function eshell-add-input-to-history):

                   (ring-remove eshell-history-ring
                        (ring-member eshell-history-ring input))

Since `ring-member` will return nil if there is no duplicate, but
`ring-remove` will delete the last entry if given a nil index, a
history entry is always deleted even if there is no duplicate.

Crucially, this happens even if the history ring is smaller than
eshell-history-size - the history ring never grows. In the most
egregious case, if the user starts from a history with one entry,
their history ring will get stuck at just one entry.

(If the user starts with zero history entries, their history ring will
also get stuck at zero entries if eshell-hist-ignoredups is 'erase,
but the cause is somewhere else in that same function - haven't had
time to track that down more precisely yet.)

I bet this reproduces on all versions of Emacs from latest going back
at least a few years, but most of my devices are on 28.2 in
particular.

Reproduce:

>From `emacs -Q`, evaluate the following:

; turn on the relevant setting
(setq eshell-hist-ignoredups 'erase)

; start with a clean blank history
(setq eshell-history-file-name "/tmp/a-new-eshell-history-for-test")

Start eshell with `M-x eshell RET`.

Manually insert one entry into the history ring after starting eshell
with a lower-level function to get started, for example by evaluating:

Now run any other command inside eshell and observe that the history
ring never grows, even though it should grow up to the default limit
(128).

Fix:

Replace the above two lines with these three lines, or something
similar. (I don't think this is substantial enough to be copyrightable
and thus shouldn't require copyright assignment, but let me know if
you need me to get those papers signed.)

                   (let ((index (ring-member eshell-history-ring input)))
                     (when index
                       (ring-remove eshell-history-ring index)))