Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 9e72a91

Browse files
committed
Ignore symlinks. You can't move them, and we don't modify them.
Summary: I don't know that this is strictly necessary: in practice what seems to happen when you modify a file that's actually a symlink is that we modify the underlying file (that is, `open(f, 'w')` opens the file that the symlink points to, and leaves the actual link untouched). And when we try to modify the file the second time, under its new name, then all the codemods have already been applied via the symlink so it's a noop, and all is good. But I don't know if that's posix standard, and anyway it seems confusing. Now we notice symlinks and just skip over them. Test Plan: make test Reviewers: benkraft Subscribers: davidflanagan, carter Differential Revision: https://phabricator.khanacademy.org/D41041
1 parent 5ceeb27 commit 9e72a91

5 files changed

Lines changed: 61 additions & 15 deletions

File tree

inputs.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@
3030

3131

3232
def _expand_and_normalize_one(project_root, old_fullname, new_fullname,
33-
path_filter=khodemod.default_path_filter()):
33+
path_filter=None):
3434
"""See expand_and_normalize.__doc__."""
3535
def filename_for(mod):
3636
return os.path.join(project_root, util.filename_for_module_name(mod))
3737

38-
def _assert_exists(module, error_prefix):
39-
if not os.path.exists(filename_for(module)):
40-
raise ValueError("%s: %s not found"
41-
% (error_prefix, filename_for(module)))
38+
def _assert_exists_as_file(module, error_prefix):
39+
fname = filename_for(module)
40+
if not os.path.exists(fname):
41+
raise ValueError("%s: %s not found" % (error_prefix, fname))
42+
if os.path.islink(fname):
43+
raise ValueError("%s: %s is a symlink" % (error_prefix, fname))
4244

4345
def _normalize_fullname_and_get_type(fullname):
4446
# Check the cases that fullname is a file or a directory.
@@ -70,7 +72,11 @@ def _normalize_fullname_and_get_type(fullname):
7072
def _modules_under(package_name):
7173
"""Yield module-names relative to package_name-root."""
7274
package_dir = os.path.dirname(filename_for(package_name + '.__init__'))
73-
for path in khodemod.resolve_paths(path_filter, root=package_dir):
75+
if path_filter is None:
76+
this_path_filter = khodemod.default_path_filter(package_dir)
77+
else:
78+
this_path_filter = path_filter
79+
for path in khodemod.resolve_paths(this_path_filter, root=package_dir):
7480
yield util.module_name_for_filename(path)
7581

7682
(old_fullname, old_type) = _normalize_fullname_and_get_type(old_fullname)
@@ -85,7 +91,7 @@ def _modules_under(package_name):
8591

8692
if old_type == "symbol":
8793
(module, symbol) = old_fullname.rsplit('.', 1)
88-
_assert_exists(module, "Cannot move %s" % old_fullname)
94+
_assert_exists_as_file(module, "Cannot move %s" % old_fullname)
8995

9096
# TODO(csilvers): check that the 2nd element of the return-value
9197
# doesn't refer to a symbol that already exists.
@@ -107,7 +113,7 @@ def _modules_under(package_name):
107113
yield (old_fullname, '%s.%s' % (new_fullname, symbol), True)
108114

109115
elif old_type == "module":
110-
_assert_exists(old_fullname, "Cannot move %s" % old_fullname)
116+
_assert_exists_as_file(old_fullname, "Cannot move %s" % old_fullname)
111117
if new_type == "symbol":
112118
raise ValueError("Cannot move a module '%s' to a symbol (%s)"
113119
% (old_fullname, new_fullname))
@@ -129,8 +135,8 @@ def _modules_under(package_name):
129135
yield (old_fullname, new_fullname, False)
130136

131137
elif old_type == "package":
132-
_assert_exists(old_fullname + '.__init__',
133-
"Cannot move %s" % old_fullname)
138+
_assert_exists_as_file(old_fullname + '.__init__',
139+
"Cannot move %s" % old_fullname)
134140
if new_type in ("symbol", "module"):
135141
raise ValueError("Cannot move a package '%s' into a %s (%s)"
136142
% (old_fullname, new_type, new_fullname))
@@ -162,7 +168,7 @@ def _modules_under(package_name):
162168

163169

164170
def expand_and_normalize(project_root, old_fullnames, new_fullname,
165-
path_filter=khodemod.default_path_filter()):
171+
path_filter=None):
166172
"""Return a list of old-new-info triples that effect the requested rename.
167173
168174
In the simple case old_fullname is a module and new_fullname is a

khodemod.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ def dotfiles_path_filter():
158158
for part in os.path.split(path))
159159

160160

161+
def symlink_path_filter(root):
162+
return lambda path: not os.path.islink(os.path.join(root, path))
163+
164+
161165
def exclude_paths_filter(exclude_paths):
162166
return lambda path: not any(part in exclude_paths
163167
for part in path.split(os.path.sep))
@@ -167,12 +171,14 @@ def and_filters(filters):
167171
return lambda item: all(f(item) for f in filters)
168172

169173

170-
def default_path_filter(extensions=DEFAULT_EXTENSIONS,
174+
def default_path_filter(root,
175+
extensions=DEFAULT_EXTENSIONS,
171176
include_extensionless=False,
172177
exclude_paths=DEFAULT_EXCLUDE_PATHS):
173178
return and_filters([
174179
extensions_path_filter(extensions, include_extensionless),
175180
dotfiles_path_filter(),
181+
symlink_path_filter(root),
176182
exclude_paths_filter(exclude_paths),
177183
])
178184

@@ -377,9 +383,10 @@ def run_suggestor_on_files(self, suggestor, filenames, root='.'):
377383
for filename in self.progress_bar(filenames):
378384
self._run_suggestor_on_file(suggestor, filename, root)
379385

380-
def run_suggestor(self, suggestor,
381-
path_filter=default_path_filter(), root='.'):
386+
def run_suggestor(self, suggestor, path_filter=None, root='.'):
382387
"""Run the suggestor on all files matching the path_filter."""
388+
if path_filter is None:
389+
path_filter = default_path_filter(root)
383390
self.run_suggestor_on_files(
384391
suggestor, resolve_paths(path_filter, root), root)
385392

test_inputs.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import
22

3+
import os
34
import unittest
45

56
import inputs
@@ -147,6 +148,26 @@ def test_symbol_to_existing_symbol(self):
147148
"'bar' already defines a symbol named 'myfunc'.")
148149
self.assert_fails('foo.myfunc', 'bar', error)
149150

151+
def test_symlink_for_file(self):
152+
self.write_file('bar.py', 'def myfunc(): return 4\n')
153+
os.symlink('bar.py', self.join('sym.py'))
154+
error = 'Cannot move sym: %s is a symlink' % self.join('sym.py')
155+
self.assert_fails('sym', 'newdir', error)
156+
157+
def test_symlink_for_symbol(self):
158+
self.write_file('bar.py', 'def myfunc(): return 4\n')
159+
os.symlink('bar.py', self.join('sym.py'))
160+
error = 'Cannot move sym.myfunc: %s is a symlink' % self.join('sym.py')
161+
self.assert_fails('sym.myfunc', 'newdir', error)
162+
163+
def test_symlink_for_package(self):
164+
self.write_file('dir_with_symlink/bar.py', 'def myfunc(): return 4\n')
165+
self.write_file('dir_with_symlink/__init__.py', '')
166+
os.symlink('bar.py', self.join('dir_with_symlink/sym.py'))
167+
self._assert('dir_with_symlink', 'newdir',
168+
[('dir_with_symlink.bar', 'newdir.bar', False),
169+
('dir_with_symlink.__init__', 'newdir.__init__', False)])
170+
150171

151172
class ManyInputsTest(test_slicker.TestBase):
152173
def setUp(self):

test_khodemod.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@ def test_resolve_paths(self):
1818

1919
self.assertItemsEqual(
2020
khodemod.resolve_paths(
21-
khodemod.default_path_filter(),
21+
khodemod.default_path_filter(self.tmpdir),
2222
root=self.tmpdir),
2323
['foo.py', 'bar/baz.py', 'build/qux.py'])
2424

2525
self.assertItemsEqual(
2626
khodemod.resolve_paths(
2727
khodemod.default_path_filter(
28+
self.tmpdir,
2829
exclude_paths=('genfiles', 'build')),
2930
root=self.tmpdir),
3031
['foo.py', 'bar/baz.py'])
3132

3233
self.assertItemsEqual(
3334
khodemod.resolve_paths(
3435
khodemod.default_path_filter(
36+
self.tmpdir,
3537
extensions=('js', 'css'), include_extensionless=True),
3638
root=self.tmpdir),
3739
['foo_extensionless_py', 'foo.js', 'foo.css'])

test_slicker.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,16 @@ def test_repeated_name(self):
11211121
'repeated_name',
11221122
'foo.foo', 'bar.foo.foo')
11231123

1124+
def test_ignore_uses_in_symlink(self):
1125+
self.create_module('foo')
1126+
os.symlink('simple_in.py', self.join('sym.py'))
1127+
self.run_test(
1128+
'simple',
1129+
'foo.some_function', 'bar.new_name')
1130+
# We shouldn't have rewritten the symlink, so it should still be
1131+
# a symlink.
1132+
self.assertTrue(os.path.islink(self.join('sym.py')))
1133+
11241134

11251135
class AliasTest(TestBase):
11261136
def assert_(self, old_module, new_module, alias,

0 commit comments

Comments
 (0)