[PATCH 2 of 2] Make util.find_exe alway returns existing file, fixing issue1459

Mads Kiilerich mads at kiilerich.com
Sun Jan 25 15:01:18 CST 2009


# HG changeset patch
# User Mads Kiilerich <mads at kiilerich.com>
# Date 1232914813 -3600
# Node ID c67125ebde3e11ecc8215295104e5eea6e0ddaa1
# Parent  606d59504114f2356439581019faada66f4b9e23
Make util.find_exe alway returns existing file, fixing issue1459

It seems like the old behaviour with different handling for commands with and
without path was intended, but I think this behaviour of util.find_exe is
better:
* Always returns existing file
* or None if command not found - no default
* Windows: Returned file thus always ends with extension from PATHEXT

This fixes http://www.selenic.com/mercurial/bts/issue1459. The change might
fix other unintended behaviour too.

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1137,7 +1137,7 @@
             try:
                 return internalpatch(patchname, ui, strip, cwd, files)
             except NoHunks:
-                patcher = util.find_exe('gpatch') or util.find_exe('patch')
+                patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
                 ui.debug(_('no valid hunks found; trying with %r instead\n') %
                          patcher)
                 if util.needbinarypatch():
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -653,7 +653,7 @@
         elif main_is_frozen():
             set_hgexecutable(sys.executable)
         else:
-            set_hgexecutable(find_exe('hg', 'hg'))
+            set_hgexecutable(find_exe('hg') or 'hg')
     return _hgexecutable
 
 def set_hgexecutable(path):
@@ -1270,29 +1270,33 @@
     def isowner(fp, st=None):
         return True
 
-    def find_in_path(name, path, default=None):
-        '''find name in search path. path can be string (will be split
-        with os.pathsep), or iterable thing that returns strings.  if name
-        found, return path to name. else return default. name is looked up
-        using cmd.exe rules, using PATHEXT.'''
-        if isinstance(path, str):
-            path = path.split(os.pathsep)
+    def find_exe(command):
+        '''Find executable for command searching like cmd.exe does.
+        If command is a basename then PATH is searched for command.
+        PATH isn't searched if command is an absolute or relative path.  
+        An extension from PATHEXT is found and added if not present.
+        If command isn't found None is returned.'''
+        pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
+        pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
+        if os.path.splitext(command)[1].lower() in pathexts:
+            pathexts = ['']
+        
+        def findexisting(pathcommand):
+            'Will append extension (if needed) and return existing file'
+            for ext in pathexts:
+                executable = pathcommand + ext
+                if os.path.exists(executable):
+                    return executable
+            return None
 
-        pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
-        pathext = pathext.lower().split(os.pathsep)
-        isexec = os.path.splitext(name)[1].lower() in pathext
-
-        for p in path:
-            p_name = os.path.join(p, name)
-
-            if isexec and os.path.exists(p_name):
-                return p_name
-
-            for ext in pathext:
-                p_name_ext = p_name + ext
-                if os.path.exists(p_name_ext):
-                    return p_name_ext
-        return default
+        if os.sep in command:
+            return findexisting(command)
+            
+        for path in os.environ.get('PATH', '').split(os.pathsep):
+            executable = findexisting(os.path.join(path, command))
+            if executable is not None:
+                return executable
+        return None
 
     def set_signal_handler():
         try:
@@ -1458,33 +1462,32 @@
             st = fstat(fp)
         return st.st_uid == os.getuid()
 
-    def find_in_path(name, path, default=None):
-        '''find name in search path. path can be string (will be split
-        with os.pathsep), or iterable thing that returns strings.  if name
-        found, return path to name. else return default.'''
-        if isinstance(path, str):
-            path = path.split(os.pathsep)
-        for p in path:
-            p_name = os.path.join(p, name)
-            if os.path.exists(p_name):
-                return p_name
-        return default
+    def find_exe(command):
+        '''Find executable for command searching like which does.
+        If command is a basename then PATH is searched for command.
+        PATH isn't searched if command is an absolute or relative path.  
+        If command isn't found None is returned.'''
+        if sys.platform == 'OpenVMS':
+            return command
+        
+        def findexisting(executable):
+            'Will return executable if existing file'
+            if os.path.exists(executable):
+                return executable
+            return None
+
+        if os.sep in command:
+            return findexisting(command)
+            
+        for path in os.environ.get('PATH', '').split(os.pathsep):
+            executable = findexisting(os.path.join(path, command))
+            if executable is not None:
+                return executable
+        return None
 
     def set_signal_handler():
         pass
 
-def find_exe(name, default=None):
-    '''find path of an executable.
-    if name contains a path component, return it as is.  otherwise,
-    use normal executable search path.'''
-
-    if os.sep in name or sys.platform == 'OpenVMS':
-        # don't check the executable bit.  if the file isn't
-        # executable, whoever tries to actually run it will give a
-        # much more useful error message.
-        return name
-    return find_in_path(name, os.environ.get('PATH', ''), default=default)
-
 def mktempcopy(name, emptyok=False, createmode=None):
     """Create a temporary file with the same contents from name
 
diff --git a/tests/test-merge-tools.out b/tests/test-merge-tools.out
--- a/tests/test-merge-tools.out
+++ b/tests/test-merge-tools.out
@@ -117,7 +117,6 @@
 true.priority=1
 # hg update -C 1
 # hg merge -r 2 --config merge-tools.true.executable=/bin/nonexistingmergetool
-sh: /bin/nonexistingmergetool: No such file or directory
 merging f
 merging f failed!
 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -224,7 +223,7 @@
 true.executable=cat
 # hg update -C 1
 # hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=/bin/nonexistingmergetool
-sh: /bin/nonexistingmergetool: No such file or directory
+couldn't find merge tool true specified for f
 merging f
 merging f failed!
 0 files updated, 0 files merged, 0 files removed, 1 files unresolved


More information about the Mercurial-devel mailing list