Patchwork D9559: windows: continue looking at `%HOME%` for user config files with py3.8+

mail settings
Submitter phabricator
Date Dec. 10, 2020, 4:28 a.m.
Message ID <>
Download mbox | patch
Permalink /patch/47853/
State Superseded
Headers show


phabricator - Dec. 10, 2020, 4:28 a.m.
mharbison72 created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

  The `%HOME%` variable is explicitly called out in `hg help config` as a location
  that is consulted when reading user files, but python stopped looking at it
  when expanding '~' in py3.8+.[1]  Restore that old functionality by copying in
  the old implementation (and simplifying it to just use bytes).  It could be
  simplfied further, since only '~' is passed, but I'm not sure yet if we need to
  make this a generic utility function on Windows.  There are other uses of
  `os.path.expanduser()`, but this is the only case I know of that documents
  `%HOME%` usage.
  (The reason for removing it was that it typically isn't set, but it actually is
  set in MSYS and PowerShell, and `%HOME%` and `%USERPROFILE%` are different in
  MSYS.  I could be convinced to just replace all uses with this as a general
  utility, so we don't have to think too hard about BC.)

  rHG Mercurial





To: mharbison72, #hg-reviewers
Cc: mercurial-patches, mercurial-devel


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -68,7 +68,7 @@ 
 def userrcpath():
     '''return os-specific hgrc search path to the user dir'''
-    home = os.path.expanduser(b'~')
+    home = _legacy_expanduser(b'~')
     path = [os.path.join(home, b'mercurial.ini'), os.path.join(home, b'.hgrc')]
     userprofile = encoding.environ.get(b'USERPROFILE')
     if userprofile and userprofile != home:
@@ -77,5 +77,37 @@ 
     return path
+def _legacy_expanduser(path):
+    """Expand ~ and ~user constructs in the pre 3.8 style"""
+    # Python 3.8+ changed the expansion of '~' from HOME to USERPROFILE.  See
+    #  It also seems to capitalize the drive
+    # letter, as though it was processed through os.path.realpath().
+    if not path.startswith(b'~'):
+        return path
+    i, n = 1, len(path)
+    while i < n and path[i] not in b'\\/':
+        i += 1
+    if b'HOME' in encoding.environ:
+        userhome = encoding.environ[b'HOME']
+    elif b'USERPROFILE' in encoding.environ:
+        userhome = encoding.environ[b'USERPROFILE']
+    elif b'HOMEPATH' not in encoding.environ:
+        return path
+    else:
+        try:
+            drive = encoding.environ[b'HOMEDRIVE']
+        except KeyError:
+            drive = b''
+        userhome = os.path.join(drive, encoding.environ[b'HOMEPATH'])
+    if i != 1:  # ~user
+        userhome = os.path.join(os.path.dirname(userhome), path[1:i])
+    return userhome + path[i:]
 def termsize(ui):
     return win32.termsize()