# HG changeset patch # User Manuel Jacob # Date 1593360165 -7200 # Node ID 1bab6b61b62b329150571e417880995dabbeea3a # Parent 2632c1ed8f34988fc918d70ccc40b01c20c08a53 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. diff -r 2632c1ed8f34 -r 1bab6b61b62b hgext/histedit.py --- 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: diff -r 2632c1ed8f34 -r 1bab6b61b62b mercurial/crecord.py --- 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 diff -r 2632c1ed8f34 -r 1bab6b61b62b mercurial/util.py --- 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