Skip to content

Commit 1c3f31c

Browse files
authored
Merge pull request pypa#7072 from TonyBeswick/master
Fix pip freeze not showing correct entry for mercurial packages that use subdirectories.
2 parents 8c66447 + 7ebc541 commit 1c3f31c

File tree

6 files changed

+129
-32
lines changed

6 files changed

+129
-32
lines changed

news/7071.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``pip freeze`` not showing correct entry for mercurial packages that use subdirectories.

src/pip/_internal/utils/subprocess.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ def call_subprocess(
123123
command_desc=None, # type: Optional[str]
124124
extra_environ=None, # type: Optional[Mapping[str, Any]]
125125
unset_environ=None, # type: Optional[Iterable[str]]
126-
spinner=None # type: Optional[SpinnerInterface]
126+
spinner=None, # type: Optional[SpinnerInterface]
127+
log_failed_cmd=True # type: Optional[bool]
127128
):
128129
# type: (...) -> Text
129130
"""
@@ -134,6 +135,7 @@ def call_subprocess(
134135
acceptable, in addition to 0. Defaults to None, which means [].
135136
unset_environ: an iterable of environment variable names to unset
136137
prior to calling subprocess.Popen().
138+
log_failed_cmd: if false, failed commands are not logged, only raised.
137139
"""
138140
if extra_ok_returncodes is None:
139141
extra_ok_returncodes = []
@@ -189,9 +191,10 @@ def call_subprocess(
189191
)
190192
proc.stdin.close()
191193
except Exception as exc:
192-
subprocess_logger.critical(
193-
"Error %s while executing command %s", exc, command_desc,
194-
)
194+
if log_failed_cmd:
195+
subprocess_logger.critical(
196+
"Error %s while executing command %s", exc, command_desc,
197+
)
195198
raise
196199
all_output = []
197200
while True:
@@ -222,7 +225,7 @@ def call_subprocess(
222225
spinner.finish("done")
223226
if proc_had_error:
224227
if on_returncode == 'raise':
225-
if not showing_subprocess:
228+
if not showing_subprocess and log_failed_cmd:
226229
# Then the subprocess streams haven't been logged to the
227230
# console yet.
228231
msg = make_subprocess_output_error(

src/pip/_internal/vcs/git.py

+12-23
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
from pip._vendor.six.moves.urllib import request as urllib_request
1313

1414
from pip._internal.exceptions import BadCommand
15-
from pip._internal.utils.compat import samefile
1615
from pip._internal.utils.misc import display_path
1716
from pip._internal.utils.subprocess import make_command
1817
from pip._internal.utils.temp_dir import TempDirectory
1918
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2019
from pip._internal.vcs.versioncontrol import (
2120
RemoteNotFoundError,
2221
VersionControl,
22+
find_path_to_setup_from_repo_root,
2323
vcs,
2424
)
2525

@@ -295,30 +295,18 @@ def get_revision(cls, location, rev=None):
295295

296296
@classmethod
297297
def get_subdirectory(cls, location):
298+
"""
299+
Return the path to setup.py, relative to the repo root.
300+
Return None if setup.py is in the repo root.
301+
"""
298302
# find the repo root
299-
git_dir = cls.run_command(['rev-parse', '--git-dir'],
300-
show_stdout=False, cwd=location).strip()
303+
git_dir = cls.run_command(
304+
['rev-parse', '--git-dir'],
305+
show_stdout=False, cwd=location).strip()
301306
if not os.path.isabs(git_dir):
302307
git_dir = os.path.join(location, git_dir)
303-
root_dir = os.path.join(git_dir, '..')
304-
# find setup.py
305-
orig_location = location
306-
while not os.path.exists(os.path.join(location, 'setup.py')):
307-
last_location = location
308-
location = os.path.dirname(location)
309-
if location == last_location:
310-
# We've traversed up to the root of the filesystem without
311-
# finding setup.py
312-
logger.warning(
313-
"Could not find setup.py for directory %s (tried all "
314-
"parent directories)",
315-
orig_location,
316-
)
317-
return None
318-
# relative path of setup.py to repo root
319-
if samefile(root_dir, location):
320-
return None
321-
return os.path.relpath(location, root_dir)
308+
repo_root = os.path.abspath(os.path.join(git_dir, '..'))
309+
return find_path_to_setup_from_repo_root(location, repo_root)
322310

323311
@classmethod
324312
def get_url_rev_and_auth(cls, url):
@@ -372,7 +360,8 @@ def controls_location(cls, location):
372360
r = cls.run_command(['rev-parse'],
373361
cwd=location,
374362
show_stdout=False,
375-
on_returncode='ignore')
363+
on_returncode='ignore',
364+
log_failed_cmd=False)
376365
return not r
377366
except BadCommand:
378367
logger.debug("could not determine if %s is under git control "

src/pip/_internal/vcs/mercurial.py

+34-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88

99
from pip._vendor.six.moves import configparser
1010

11+
from pip._internal.exceptions import BadCommand, InstallationError
1112
from pip._internal.utils.misc import display_path
1213
from pip._internal.utils.subprocess import make_command
1314
from pip._internal.utils.temp_dir import TempDirectory
1415
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1516
from pip._internal.utils.urls import path_to_url
16-
from pip._internal.vcs.versioncontrol import VersionControl, vcs
17+
from pip._internal.vcs.versioncontrol import (
18+
VersionControl,
19+
find_path_to_setup_from_repo_root,
20+
vcs,
21+
)
1722

1823
if MYPY_CHECK_RUNNING:
1924
from pip._internal.utils.misc import HiddenText
@@ -118,5 +123,33 @@ def is_commit_id_equal(cls, dest, name):
118123
"""Always assume the versions don't match"""
119124
return False
120125

126+
@classmethod
127+
def get_subdirectory(cls, location):
128+
"""
129+
Return the path to setup.py, relative to the repo root.
130+
Return None if setup.py is in the repo root.
131+
"""
132+
# find the repo root
133+
repo_root = cls.run_command(
134+
['root'], show_stdout=False, cwd=location).strip()
135+
if not os.path.isabs(repo_root):
136+
repo_root = os.path.abspath(os.path.join(location, repo_root))
137+
return find_path_to_setup_from_repo_root(location, repo_root)
138+
139+
@classmethod
140+
def controls_location(cls, location):
141+
if super(Mercurial, cls).controls_location(location):
142+
return True
143+
try:
144+
cls.run_command(
145+
['identify'],
146+
cwd=location,
147+
show_stdout=False,
148+
on_returncode='raise',
149+
log_failed_cmd=False)
150+
return True
151+
except (BadCommand, InstallationError):
152+
return False
153+
121154

122155
vcs.register(Mercurial)

src/pip/_internal/vcs/versioncontrol.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pip._vendor.six.moves.urllib import parse as urllib_parse
1616

1717
from pip._internal.exceptions import BadCommand
18+
from pip._internal.utils.compat import samefile
1819
from pip._internal.utils.misc import (
1920
ask_path_exists,
2021
backup_dir,
@@ -71,6 +72,33 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
7172
return req
7273

7374

75+
def find_path_to_setup_from_repo_root(location, repo_root):
76+
"""
77+
Find the path to `setup.py` by searching up the filesystem from `location`.
78+
Return the path to `setup.py` relative to `repo_root`.
79+
Return None if `setup.py` is in `repo_root` or cannot be found.
80+
"""
81+
# find setup.py
82+
orig_location = location
83+
while not os.path.exists(os.path.join(location, 'setup.py')):
84+
last_location = location
85+
location = os.path.dirname(location)
86+
if location == last_location:
87+
# We've traversed up to the root of the filesystem without
88+
# finding setup.py
89+
logger.warning(
90+
"Could not find setup.py for directory %s (tried all "
91+
"parent directories)",
92+
orig_location,
93+
)
94+
return None
95+
96+
if samefile(repo_root, location):
97+
return None
98+
99+
return os.path.relpath(location, repo_root)
100+
101+
74102
class RemoteNotFoundError(Exception):
75103
pass
76104

@@ -240,9 +268,10 @@ def should_add_vcs_url_prefix(cls, remote_url):
240268
return not remote_url.lower().startswith('{}:'.format(cls.name))
241269

242270
@classmethod
243-
def get_subdirectory(cls, repo_dir):
271+
def get_subdirectory(cls, location):
244272
"""
245273
Return the path to setup.py, relative to the repo root.
274+
Return None if setup.py is in the repo root.
246275
"""
247276
return None
248277

@@ -582,7 +611,8 @@ def run_command(
582611
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
583612
command_desc=None, # type: Optional[str]
584613
extra_environ=None, # type: Optional[Mapping[str, Any]]
585-
spinner=None # type: Optional[SpinnerInterface]
614+
spinner=None, # type: Optional[SpinnerInterface]
615+
log_failed_cmd=True
586616
):
587617
# type: (...) -> Text
588618
"""
@@ -598,7 +628,8 @@ def run_command(
598628
command_desc=command_desc,
599629
extra_environ=extra_environ,
600630
unset_environ=cls.unset_environ,
601-
spinner=spinner)
631+
spinner=spinner,
632+
log_failed_cmd=log_failed_cmd)
602633
except OSError as e:
603634
# errno.ENOENT = no such file or directory
604635
# In other words, the VCS executable isn't available

tests/functional/test_freeze.py

+40
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,46 @@ def test_freeze_git_clone_srcdir(script, tmpdir):
322322
_check_output(result.stdout, expected)
323323

324324

325+
@need_mercurial
326+
def test_freeze_mercurial_clone_srcdir(script, tmpdir):
327+
"""
328+
Test freezing a Mercurial clone where setup.py is in a subdirectory
329+
relative to the repo root and the source code is in a subdirectory
330+
relative to setup.py.
331+
"""
332+
# Returns path to a generated package called "version_pkg"
333+
pkg_version = _create_test_package_with_srcdir(script, vcs='hg')
334+
335+
result = script.run(
336+
'hg', 'clone', pkg_version, 'pip-test-package'
337+
)
338+
repo_dir = script.scratch_path / 'pip-test-package'
339+
result = script.run(
340+
'python', 'setup.py', 'develop',
341+
cwd=repo_dir / 'subdir'
342+
)
343+
result = script.pip('freeze')
344+
expected = textwrap.dedent(
345+
"""
346+
...-e hg+...#egg=version_pkg&subdirectory=subdir
347+
...
348+
"""
349+
).strip()
350+
_check_output(result.stdout, expected)
351+
352+
result = script.pip(
353+
'freeze', '-f', '%s#egg=pip_test_package' % repo_dir
354+
)
355+
expected = textwrap.dedent(
356+
"""
357+
-f %(repo)s#egg=pip_test_package...
358+
-e hg+...#egg=version_pkg&subdirectory=subdir
359+
...
360+
""" % {'repo': repo_dir},
361+
).strip()
362+
_check_output(result.stdout, expected)
363+
364+
325365
@pytest.mark.git
326366
def test_freeze_git_remote(script, tmpdir):
327367
"""

0 commit comments

Comments
 (0)