Skip to content

Commit f935325

Browse files
Merge pull request #252 from acsone/issue-251-sbi
File finders spec compliance, work in subdirectories too fixes #251 fixes #248
2 parents a994678 + 5343f14 commit f935325

File tree

10 files changed

+174
-98
lines changed

10 files changed

+174
-98
lines changed

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ def parse(root):
8181
pip-egg-info = setuptools_scm.hacks:parse_pip_egg_info
8282
8383
[setuptools_scm.files_command]
84-
.hg = setuptools_scm.hg:FILES_COMMAND
85-
.git = setuptools_scm.git_file_finder:find_files
84+
.hg = setuptools_scm.file_finder_hg:hg_find_files
85+
.git = setuptools_scm.file_finder_git:git_find_files
8686
8787
[setuptools_scm.version_scheme]
8888
guess-next-dev = setuptools_scm.version:guess_next_dev_version

setuptools_scm/file_finder.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
3+
4+
def scm_find_files(path, scm_files, scm_dirs):
5+
""" setuptools compatible file finder that follows symlinks
6+
7+
- path: the root directory from which to search
8+
- scm_files: set of scm controlled files
9+
- scm_files: set of scm controlled directories
10+
11+
scm_files and scm_dirs must be absolute with symlinks resolved (realpath),
12+
with normalized case (normcase)
13+
14+
Spec here: http://setuptools.readthedocs.io/en/latest/setuptools.html#\
15+
adding-support-for-revision-control-systems
16+
"""
17+
realpath = os.path.normcase(os.path.realpath(path))
18+
seen = set()
19+
res = []
20+
for dirpath, dirnames, filenames in os.walk(realpath, followlinks=True):
21+
# dirpath with symlinks resolved
22+
realdirpath = os.path.normcase(os.path.realpath(dirpath))
23+
if realdirpath not in scm_dirs or realdirpath in seen:
24+
dirnames[:] = []
25+
continue
26+
for filename in filenames:
27+
# dirpath + filename with symlinks preserved
28+
fullfilename = os.path.join(dirpath, filename)
29+
if os.path.normcase(os.path.realpath(fullfilename)) in scm_files:
30+
res.append(
31+
os.path.join(path, os.path.relpath(fullfilename, path)))
32+
seen.add(realdirpath)
33+
return res

setuptools_scm/file_finder_git.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
import subprocess
3+
import tarfile
4+
5+
from .file_finder import scm_find_files
6+
7+
8+
def _git_toplevel(path):
9+
try:
10+
with open(os.devnull, 'wb') as devnull:
11+
out = subprocess.check_output([
12+
'git', 'rev-parse', '--show-toplevel',
13+
], cwd=(path or '.'), universal_newlines=True, stderr=devnull)
14+
return os.path.normcase(os.path.realpath(out.strip()))
15+
except subprocess.CalledProcessError:
16+
# git returned error, we are not in a git repo
17+
return None
18+
except OSError:
19+
# git command not found, probably
20+
return None
21+
22+
23+
def _git_ls_files_and_dirs(toplevel):
24+
# use git archive instead of git ls-file to honor
25+
# export-ignore git attribute
26+
cmd = ['git', 'archive', '--prefix', toplevel + os.path.sep, 'HEAD']
27+
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=toplevel)
28+
tf = tarfile.open(fileobj=proc.stdout, mode='r|*')
29+
git_files = set()
30+
git_dirs = set([toplevel])
31+
for member in tf.getmembers():
32+
name = os.path.normcase(member.name).replace('/', os.path.sep)
33+
if member.type == tarfile.DIRTYPE:
34+
git_dirs.add(name)
35+
else:
36+
git_files.add(name)
37+
return git_files, git_dirs
38+
39+
40+
def git_find_files(path=''):
41+
toplevel = _git_toplevel(path)
42+
if not toplevel:
43+
return []
44+
git_files, git_dirs = _git_ls_files_and_dirs(toplevel)
45+
return scm_find_files(path, git_files, git_dirs)

setuptools_scm/file_finder_hg.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
import subprocess
3+
4+
from .file_finder import scm_find_files
5+
6+
7+
def _hg_toplevel(path):
8+
try:
9+
with open(os.devnull, 'wb') as devnull:
10+
out = subprocess.check_output([
11+
'hg', 'root',
12+
], cwd=(path or '.'), universal_newlines=True, stderr=devnull)
13+
return os.path.normcase(os.path.realpath(out.strip()))
14+
except subprocess.CalledProcessError:
15+
# hg returned error, we are not in a mercurial repo
16+
return None
17+
except OSError:
18+
# hg command not found, probably
19+
return None
20+
21+
22+
def _hg_ls_files_and_dirs(toplevel):
23+
hg_files = set()
24+
hg_dirs = set([toplevel])
25+
out = subprocess.check_output([
26+
'hg', 'files',
27+
], cwd=toplevel, universal_newlines=True)
28+
for name in out.splitlines():
29+
name = os.path.normcase(name).replace('/', os.path.sep)
30+
fullname = os.path.join(toplevel, name)
31+
hg_files.add(fullname)
32+
dirname = os.path.dirname(fullname)
33+
while len(dirname) > len(toplevel) and dirname not in hg_dirs:
34+
hg_dirs.add(dirname)
35+
dirname = os.path.dirname(dirname)
36+
return hg_files, hg_dirs
37+
38+
39+
def hg_find_files(path=''):
40+
toplevel = _hg_toplevel(path)
41+
if not toplevel:
42+
return []
43+
hg_files, hg_dirs = _hg_ls_files_and_dirs(toplevel)
44+
return scm_find_files(path, hg_files, hg_dirs)

setuptools_scm/git_file_finder.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

setuptools_scm/hg.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from .utils import do, trace, data_from_mime, has_command
33
from .version import meta, tags_to_versions
44

5-
FILES_COMMAND = 'hg locate -I .'
6-
75

86
def _hg_tagdist_normalize_tagcommit(root, tag, dist, node, branch):
97
dirty = node.endswith('+')

setuptools_scm/integration.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
from pkg_resources import iter_entry_points
22

33
from .version import _warn_if_setuptools_outdated
44
from .utils import do
@@ -22,22 +22,14 @@ def version_keyword(dist, keyword, value):
2222
dist.metadata.version = get_version(**value)
2323

2424

25-
def find_files(path='.'):
26-
if not path:
27-
path = '.'
28-
abs = os.path.abspath(path)
29-
ep = next(iter_matching_entrypoints(
30-
abs, 'setuptools_scm.files_command'), None)
31-
if ep:
25+
def find_files(path=''):
26+
for ep in iter_entry_points('setuptools_scm.files_command'):
3227
command = ep.load()
33-
try:
34-
if isinstance(command, str):
35-
return do(ep.load(), path).splitlines()
36-
else:
37-
return command(path)
38-
except Exception:
39-
print("File Finder Failed for %s" % ep)
40-
raise
41-
42-
else:
43-
return []
28+
if isinstance(command, str):
29+
# this technique is deprecated
30+
res = do(ep.load(), path or '.').splitlines()
31+
else:
32+
res = command(path)
33+
if res:
34+
return res
35+
return []

testing/test_git_file_finder.py renamed to testing/test_file_finder.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33

44
import pytest
55

6-
from setuptools_scm.git_file_finder import find_files
7-
8-
9-
@pytest.fixture
10-
def inwd(wd):
11-
wd('git init')
12-
wd('git config user.email [email protected]')
13-
wd('git config user.name "a test"')
14-
wd.add_command = 'git add .'
15-
wd.commit_command = 'git commit -m test-{reason}'
6+
from setuptools_scm.integration import find_files
7+
8+
9+
@pytest.fixture(params=['git', 'hg'])
10+
def inwd(request, wd):
11+
if request.param == 'git':
12+
wd('git init')
13+
wd('git config user.email [email protected]')
14+
wd('git config user.name "a test"')
15+
wd.add_command = 'git add .'
16+
wd.commit_command = 'git commit -m test-{reason}'
17+
elif request.param == 'hg':
18+
wd('hg init')
19+
wd.add_command = 'hg add .'
20+
wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"'
1621
(wd.cwd / 'file1').ensure(file=True)
1722
adir = (wd.cwd / 'adir').ensure(dir=True)
1823
(adir / 'filea').ensure(file=True)
@@ -46,6 +51,15 @@ def test_basic(inwd):
4651
})
4752

4853

54+
def test_whitespace(inwd):
55+
(inwd.cwd / 'adir' / 'space file').ensure(file=True)
56+
inwd.add_and_commit()
57+
assert set(find_files('adir')) == _sep({
58+
'adir/space file',
59+
'adir/filea',
60+
})
61+
62+
4963
def test_case(inwd):
5064
(inwd.cwd / 'CamelFile').ensure(file=True)
5165
(inwd.cwd / 'file2').ensure(file=True)

testing/test_git.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ def test_git_archive_subdirectory(wd):
138138
assert integration.find_files('.') == [opj('.', 'foobar', 'test1.txt')]
139139

140140

141+
@pytest.mark.issue(251)
142+
def test_git_archive_run_from_subdirectory(wd):
143+
wd('mkdir foobar')
144+
wd.write('foobar/test1.txt', 'test')
145+
wd('git add foobar')
146+
wd.commit()
147+
with (wd.cwd / 'foobar').as_cwd():
148+
assert integration.find_files('.') == [opj('.', 'test1.txt')]
149+
150+
141151
def test_git_feature_branch_increments_major(wd):
142152
wd.commit_testfile()
143153
wd("git tag 1.0.0")

testing/test_mercurial.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ def test_archival_to_version(expected, data):
4141
def test_find_files_stop_at_root_hg(wd):
4242
wd.commit_testfile()
4343
wd.cwd.ensure('project/setup.cfg')
44+
# setup.cfg has not been committed
4445
assert integration.find_files(str(wd.cwd / 'project')) == []
46+
# issue 251
47+
wd.add_and_commit()
48+
with (wd.cwd / 'project').as_cwd():
49+
assert integration.find_files() == ['setup.cfg']
4550

4651

4752
# XXX: better tests for tag prefixes

0 commit comments

Comments
 (0)