curses: do not initialize LC_ALL to user settings (
issue6358)
701341f57ceb moved the setlocale() call to right before curses was used. This
didn’t fully solve the problem it was supposed to solve (locale-dependent
functions, like date formatting/parsing and str methods on Python 2), but only
postponed it.
Initializing LC_CTYPE seems to be sufficient for curses to work correctly.
Therefore LC_CTYPE is set while curses is used and reset afterwards. Some
locale-dependent str methods might behave differently on Python 2 while curses
is used, but that shouldn’d be a problem.
--- a/hgext/histedit.py Thu Jun 25 03:46:07 2020 +0200
+++ b/hgext/histedit.py Sun Jun 28 18:02:45 2020 +0200
@@ -201,7 +201,6 @@
termios = None
import functools
-import locale
import os
import struct
@@ -1710,11 +1709,8 @@
ctxs = []
for i, r in enumerate(revs):
ctxs.append(histeditrule(ui, repo[r], i))
- # Curses requires setting the locale or it will default to the C
- # locale. This sets the locale to the user's default system
- # locale.
- locale.setlocale(locale.LC_ALL, '')
- rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
+ with util.with_lc_ctype():
+ rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
curses.echo()
curses.endwin()
if rc is False:
--- a/mercurial/crecord.py Thu Jun 25 03:46:07 2020 +0200
+++ b/mercurial/crecord.py Sun Jun 28 18:02:45 2020 +0200
@@ -10,7 +10,6 @@
from __future__ import absolute_import
-import locale
import os
import re
import signal
@@ -574,14 +573,12 @@
"""
ui.write(_(b'starting interactive selection\n'))
chunkselector = curseschunkselector(headerlist, ui, operation)
- # This is required for ncurses to display non-ASCII characters in
- # default user locale encoding correctly. --immerrr
- locale.setlocale(locale.LC_ALL, '')
origsigtstp = sentinel = object()
if util.safehasattr(signal, b'SIGTSTP'):
origsigtstp = signal.getsignal(signal.SIGTSTP)
try:
- curses.wrapper(chunkselector.main)
+ with util.with_lc_ctype():
+ curses.wrapper(chunkselector.main)
if chunkselector.initexc is not None:
raise chunkselector.initexc
# ncurses does not restore signal handler for SIGTSTP
--- a/mercurial/util.py Thu Jun 25 03:46:07 2020 +0200
+++ b/mercurial/util.py Sun Jun 28 18:02:45 2020 +0200
@@ -22,6 +22,7 @@
import gc
import hashlib
import itertools
+import locale
import mmap
import os
import platform as pyplatform
@@ -3626,3 +3627,32 @@
if not (byte & 0x80):
return result
shift += 7
+
+
+# Passing the '' locale means that the locale should be set according to the
+# user settings (environment variables).
+# Python sometimes avoids setting the global locale settings. When interfacing
+# with C code (e.g. the curses module or the Subversion bindings), the global
+# locale settings must be initialized correctly. Python 2 does not initialize
+# the global locale settings on interpreter startup. Python 3 sometimes
+# initializes LC_CTYPE, but not consistently at least on Windows. Therefore we
+# explicitly initialize it to get consistent behavior if it's not already
+# initialized. Since CPython commit 177d921c8c03d30daa32994362023f777624b10d,
+# LC_CTYPE is always initialized. If we require Python 3.8+, we should re-check
+# if we can remove this code.
+@contextlib.contextmanager
+def with_lc_ctype():
+ oldloc = locale.setlocale(locale.LC_CTYPE, None)
+ if oldloc == 'C':
+ try:
+ try:
+ locale.setlocale(locale.LC_CTYPE, '')
+ except locale.Error:
+ # The likely case is that the locale from the environment
+ # variables is unknown.
+ pass
+ yield
+ finally:
+ locale.setlocale(locale.LC_CTYPE, oldloc)
+ else:
+ yield