##// END OF EJS Templates
narrow: remove dependency from narrowspec module to hg module...
Martin von Zweigbergk -
r36488:d851951b default
parent child Browse files
Show More
@@ -1,110 +1,116 b''
1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from mercurial import (
10 from mercurial import (
11 bundlerepo,
11 bundlerepo,
12 changegroup,
12 changegroup,
13 hg,
13 hg,
14 localrepo,
14 localrepo,
15 match as matchmod,
15 match as matchmod,
16 narrowspec,
16 narrowspec,
17 scmutil,
17 scmutil,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 narrowrevlog,
21 narrowrevlog,
22 )
22 )
23
23
24 def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
24 def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
25 orig(sourcerepo, destrepo, **kwargs)
25 orig(sourcerepo, destrepo, **kwargs)
26 if changegroup.NARROW_REQUIREMENT in sourcerepo.requirements:
26 if changegroup.NARROW_REQUIREMENT in sourcerepo.requirements:
27 with destrepo.wlock():
27 with destrepo.wlock():
28 with destrepo.vfs('shared', 'a') as fp:
28 with destrepo.vfs('shared', 'a') as fp:
29 fp.write(narrowspec.FILENAME + '\n')
29 fp.write(narrowspec.FILENAME + '\n')
30
30
31 def unsharenarrowspec(orig, ui, repo, repopath):
31 def unsharenarrowspec(orig, ui, repo, repopath):
32 if (changegroup.NARROW_REQUIREMENT in repo.requirements
32 if (changegroup.NARROW_REQUIREMENT in repo.requirements
33 and repo.path == repopath and repo.shared()):
33 and repo.path == repopath and repo.shared()):
34 srcrepo = hg.sharedreposource(repo)
34 srcrepo = hg.sharedreposource(repo)
35 with srcrepo.vfs(narrowspec.FILENAME) as f:
35 with srcrepo.vfs(narrowspec.FILENAME) as f:
36 spec = f.read()
36 spec = f.read()
37 with repo.vfs(narrowspec.FILENAME, 'w') as f:
37 with repo.vfs(narrowspec.FILENAME, 'w') as f:
38 f.write(spec)
38 f.write(spec)
39 return orig(ui, repo, repopath)
39 return orig(ui, repo, repopath)
40
40
41 def wraprepo(repo):
41 def wraprepo(repo):
42 """Enables narrow clone functionality on a single local repository."""
42 """Enables narrow clone functionality on a single local repository."""
43
43
44 cacheprop = localrepo.storecache
44 cacheprop = localrepo.storecache
45 if isinstance(repo, bundlerepo.bundlerepository):
45 if isinstance(repo, bundlerepo.bundlerepository):
46 # We have to use a different caching property decorator for
46 # We have to use a different caching property decorator for
47 # bundlerepo because storecache blows up in strange ways on a
47 # bundlerepo because storecache blows up in strange ways on a
48 # bundlerepo. Fortunately, there's no risk of data changing in
48 # bundlerepo. Fortunately, there's no risk of data changing in
49 # a bundlerepo.
49 # a bundlerepo.
50 cacheprop = lambda name: localrepo.unfilteredpropertycache
50 cacheprop = lambda name: localrepo.unfilteredpropertycache
51
51
52 class narrowrepository(repo.__class__):
52 class narrowrepository(repo.__class__):
53
53
54 def _constructmanifest(self):
54 def _constructmanifest(self):
55 manifest = super(narrowrepository, self)._constructmanifest()
55 manifest = super(narrowrepository, self)._constructmanifest()
56 narrowrevlog.makenarrowmanifestrevlog(manifest, repo)
56 narrowrevlog.makenarrowmanifestrevlog(manifest, repo)
57 return manifest
57 return manifest
58
58
59 @cacheprop('00manifest.i')
59 @cacheprop('00manifest.i')
60 def manifestlog(self):
60 def manifestlog(self):
61 mfl = super(narrowrepository, self).manifestlog
61 mfl = super(narrowrepository, self).manifestlog
62 narrowrevlog.makenarrowmanifestlog(mfl, self)
62 narrowrevlog.makenarrowmanifestlog(mfl, self)
63 return mfl
63 return mfl
64
64
65 def file(self, f):
65 def file(self, f):
66 fl = super(narrowrepository, self).file(f)
66 fl = super(narrowrepository, self).file(f)
67 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
67 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
68 return fl
68 return fl
69
69
70 @localrepo.repofilecache(narrowspec.FILENAME)
70 @localrepo.repofilecache(narrowspec.FILENAME)
71 def narrowpats(self):
71 def narrowpats(self):
72 """matcher patterns for this repository's narrowspec
72 """matcher patterns for this repository's narrowspec
73
73
74 A tuple of (includes, excludes).
74 A tuple of (includes, excludes).
75 """
75 """
76 return narrowspec.load(self)
76 source = self
77 if self.shared():
78 source = hg.sharedreposource(self)
79 return narrowspec.load(source)
77
80
78 @localrepo.repofilecache(narrowspec.FILENAME)
81 @localrepo.repofilecache(narrowspec.FILENAME)
79 def _narrowmatch(self):
82 def _narrowmatch(self):
80 if changegroup.NARROW_REQUIREMENT not in self.requirements:
83 if changegroup.NARROW_REQUIREMENT not in self.requirements:
81 return matchmod.always(self.root, '')
84 return matchmod.always(self.root, '')
82 include, exclude = self.narrowpats
85 include, exclude = self.narrowpats
83 return narrowspec.match(self.root, include=include, exclude=exclude)
86 return narrowspec.match(self.root, include=include, exclude=exclude)
84
87
85 # TODO(martinvonz): make this property-like instead?
88 # TODO(martinvonz): make this property-like instead?
86 def narrowmatch(self):
89 def narrowmatch(self):
87 return self._narrowmatch
90 return self._narrowmatch
88
91
89 def setnarrowpats(self, newincludes, newexcludes):
92 def setnarrowpats(self, newincludes, newexcludes):
90 narrowspec.save(self, newincludes, newexcludes)
93 target = self
94 if self.shared():
95 target = hg.sharedreposource(self)
96 narrowspec.save(target, newincludes, newexcludes)
91 self.invalidate(clearfilecache=True)
97 self.invalidate(clearfilecache=True)
92
98
93 # I'm not sure this is the right place to do this filter.
99 # I'm not sure this is the right place to do this filter.
94 # context._manifestmatches() would probably be better, or perhaps
100 # context._manifestmatches() would probably be better, or perhaps
95 # move it to a later place, in case some of the callers do want to know
101 # move it to a later place, in case some of the callers do want to know
96 # which directories changed. This seems to work for now, though.
102 # which directories changed. This seems to work for now, though.
97 def status(self, *args, **kwargs):
103 def status(self, *args, **kwargs):
98 s = super(narrowrepository, self).status(*args, **kwargs)
104 s = super(narrowrepository, self).status(*args, **kwargs)
99 narrowmatch = self.narrowmatch()
105 narrowmatch = self.narrowmatch()
100 modified = list(filter(narrowmatch, s.modified))
106 modified = list(filter(narrowmatch, s.modified))
101 added = list(filter(narrowmatch, s.added))
107 added = list(filter(narrowmatch, s.added))
102 removed = list(filter(narrowmatch, s.removed))
108 removed = list(filter(narrowmatch, s.removed))
103 deleted = list(filter(narrowmatch, s.deleted))
109 deleted = list(filter(narrowmatch, s.deleted))
104 unknown = list(filter(narrowmatch, s.unknown))
110 unknown = list(filter(narrowmatch, s.unknown))
105 ignored = list(filter(narrowmatch, s.ignored))
111 ignored = list(filter(narrowmatch, s.ignored))
106 clean = list(filter(narrowmatch, s.clean))
112 clean = list(filter(narrowmatch, s.clean))
107 return scmutil.status(modified, added, removed, deleted, unknown,
113 return scmutil.status(modified, added, removed, deleted, unknown,
108 ignored, clean)
114 ignored, clean)
109
115
110 repo.__class__ = narrowrepository
116 repo.__class__ = narrowrepository
@@ -1,204 +1,199 b''
1 # narrowspec.py - methods for working with a narrow view of a repository
1 # narrowspec.py - methods for working with a narrow view of a repository
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 hg,
16 match as matchmod,
15 match as matchmod,
17 util,
16 util,
18 )
17 )
19
18
20 FILENAME = 'narrowspec'
19 FILENAME = 'narrowspec'
21
20
22 def _parsestoredpatterns(text):
21 def _parsestoredpatterns(text):
23 """Parses the narrowspec format that's stored on disk."""
22 """Parses the narrowspec format that's stored on disk."""
24 patlist = None
23 patlist = None
25 includepats = []
24 includepats = []
26 excludepats = []
25 excludepats = []
27 for l in text.splitlines():
26 for l in text.splitlines():
28 if l == '[includes]':
27 if l == '[includes]':
29 if patlist is None:
28 if patlist is None:
30 patlist = includepats
29 patlist = includepats
31 else:
30 else:
32 raise error.Abort(_('narrowspec includes section must appear '
31 raise error.Abort(_('narrowspec includes section must appear '
33 'at most once, before excludes'))
32 'at most once, before excludes'))
34 elif l == '[excludes]':
33 elif l == '[excludes]':
35 if patlist is not excludepats:
34 if patlist is not excludepats:
36 patlist = excludepats
35 patlist = excludepats
37 else:
36 else:
38 raise error.Abort(_('narrowspec excludes section must appear '
37 raise error.Abort(_('narrowspec excludes section must appear '
39 'at most once'))
38 'at most once'))
40 else:
39 else:
41 patlist.append(l)
40 patlist.append(l)
42
41
43 return set(includepats), set(excludepats)
42 return set(includepats), set(excludepats)
44
43
45 def parseserverpatterns(text):
44 def parseserverpatterns(text):
46 """Parses the narrowspec format that's returned by the server."""
45 """Parses the narrowspec format that's returned by the server."""
47 includepats = set()
46 includepats = set()
48 excludepats = set()
47 excludepats = set()
49
48
50 # We get one entry per line, in the format "<key> <value>".
49 # We get one entry per line, in the format "<key> <value>".
51 # It's OK for value to contain other spaces.
50 # It's OK for value to contain other spaces.
52 for kp in (l.split(' ', 1) for l in text.splitlines()):
51 for kp in (l.split(' ', 1) for l in text.splitlines()):
53 if len(kp) != 2:
52 if len(kp) != 2:
54 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
53 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
55 key = kp[0]
54 key = kp[0]
56 pat = kp[1]
55 pat = kp[1]
57 if key == 'include':
56 if key == 'include':
58 includepats.add(pat)
57 includepats.add(pat)
59 elif key == 'exclude':
58 elif key == 'exclude':
60 excludepats.add(pat)
59 excludepats.add(pat)
61 else:
60 else:
62 raise error.Abort(_('Invalid key "%s" in server response') % key)
61 raise error.Abort(_('Invalid key "%s" in server response') % key)
63
62
64 return includepats, excludepats
63 return includepats, excludepats
65
64
66 def normalizesplitpattern(kind, pat):
65 def normalizesplitpattern(kind, pat):
67 """Returns the normalized version of a pattern and kind.
66 """Returns the normalized version of a pattern and kind.
68
67
69 Returns a tuple with the normalized kind and normalized pattern.
68 Returns a tuple with the normalized kind and normalized pattern.
70 """
69 """
71 pat = pat.rstrip('/')
70 pat = pat.rstrip('/')
72 _validatepattern(pat)
71 _validatepattern(pat)
73 return kind, pat
72 return kind, pat
74
73
75 def _numlines(s):
74 def _numlines(s):
76 """Returns the number of lines in s, including ending empty lines."""
75 """Returns the number of lines in s, including ending empty lines."""
77 # We use splitlines because it is Unicode-friendly and thus Python 3
76 # We use splitlines because it is Unicode-friendly and thus Python 3
78 # compatible. However, it does not count empty lines at the end, so trick
77 # compatible. However, it does not count empty lines at the end, so trick
79 # it by adding a character at the end.
78 # it by adding a character at the end.
80 return len((s + 'x').splitlines())
79 return len((s + 'x').splitlines())
81
80
82 def _validatepattern(pat):
81 def _validatepattern(pat):
83 """Validates the pattern and aborts if it is invalid.
82 """Validates the pattern and aborts if it is invalid.
84
83
85 Patterns are stored in the narrowspec as newline-separated
84 Patterns are stored in the narrowspec as newline-separated
86 POSIX-style bytestring paths. There's no escaping.
85 POSIX-style bytestring paths. There's no escaping.
87 """
86 """
88
87
89 # We use newlines as separators in the narrowspec file, so don't allow them
88 # We use newlines as separators in the narrowspec file, so don't allow them
90 # in patterns.
89 # in patterns.
91 if _numlines(pat) > 1:
90 if _numlines(pat) > 1:
92 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
91 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
93
92
94 components = pat.split('/')
93 components = pat.split('/')
95 if '.' in components or '..' in components:
94 if '.' in components or '..' in components:
96 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
95 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
97
96
98 def normalizepattern(pattern, defaultkind='path'):
97 def normalizepattern(pattern, defaultkind='path'):
99 """Returns the normalized version of a text-format pattern.
98 """Returns the normalized version of a text-format pattern.
100
99
101 If the pattern has no kind, the default will be added.
100 If the pattern has no kind, the default will be added.
102 """
101 """
103 kind, pat = matchmod._patsplit(pattern, defaultkind)
102 kind, pat = matchmod._patsplit(pattern, defaultkind)
104 return '%s:%s' % normalizesplitpattern(kind, pat)
103 return '%s:%s' % normalizesplitpattern(kind, pat)
105
104
106 def parsepatterns(pats):
105 def parsepatterns(pats):
107 """Parses a list of patterns into a typed pattern set."""
106 """Parses a list of patterns into a typed pattern set."""
108 return set(normalizepattern(p) for p in pats)
107 return set(normalizepattern(p) for p in pats)
109
108
110 def format(includes, excludes):
109 def format(includes, excludes):
111 output = '[includes]\n'
110 output = '[includes]\n'
112 for i in sorted(includes - excludes):
111 for i in sorted(includes - excludes):
113 output += i + '\n'
112 output += i + '\n'
114 output += '[excludes]\n'
113 output += '[excludes]\n'
115 for e in sorted(excludes):
114 for e in sorted(excludes):
116 output += e + '\n'
115 output += e + '\n'
117 return output
116 return output
118
117
119 def match(root, include=None, exclude=None):
118 def match(root, include=None, exclude=None):
120 if not include:
119 if not include:
121 # Passing empty include and empty exclude to matchmod.match()
120 # Passing empty include and empty exclude to matchmod.match()
122 # gives a matcher that matches everything, so explicitly use
121 # gives a matcher that matches everything, so explicitly use
123 # the nevermatcher.
122 # the nevermatcher.
124 return matchmod.never(root, '')
123 return matchmod.never(root, '')
125 return matchmod.match(root, '', [], include=include or [],
124 return matchmod.match(root, '', [], include=include or [],
126 exclude=exclude or [])
125 exclude=exclude or [])
127
126
128 def needsexpansion(includes):
127 def needsexpansion(includes):
129 return [i for i in includes if i.startswith('include:')]
128 return [i for i in includes if i.startswith('include:')]
130
129
131 def load(repo):
130 def load(repo):
132 if repo.shared():
133 repo = hg.sharedreposource(repo)
134 try:
131 try:
135 spec = repo.vfs.read(FILENAME)
132 spec = repo.vfs.read(FILENAME)
136 except IOError as e:
133 except IOError as e:
137 # Treat "narrowspec does not exist" the same as "narrowspec file exists
134 # Treat "narrowspec does not exist" the same as "narrowspec file exists
138 # and is empty".
135 # and is empty".
139 if e.errno == errno.ENOENT:
136 if e.errno == errno.ENOENT:
140 # Without this the next call to load will use the cached
137 # Without this the next call to load will use the cached
141 # non-existence of the file, which can cause some odd issues.
138 # non-existence of the file, which can cause some odd issues.
142 repo.invalidate(clearfilecache=True)
139 repo.invalidate(clearfilecache=True)
143 return set(), set()
140 return set(), set()
144 raise
141 raise
145 return _parsestoredpatterns(spec)
142 return _parsestoredpatterns(spec)
146
143
147 def save(repo, includepats, excludepats):
144 def save(repo, includepats, excludepats):
148 spec = format(includepats, excludepats)
145 spec = format(includepats, excludepats)
149 if repo.shared():
150 repo = hg.sharedreposource(repo)
151 repo.vfs.write(FILENAME, spec)
146 repo.vfs.write(FILENAME, spec)
152
147
153 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
148 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
154 r""" Restricts the patterns according to repo settings,
149 r""" Restricts the patterns according to repo settings,
155 results in a logical AND operation
150 results in a logical AND operation
156
151
157 :param req_includes: requested includes
152 :param req_includes: requested includes
158 :param req_excludes: requested excludes
153 :param req_excludes: requested excludes
159 :param repo_includes: repo includes
154 :param repo_includes: repo includes
160 :param repo_excludes: repo excludes
155 :param repo_excludes: repo excludes
161 :return: include patterns, exclude patterns, and invalid include patterns.
156 :return: include patterns, exclude patterns, and invalid include patterns.
162
157
163 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
158 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
164 (set(['f1']), {}, [])
159 (set(['f1']), {}, [])
165 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
160 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
166 (set(['f1']), {}, [])
161 (set(['f1']), {}, [])
167 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
162 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
168 (set(['f1/fc1']), {}, [])
163 (set(['f1/fc1']), {}, [])
169 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
164 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
170 ([], set(['path:.']), [])
165 ([], set(['path:.']), [])
171 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
166 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
172 (set(['f2/fc2']), {}, [])
167 (set(['f2/fc2']), {}, [])
173 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
168 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
174 ([], set(['path:.']), [])
169 ([], set(['path:.']), [])
175 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
170 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
176 (set(['f1/$non_exitent_var']), {}, [])
171 (set(['f1/$non_exitent_var']), {}, [])
177 """
172 """
178 res_excludes = set(req_excludes)
173 res_excludes = set(req_excludes)
179 res_excludes.update(repo_excludes)
174 res_excludes.update(repo_excludes)
180 invalid_includes = []
175 invalid_includes = []
181 if not req_includes:
176 if not req_includes:
182 res_includes = set(repo_includes)
177 res_includes = set(repo_includes)
183 elif 'path:.' not in repo_includes:
178 elif 'path:.' not in repo_includes:
184 res_includes = []
179 res_includes = []
185 for req_include in req_includes:
180 for req_include in req_includes:
186 req_include = util.expandpath(util.normpath(req_include))
181 req_include = util.expandpath(util.normpath(req_include))
187 if req_include in repo_includes:
182 if req_include in repo_includes:
188 res_includes.append(req_include)
183 res_includes.append(req_include)
189 continue
184 continue
190 valid = False
185 valid = False
191 for repo_include in repo_includes:
186 for repo_include in repo_includes:
192 if req_include.startswith(repo_include + '/'):
187 if req_include.startswith(repo_include + '/'):
193 valid = True
188 valid = True
194 res_includes.append(req_include)
189 res_includes.append(req_include)
195 break
190 break
196 if not valid:
191 if not valid:
197 invalid_includes.append(req_include)
192 invalid_includes.append(req_include)
198 if len(res_includes) == 0:
193 if len(res_includes) == 0:
199 res_excludes = {'path:.'}
194 res_excludes = {'path:.'}
200 else:
195 else:
201 res_includes = set(res_includes)
196 res_includes = set(res_includes)
202 else:
197 else:
203 res_includes = set(req_includes)
198 res_includes = set(req_includes)
204 return res_includes, res_excludes, invalid_includes
199 return res_includes, res_excludes, invalid_includes
General Comments 0
You need to be logged in to leave comments. Login now