##// END OF EJS Templates
narrowspec: remove parseserverpatterns() which isn't used anymore...
Yuya Nishihara -
r39592:c8ea5c7e default
parent child Browse files
Show More
@@ -1,244 +1,223 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 match as matchmod,
15 match as matchmod,
16 repository,
16 repository,
17 sparse,
17 sparse,
18 util,
18 util,
19 )
19 )
20
20
21 FILENAME = 'narrowspec'
21 FILENAME = 'narrowspec'
22
22
23 # Pattern prefixes that are allowed in narrow patterns. This list MUST
23 # Pattern prefixes that are allowed in narrow patterns. This list MUST
24 # only contain patterns that are fast and safe to evaluate. Keep in mind
24 # only contain patterns that are fast and safe to evaluate. Keep in mind
25 # that patterns are supplied by clients and executed on remote servers
25 # that patterns are supplied by clients and executed on remote servers
26 # as part of wire protocol commands.
26 # as part of wire protocol commands.
27 VALID_PREFIXES = (
27 VALID_PREFIXES = (
28 b'path:',
28 b'path:',
29 b'rootfilesin:',
29 b'rootfilesin:',
30 )
30 )
31
31
32 def parseserverpatterns(text):
33 """Parses the narrowspec format that's returned by the server."""
34 includepats = set()
35 excludepats = set()
36
37 # We get one entry per line, in the format "<key> <value>".
38 # It's OK for value to contain other spaces.
39 for kp in (l.split(' ', 1) for l in text.splitlines()):
40 if len(kp) != 2:
41 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
42 key = kp[0]
43 pat = kp[1]
44 if key == 'include':
45 includepats.add(pat)
46 elif key == 'exclude':
47 excludepats.add(pat)
48 else:
49 raise error.Abort(_('Invalid key "%s" in server response') % key)
50
51 return includepats, excludepats
52
53 def normalizesplitpattern(kind, pat):
32 def normalizesplitpattern(kind, pat):
54 """Returns the normalized version of a pattern and kind.
33 """Returns the normalized version of a pattern and kind.
55
34
56 Returns a tuple with the normalized kind and normalized pattern.
35 Returns a tuple with the normalized kind and normalized pattern.
57 """
36 """
58 pat = pat.rstrip('/')
37 pat = pat.rstrip('/')
59 _validatepattern(pat)
38 _validatepattern(pat)
60 return kind, pat
39 return kind, pat
61
40
62 def _numlines(s):
41 def _numlines(s):
63 """Returns the number of lines in s, including ending empty lines."""
42 """Returns the number of lines in s, including ending empty lines."""
64 # We use splitlines because it is Unicode-friendly and thus Python 3
43 # We use splitlines because it is Unicode-friendly and thus Python 3
65 # compatible. However, it does not count empty lines at the end, so trick
44 # compatible. However, it does not count empty lines at the end, so trick
66 # it by adding a character at the end.
45 # it by adding a character at the end.
67 return len((s + 'x').splitlines())
46 return len((s + 'x').splitlines())
68
47
69 def _validatepattern(pat):
48 def _validatepattern(pat):
70 """Validates the pattern and aborts if it is invalid.
49 """Validates the pattern and aborts if it is invalid.
71
50
72 Patterns are stored in the narrowspec as newline-separated
51 Patterns are stored in the narrowspec as newline-separated
73 POSIX-style bytestring paths. There's no escaping.
52 POSIX-style bytestring paths. There's no escaping.
74 """
53 """
75
54
76 # We use newlines as separators in the narrowspec file, so don't allow them
55 # We use newlines as separators in the narrowspec file, so don't allow them
77 # in patterns.
56 # in patterns.
78 if _numlines(pat) > 1:
57 if _numlines(pat) > 1:
79 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
58 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
80
59
81 components = pat.split('/')
60 components = pat.split('/')
82 if '.' in components or '..' in components:
61 if '.' in components or '..' in components:
83 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
62 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
84
63
85 def normalizepattern(pattern, defaultkind='path'):
64 def normalizepattern(pattern, defaultkind='path'):
86 """Returns the normalized version of a text-format pattern.
65 """Returns the normalized version of a text-format pattern.
87
66
88 If the pattern has no kind, the default will be added.
67 If the pattern has no kind, the default will be added.
89 """
68 """
90 kind, pat = matchmod._patsplit(pattern, defaultkind)
69 kind, pat = matchmod._patsplit(pattern, defaultkind)
91 return '%s:%s' % normalizesplitpattern(kind, pat)
70 return '%s:%s' % normalizesplitpattern(kind, pat)
92
71
93 def parsepatterns(pats):
72 def parsepatterns(pats):
94 """Parses an iterable of patterns into a typed pattern set.
73 """Parses an iterable of patterns into a typed pattern set.
95
74
96 Patterns are assumed to be ``path:`` if no prefix is present.
75 Patterns are assumed to be ``path:`` if no prefix is present.
97 For safety and performance reasons, only some prefixes are allowed.
76 For safety and performance reasons, only some prefixes are allowed.
98 See ``validatepatterns()``.
77 See ``validatepatterns()``.
99
78
100 This function should be used on patterns that come from the user to
79 This function should be used on patterns that come from the user to
101 normalize and validate them to the internal data structure used for
80 normalize and validate them to the internal data structure used for
102 representing patterns.
81 representing patterns.
103 """
82 """
104 res = {normalizepattern(orig) for orig in pats}
83 res = {normalizepattern(orig) for orig in pats}
105 validatepatterns(res)
84 validatepatterns(res)
106 return res
85 return res
107
86
108 def validatepatterns(pats):
87 def validatepatterns(pats):
109 """Validate that patterns are in the expected data structure and format.
88 """Validate that patterns are in the expected data structure and format.
110
89
111 And that is a set of normalized patterns beginning with ``path:`` or
90 And that is a set of normalized patterns beginning with ``path:`` or
112 ``rootfilesin:``.
91 ``rootfilesin:``.
113
92
114 This function should be used to validate internal data structures
93 This function should be used to validate internal data structures
115 and patterns that are loaded from sources that use the internal,
94 and patterns that are loaded from sources that use the internal,
116 prefixed pattern representation (but can't necessarily be fully trusted).
95 prefixed pattern representation (but can't necessarily be fully trusted).
117 """
96 """
118 if not isinstance(pats, set):
97 if not isinstance(pats, set):
119 raise error.ProgrammingError('narrow patterns should be a set; '
98 raise error.ProgrammingError('narrow patterns should be a set; '
120 'got %r' % pats)
99 'got %r' % pats)
121
100
122 for pat in pats:
101 for pat in pats:
123 if not pat.startswith(VALID_PREFIXES):
102 if not pat.startswith(VALID_PREFIXES):
124 # Use a Mercurial exception because this can happen due to user
103 # Use a Mercurial exception because this can happen due to user
125 # bugs (e.g. manually updating spec file).
104 # bugs (e.g. manually updating spec file).
126 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
105 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
127 hint=_('narrow patterns must begin with one of '
106 hint=_('narrow patterns must begin with one of '
128 'the following: %s') %
107 'the following: %s') %
129 ', '.join(VALID_PREFIXES))
108 ', '.join(VALID_PREFIXES))
130
109
131 def format(includes, excludes):
110 def format(includes, excludes):
132 output = '[include]\n'
111 output = '[include]\n'
133 for i in sorted(includes - excludes):
112 for i in sorted(includes - excludes):
134 output += i + '\n'
113 output += i + '\n'
135 output += '[exclude]\n'
114 output += '[exclude]\n'
136 for e in sorted(excludes):
115 for e in sorted(excludes):
137 output += e + '\n'
116 output += e + '\n'
138 return output
117 return output
139
118
140 def match(root, include=None, exclude=None):
119 def match(root, include=None, exclude=None):
141 if not include:
120 if not include:
142 # Passing empty include and empty exclude to matchmod.match()
121 # Passing empty include and empty exclude to matchmod.match()
143 # gives a matcher that matches everything, so explicitly use
122 # gives a matcher that matches everything, so explicitly use
144 # the nevermatcher.
123 # the nevermatcher.
145 return matchmod.never(root, '')
124 return matchmod.never(root, '')
146 return matchmod.match(root, '', [], include=include or [],
125 return matchmod.match(root, '', [], include=include or [],
147 exclude=exclude or [])
126 exclude=exclude or [])
148
127
149 def load(repo):
128 def load(repo):
150 try:
129 try:
151 spec = repo.svfs.read(FILENAME)
130 spec = repo.svfs.read(FILENAME)
152 except IOError as e:
131 except IOError as e:
153 # Treat "narrowspec does not exist" the same as "narrowspec file exists
132 # Treat "narrowspec does not exist" the same as "narrowspec file exists
154 # and is empty".
133 # and is empty".
155 if e.errno == errno.ENOENT:
134 if e.errno == errno.ENOENT:
156 return set(), set()
135 return set(), set()
157 raise
136 raise
158 # maybe we should care about the profiles returned too
137 # maybe we should care about the profiles returned too
159 includepats, excludepats, profiles = sparse.parseconfig(repo.ui, spec,
138 includepats, excludepats, profiles = sparse.parseconfig(repo.ui, spec,
160 'narrow')
139 'narrow')
161 if profiles:
140 if profiles:
162 raise error.Abort(_("including other spec files using '%include' is not"
141 raise error.Abort(_("including other spec files using '%include' is not"
163 " supported in narrowspec"))
142 " supported in narrowspec"))
164
143
165 validatepatterns(includepats)
144 validatepatterns(includepats)
166 validatepatterns(excludepats)
145 validatepatterns(excludepats)
167
146
168 return includepats, excludepats
147 return includepats, excludepats
169
148
170 def save(repo, includepats, excludepats):
149 def save(repo, includepats, excludepats):
171 validatepatterns(includepats)
150 validatepatterns(includepats)
172 validatepatterns(excludepats)
151 validatepatterns(excludepats)
173 spec = format(includepats, excludepats)
152 spec = format(includepats, excludepats)
174 repo.svfs.write(FILENAME, spec)
153 repo.svfs.write(FILENAME, spec)
175
154
176 def savebackup(repo, backupname):
155 def savebackup(repo, backupname):
177 if repository.NARROW_REQUIREMENT not in repo.requirements:
156 if repository.NARROW_REQUIREMENT not in repo.requirements:
178 return
157 return
179 vfs = repo.vfs
158 vfs = repo.vfs
180 vfs.tryunlink(backupname)
159 vfs.tryunlink(backupname)
181 util.copyfile(repo.svfs.join(FILENAME), vfs.join(backupname), hardlink=True)
160 util.copyfile(repo.svfs.join(FILENAME), vfs.join(backupname), hardlink=True)
182
161
183 def restorebackup(repo, backupname):
162 def restorebackup(repo, backupname):
184 if repository.NARROW_REQUIREMENT not in repo.requirements:
163 if repository.NARROW_REQUIREMENT not in repo.requirements:
185 return
164 return
186 util.rename(repo.vfs.join(backupname), repo.svfs.join(FILENAME))
165 util.rename(repo.vfs.join(backupname), repo.svfs.join(FILENAME))
187
166
188 def clearbackup(repo, backupname):
167 def clearbackup(repo, backupname):
189 if repository.NARROW_REQUIREMENT not in repo.requirements:
168 if repository.NARROW_REQUIREMENT not in repo.requirements:
190 return
169 return
191 repo.vfs.unlink(backupname)
170 repo.vfs.unlink(backupname)
192
171
193 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
172 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
194 r""" Restricts the patterns according to repo settings,
173 r""" Restricts the patterns according to repo settings,
195 results in a logical AND operation
174 results in a logical AND operation
196
175
197 :param req_includes: requested includes
176 :param req_includes: requested includes
198 :param req_excludes: requested excludes
177 :param req_excludes: requested excludes
199 :param repo_includes: repo includes
178 :param repo_includes: repo includes
200 :param repo_excludes: repo excludes
179 :param repo_excludes: repo excludes
201 :return: include patterns, exclude patterns, and invalid include patterns.
180 :return: include patterns, exclude patterns, and invalid include patterns.
202
181
203 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
182 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
204 (set(['f1']), {}, [])
183 (set(['f1']), {}, [])
205 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
184 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
206 (set(['f1']), {}, [])
185 (set(['f1']), {}, [])
207 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
186 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
208 (set(['f1/fc1']), {}, [])
187 (set(['f1/fc1']), {}, [])
209 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
188 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
210 ([], set(['path:.']), [])
189 ([], set(['path:.']), [])
211 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
190 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
212 (set(['f2/fc2']), {}, [])
191 (set(['f2/fc2']), {}, [])
213 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
192 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
214 ([], set(['path:.']), [])
193 ([], set(['path:.']), [])
215 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
194 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
216 (set(['f1/$non_exitent_var']), {}, [])
195 (set(['f1/$non_exitent_var']), {}, [])
217 """
196 """
218 res_excludes = set(req_excludes)
197 res_excludes = set(req_excludes)
219 res_excludes.update(repo_excludes)
198 res_excludes.update(repo_excludes)
220 invalid_includes = []
199 invalid_includes = []
221 if not req_includes:
200 if not req_includes:
222 res_includes = set(repo_includes)
201 res_includes = set(repo_includes)
223 elif 'path:.' not in repo_includes:
202 elif 'path:.' not in repo_includes:
224 res_includes = []
203 res_includes = []
225 for req_include in req_includes:
204 for req_include in req_includes:
226 req_include = util.expandpath(util.normpath(req_include))
205 req_include = util.expandpath(util.normpath(req_include))
227 if req_include in repo_includes:
206 if req_include in repo_includes:
228 res_includes.append(req_include)
207 res_includes.append(req_include)
229 continue
208 continue
230 valid = False
209 valid = False
231 for repo_include in repo_includes:
210 for repo_include in repo_includes:
232 if req_include.startswith(repo_include + '/'):
211 if req_include.startswith(repo_include + '/'):
233 valid = True
212 valid = True
234 res_includes.append(req_include)
213 res_includes.append(req_include)
235 break
214 break
236 if not valid:
215 if not valid:
237 invalid_includes.append(req_include)
216 invalid_includes.append(req_include)
238 if len(res_includes) == 0:
217 if len(res_includes) == 0:
239 res_excludes = {'path:.'}
218 res_excludes = {'path:.'}
240 else:
219 else:
241 res_includes = set(res_includes)
220 res_includes = set(res_includes)
242 else:
221 else:
243 res_includes = set(req_includes)
222 res_includes = set(req_includes)
244 return res_includes, res_excludes, invalid_includes
223 return res_includes, res_excludes, invalid_includes
General Comments 0
You need to be logged in to leave comments. Login now