Merge lp:~doxxx/bzr/bug939605 into lp:bzr

Proposed by Gordon Tyler
Status: Rejected
Rejected by: Martin Packman
Proposed branch: lp:~doxxx/bzr/bug939605
Merge into: lp:bzr
Diff against target: 155 lines (+53/-21)
5 files modified
bzrlib/mergetools.py (+16/-7)
bzrlib/osutils.py (+12/-9)
bzrlib/tests/test_mergetools.py (+14/-5)
bzrlib/tests/test_osutils.py (+8/-0)
doc/en/release-notes/bzr-2.6.txt (+3/-0)
To merge this branch: bzr merge lp:~doxxx/bzr/bug939605
Reviewer Review Type Date Requested Status
Martin Pool Approve
Martin Packman (community) Needs Information
Alexander Belchenko Approve
Review via email: mp+94488@code.launchpad.net

Description of the change

Fixes bug 939605 by updating osutils.find_executable_on_path to additionally search the Windows App Path registry.

The check_availability and invoke functions in mergetools use a small utility function, _get_executable_path, to get the full path to the executable in the mergetool commandline, which skips the PATH search if the path is already absolute.

To post a comment you must log in.
Revision history for this message
Alexander Belchenko (bialix) wrote :

Looks OK for me, though I can't comment on your change to existing test. Maybe second voice from core dev is needed. I can say that I've tested it and it works. Thank you for fixing it so fast.

Also, I'd like to have this patch backported to bzr 2.5.1, because it clearly fixed bug and don't change anything public.

And you need NEWS message, I guess ;-)

review: Approve
Revision history for this message
Gordon Tyler (doxxx) wrote :

Darn NEWS...

Revision history for this message
Gordon Tyler (doxxx) wrote :

Release notes updated.

Revision history for this message
Martin Packman (gz) wrote :

Wouldn't it be better to fold the logic you've added in _get_executable_path into osutils.find_executable_on_path? If we need a look-before-you-leap executable finder, we may as well have a single one that does the right thing rather than needing extra special cases on top. I wouldn't worry about that making the '_on_path' naming a little misleading, it's hardly the biggest problem is osutils.

For the test you could override the PATH envvar for the duration of the test. Just %SystemRoot% should do.

review: Needs Information
Revision history for this message
Gordon Tyler (doxxx) wrote :

Since Win32's ShellExecute function searches the App Path, it makes sense to include it in osutils.find_executable_on_path.

That's a good point about the SystemRoot env var. Since '%SystemRoot\system32' is guaranteed to be in the PATH, I can use something like cmd.exe to test that and use the env var to construct the path to test against.

Thanks.

Revision history for this message
Gordon Tyler (doxxx) wrote :

Updated according to Martin's suggestions.

Revision history for this message
Martin Pool (mbp) wrote :

looks reasonable to me too, and +1 more to putting this in 2.5

review: Approve
Revision history for this message
Martin Packman (gz) wrote :

Unmerged revisions

6481. By Gordon Tyler

Re-removed unnecessary import.

6480. By Gordon Tyler

Improved readablity of check_availability.

6479. By Gordon Tyler

Removed unnecessary import.

6478. By Gordon Tyler

Updated mergetools._get_executable_path and improved test_invoke_expands_exe_path.

6477. By Gordon Tyler

Changed osutils.find_executable_on_path to search using win32utils.get_app_path if the executable is not found using the PATH env var.

6476. By Gordon Tyler

Updated release notes.

6475. By Gordon Tyler

Added _get_executable_path function to mergetools module, which searches the PATH and App Path registry on win32.
Changed check_availability and invoke to use _get_executable_path.
Updated tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/mergetools.py'
2--- bzrlib/mergetools.py 2011-12-19 13:23:58 +0000
3+++ bzrlib/mergetools.py 2012-02-27 17:50:24 +0000
4@@ -48,13 +48,13 @@
5 cmd_list = cmdline.split(command_line)
6 exe = cmd_list[0]
7 if sys.platform == 'win32':
8- if os.path.isabs(exe):
9- base, ext = os.path.splitext(exe)
10- path_ext = [unicode(s.lower())
11- for s in os.getenv('PATHEXT', '').split(os.pathsep)]
12- return os.path.exists(exe) and ext in path_ext
13- else:
14- return osutils.find_executable_on_path(exe) is not None
15+ exe = _get_executable_path(exe)
16+ if exe is None:
17+ return False
18+ base, ext = os.path.splitext(exe)
19+ path_ext = [unicode(s.lower())
20+ for s in os.getenv('PATHEXT', '').split(os.pathsep)]
21+ return os.path.exists(exe) and ext in path_ext
22 else:
23 return (os.access(exe, os.X_OK)
24 or osutils.find_executable_on_path(exe) is not None)
25@@ -69,6 +69,9 @@
26 if invoker is None:
27 invoker = subprocess_invoker
28 cmd_list = cmdline.split(command_line)
29+ exe = _get_executable_path(cmd_list[0])
30+ if exe is not None:
31+ cmd_list[0] = exe
32 args, tmp_file = _subst_filename(cmd_list, filename)
33 def cleanup(retcode):
34 if tmp_file is not None:
35@@ -79,6 +82,12 @@
36 return invoker(args[0], args[1:], cleanup)
37
38
39+def _get_executable_path(exe):
40+ if os.path.isabs(exe):
41+ return exe
42+ return osutils.find_executable_on_path(exe)
43+
44+
45 def _subst_filename(args, filename):
46 subst_names = {
47 'base': filename + u'.BASE',
48
49=== modified file 'bzrlib/osutils.py'
50--- bzrlib/osutils.py 2012-02-01 12:31:53 +0000
51+++ bzrlib/osutils.py 2012-02-27 17:50:24 +0000
52@@ -2473,10 +2473,6 @@
53 :param name: The base name of the executable.
54 :return: The path to the executable found or None.
55 """
56- path = os.environ.get('PATH')
57- if path is None:
58- return None
59- path = path.split(os.pathsep)
60 if sys.platform == 'win32':
61 exts = os.environ.get('PATHEXT', '').split(os.pathsep)
62 exts = [ext.lower() for ext in exts]
63@@ -2488,11 +2484,18 @@
64 exts = [ext]
65 else:
66 exts = ['']
67- for ext in exts:
68- for d in path:
69- f = os.path.join(d, name) + ext
70- if os.access(f, os.X_OK):
71- return f
72+ path = os.environ.get('PATH')
73+ if path is not None:
74+ path = path.split(os.pathsep)
75+ for ext in exts:
76+ for d in path:
77+ f = os.path.join(d, name) + ext
78+ if os.access(f, os.X_OK):
79+ return f
80+ if sys.platform == 'win32':
81+ app_path = win32utils.get_app_path(name)
82+ if app_path != name:
83+ return app_path
84 return None
85
86
87
88=== modified file 'bzrlib/tests/test_mergetools.py'
89--- bzrlib/tests/test_mergetools.py 2011-06-30 21:00:38 +0000
90+++ bzrlib/tests/test_mergetools.py 2012-02-27 17:50:24 +0000
91@@ -81,11 +81,7 @@
92 self.assertTrue(mergetools.check_availability(sys.executable))
93
94 def test_exe_on_path(self):
95- if sys.platform == 'win32':
96- exe = 'cmd.exe'
97- else:
98- exe = 'sh'
99- self.assertTrue(mergetools.check_availability(exe))
100+ self.assertTrue(mergetools.check_availability('python'))
101
102 def test_nonexistent(self):
103 self.assertFalse(mergetools.check_availability('DOES NOT EXIST'))
104@@ -112,6 +108,19 @@
105 ('test.txt.OTHER', 'other stuff'),
106 ))
107
108+ def test_invoke_expands_exe_path(self):
109+ self.overrideEnv('PATH', os.path.dirname(sys.executable))
110+ def dummy_invoker(exe, args, cleanup):
111+ self._exe = exe
112+ self._args = args
113+ cleanup(0)
114+ return 0
115+ command = '%s {result}' % os.path.basename(sys.executable)
116+ retcode = mergetools.invoke(command, 'test.txt', dummy_invoker)
117+ self.assertEqual(0, retcode)
118+ self.assertEqual(sys.executable, self._exe)
119+ self.assertEqual(['test.txt'], self._args)
120+
121 def test_success(self):
122 def dummy_invoker(exe, args, cleanup):
123 self._exe = exe
124
125=== modified file 'bzrlib/tests/test_osutils.py'
126--- bzrlib/tests/test_osutils.py 2012-02-01 12:32:26 +0000
127+++ bzrlib/tests/test_osutils.py 2012-02-27 17:50:24 +0000
128@@ -2225,6 +2225,14 @@
129 self.assertTrue(
130 osutils.find_executable_on_path('THIS SHOULD NOT EXIST') is None)
131 self.assertTrue(osutils.find_executable_on_path('file.txt') is None)
132+
133+ def test_windows_app_path(self):
134+ if sys.platform != 'win32':
135+ raise tests.TestSkipped('test requires win32')
136+ # Override PATH env var so that exe can only be found on App Path
137+ self.overrideEnv('PATH', '')
138+ # Internt Explorer is always registered in the App Path
139+ self.assertTrue(osutils.find_executable_on_path('iexplore') is not None)
140
141 def test_other(self):
142 if sys.platform == 'win32':
143
144=== modified file 'doc/en/release-notes/bzr-2.6.txt'
145--- doc/en/release-notes/bzr-2.6.txt 2012-02-25 14:13:19 +0000
146+++ doc/en/release-notes/bzr-2.6.txt 2012-02-27 17:50:24 +0000
147@@ -47,6 +47,9 @@
148 * Fix ``bzr config`` display for ``RegistryOption`` values.
149 (Vincent Ladeuil, #930182)
150
151+* Fixed merge tool availability checking and invocation to search the
152+ Windows App Path registry in addition to the PATH. (Gordon Tyler, #939605)
153+
154 Documentation
155 *************
156