##// END OF EJS Templates
narrowspec: use sparse.parseconfig() to parse narrowspec file (BC)...
Pulkit Goyal -
r39112:f64ebe7d default
parent child Browse files
Show More
@@ -1,209 +1,216 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 sparse,
16 util,
17 util,
17 )
18 )
18
19
19 FILENAME = 'narrowspec'
20 FILENAME = 'narrowspec'
20
21
21 def _parsestoredpatterns(text):
22 def _parsestoredpatterns(text):
22 """Parses the narrowspec format that's stored on disk."""
23 """Parses the narrowspec format that's stored on disk."""
23 patlist = None
24 patlist = None
24 includepats = []
25 includepats = []
25 excludepats = []
26 excludepats = []
26 for l in text.splitlines():
27 for l in text.splitlines():
27 if l == '[includes]':
28 if l == '[includes]':
28 if patlist is None:
29 if patlist is None:
29 patlist = includepats
30 patlist = includepats
30 else:
31 else:
31 raise error.Abort(_('narrowspec includes section must appear '
32 raise error.Abort(_('narrowspec includes section must appear '
32 'at most once, before excludes'))
33 'at most once, before excludes'))
33 elif l == '[excludes]':
34 elif l == '[excludes]':
34 if patlist is not excludepats:
35 if patlist is not excludepats:
35 patlist = excludepats
36 patlist = excludepats
36 else:
37 else:
37 raise error.Abort(_('narrowspec excludes section must appear '
38 raise error.Abort(_('narrowspec excludes section must appear '
38 'at most once'))
39 'at most once'))
39 else:
40 else:
40 patlist.append(l)
41 patlist.append(l)
41
42
42 return set(includepats), set(excludepats)
43 return set(includepats), set(excludepats)
43
44
44 def parseserverpatterns(text):
45 def parseserverpatterns(text):
45 """Parses the narrowspec format that's returned by the server."""
46 """Parses the narrowspec format that's returned by the server."""
46 includepats = set()
47 includepats = set()
47 excludepats = set()
48 excludepats = set()
48
49
49 # We get one entry per line, in the format "<key> <value>".
50 # We get one entry per line, in the format "<key> <value>".
50 # It's OK for value to contain other spaces.
51 # It's OK for value to contain other spaces.
51 for kp in (l.split(' ', 1) for l in text.splitlines()):
52 for kp in (l.split(' ', 1) for l in text.splitlines()):
52 if len(kp) != 2:
53 if len(kp) != 2:
53 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
54 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
54 key = kp[0]
55 key = kp[0]
55 pat = kp[1]
56 pat = kp[1]
56 if key == 'include':
57 if key == 'include':
57 includepats.add(pat)
58 includepats.add(pat)
58 elif key == 'exclude':
59 elif key == 'exclude':
59 excludepats.add(pat)
60 excludepats.add(pat)
60 else:
61 else:
61 raise error.Abort(_('Invalid key "%s" in server response') % key)
62 raise error.Abort(_('Invalid key "%s" in server response') % key)
62
63
63 return includepats, excludepats
64 return includepats, excludepats
64
65
65 def normalizesplitpattern(kind, pat):
66 def normalizesplitpattern(kind, pat):
66 """Returns the normalized version of a pattern and kind.
67 """Returns the normalized version of a pattern and kind.
67
68
68 Returns a tuple with the normalized kind and normalized pattern.
69 Returns a tuple with the normalized kind and normalized pattern.
69 """
70 """
70 pat = pat.rstrip('/')
71 pat = pat.rstrip('/')
71 _validatepattern(pat)
72 _validatepattern(pat)
72 return kind, pat
73 return kind, pat
73
74
74 def _numlines(s):
75 def _numlines(s):
75 """Returns the number of lines in s, including ending empty lines."""
76 """Returns the number of lines in s, including ending empty lines."""
76 # We use splitlines because it is Unicode-friendly and thus Python 3
77 # We use splitlines because it is Unicode-friendly and thus Python 3
77 # compatible. However, it does not count empty lines at the end, so trick
78 # compatible. However, it does not count empty lines at the end, so trick
78 # it by adding a character at the end.
79 # it by adding a character at the end.
79 return len((s + 'x').splitlines())
80 return len((s + 'x').splitlines())
80
81
81 def _validatepattern(pat):
82 def _validatepattern(pat):
82 """Validates the pattern and aborts if it is invalid.
83 """Validates the pattern and aborts if it is invalid.
83
84
84 Patterns are stored in the narrowspec as newline-separated
85 Patterns are stored in the narrowspec as newline-separated
85 POSIX-style bytestring paths. There's no escaping.
86 POSIX-style bytestring paths. There's no escaping.
86 """
87 """
87
88
88 # We use newlines as separators in the narrowspec file, so don't allow them
89 # We use newlines as separators in the narrowspec file, so don't allow them
89 # in patterns.
90 # in patterns.
90 if _numlines(pat) > 1:
91 if _numlines(pat) > 1:
91 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
92 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
92
93
93 components = pat.split('/')
94 components = pat.split('/')
94 if '.' in components or '..' in components:
95 if '.' in components or '..' in components:
95 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
96 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
96
97
97 def normalizepattern(pattern, defaultkind='path'):
98 def normalizepattern(pattern, defaultkind='path'):
98 """Returns the normalized version of a text-format pattern.
99 """Returns the normalized version of a text-format pattern.
99
100
100 If the pattern has no kind, the default will be added.
101 If the pattern has no kind, the default will be added.
101 """
102 """
102 kind, pat = matchmod._patsplit(pattern, defaultkind)
103 kind, pat = matchmod._patsplit(pattern, defaultkind)
103 return '%s:%s' % normalizesplitpattern(kind, pat)
104 return '%s:%s' % normalizesplitpattern(kind, pat)
104
105
105 def parsepatterns(pats):
106 def parsepatterns(pats):
106 """Parses a list of patterns into a typed pattern set."""
107 """Parses a list of patterns into a typed pattern set."""
107 return set(normalizepattern(p) for p in pats)
108 return set(normalizepattern(p) for p in pats)
108
109
109 def format(includes, excludes):
110 def format(includes, excludes):
110 output = '[includes]\n'
111 output = '[include]\n'
111 for i in sorted(includes - excludes):
112 for i in sorted(includes - excludes):
112 output += i + '\n'
113 output += i + '\n'
113 output += '[excludes]\n'
114 output += '[exclude]\n'
114 for e in sorted(excludes):
115 for e in sorted(excludes):
115 output += e + '\n'
116 output += e + '\n'
116 return output
117 return output
117
118
118 def match(root, include=None, exclude=None):
119 def match(root, include=None, exclude=None):
119 if not include:
120 if not include:
120 # Passing empty include and empty exclude to matchmod.match()
121 # Passing empty include and empty exclude to matchmod.match()
121 # gives a matcher that matches everything, so explicitly use
122 # gives a matcher that matches everything, so explicitly use
122 # the nevermatcher.
123 # the nevermatcher.
123 return matchmod.never(root, '')
124 return matchmod.never(root, '')
124 return matchmod.match(root, '', [], include=include or [],
125 return matchmod.match(root, '', [], include=include or [],
125 exclude=exclude or [])
126 exclude=exclude or [])
126
127
127 def needsexpansion(includes):
128 def needsexpansion(includes):
128 return [i for i in includes if i.startswith('include:')]
129 return [i for i in includes if i.startswith('include:')]
129
130
130 def load(repo):
131 def load(repo):
131 try:
132 try:
132 spec = repo.vfs.read(FILENAME)
133 spec = repo.vfs.read(FILENAME)
133 except IOError as e:
134 except IOError as e:
134 # Treat "narrowspec does not exist" the same as "narrowspec file exists
135 # Treat "narrowspec does not exist" the same as "narrowspec file exists
135 # and is empty".
136 # and is empty".
136 if e.errno == errno.ENOENT:
137 if e.errno == errno.ENOENT:
137 # Without this the next call to load will use the cached
138 # Without this the next call to load will use the cached
138 # non-existence of the file, which can cause some odd issues.
139 # non-existence of the file, which can cause some odd issues.
139 repo.invalidate(clearfilecache=True)
140 repo.invalidate(clearfilecache=True)
140 return set(), set()
141 return set(), set()
141 raise
142 raise
142 return _parsestoredpatterns(spec)
143 # maybe we should care about the profiles returned too
144 includepats, excludepats, profiles = sparse.parseconfig(repo.ui, spec,
145 'narrow')
146 if profiles:
147 raise error.Abort(_("including other spec files using '%include' is not"
148 " suported in narrowspec"))
149 return includepats, excludepats
143
150
144 def save(repo, includepats, excludepats):
151 def save(repo, includepats, excludepats):
145 spec = format(includepats, excludepats)
152 spec = format(includepats, excludepats)
146 repo.vfs.write(FILENAME, spec)
153 repo.vfs.write(FILENAME, spec)
147
154
148 def savebackup(vfs, backupname):
155 def savebackup(vfs, backupname):
149 vfs.tryunlink(backupname)
156 vfs.tryunlink(backupname)
150 util.copyfile(vfs.join(FILENAME), vfs.join(backupname), hardlink=True)
157 util.copyfile(vfs.join(FILENAME), vfs.join(backupname), hardlink=True)
151
158
152 def restorebackup(vfs, backupname):
159 def restorebackup(vfs, backupname):
153 vfs.rename(backupname, FILENAME, checkambig=True)
160 vfs.rename(backupname, FILENAME, checkambig=True)
154
161
155 def clearbackup(vfs, backupname):
162 def clearbackup(vfs, backupname):
156 vfs.unlink(backupname)
163 vfs.unlink(backupname)
157
164
158 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
165 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
159 r""" Restricts the patterns according to repo settings,
166 r""" Restricts the patterns according to repo settings,
160 results in a logical AND operation
167 results in a logical AND operation
161
168
162 :param req_includes: requested includes
169 :param req_includes: requested includes
163 :param req_excludes: requested excludes
170 :param req_excludes: requested excludes
164 :param repo_includes: repo includes
171 :param repo_includes: repo includes
165 :param repo_excludes: repo excludes
172 :param repo_excludes: repo excludes
166 :return: include patterns, exclude patterns, and invalid include patterns.
173 :return: include patterns, exclude patterns, and invalid include patterns.
167
174
168 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
175 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
169 (set(['f1']), {}, [])
176 (set(['f1']), {}, [])
170 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
177 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
171 (set(['f1']), {}, [])
178 (set(['f1']), {}, [])
172 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
179 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
173 (set(['f1/fc1']), {}, [])
180 (set(['f1/fc1']), {}, [])
174 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
181 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
175 ([], set(['path:.']), [])
182 ([], set(['path:.']), [])
176 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
183 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
177 (set(['f2/fc2']), {}, [])
184 (set(['f2/fc2']), {}, [])
178 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
185 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
179 ([], set(['path:.']), [])
186 ([], set(['path:.']), [])
180 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
187 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
181 (set(['f1/$non_exitent_var']), {}, [])
188 (set(['f1/$non_exitent_var']), {}, [])
182 """
189 """
183 res_excludes = set(req_excludes)
190 res_excludes = set(req_excludes)
184 res_excludes.update(repo_excludes)
191 res_excludes.update(repo_excludes)
185 invalid_includes = []
192 invalid_includes = []
186 if not req_includes:
193 if not req_includes:
187 res_includes = set(repo_includes)
194 res_includes = set(repo_includes)
188 elif 'path:.' not in repo_includes:
195 elif 'path:.' not in repo_includes:
189 res_includes = []
196 res_includes = []
190 for req_include in req_includes:
197 for req_include in req_includes:
191 req_include = util.expandpath(util.normpath(req_include))
198 req_include = util.expandpath(util.normpath(req_include))
192 if req_include in repo_includes:
199 if req_include in repo_includes:
193 res_includes.append(req_include)
200 res_includes.append(req_include)
194 continue
201 continue
195 valid = False
202 valid = False
196 for repo_include in repo_includes:
203 for repo_include in repo_includes:
197 if req_include.startswith(repo_include + '/'):
204 if req_include.startswith(repo_include + '/'):
198 valid = True
205 valid = True
199 res_includes.append(req_include)
206 res_includes.append(req_include)
200 break
207 break
201 if not valid:
208 if not valid:
202 invalid_includes.append(req_include)
209 invalid_includes.append(req_include)
203 if len(res_includes) == 0:
210 if len(res_includes) == 0:
204 res_excludes = {'path:.'}
211 res_excludes = {'path:.'}
205 else:
212 else:
206 res_includes = set(res_includes)
213 res_includes = set(res_includes)
207 else:
214 else:
208 res_includes = set(req_includes)
215 res_includes = set(req_includes)
209 return res_includes, res_excludes, invalid_includes
216 return res_includes, res_excludes, invalid_includes
@@ -1,43 +1,43 b''
1 $ . "$TESTDIR/narrow-library.sh"
1 $ . "$TESTDIR/narrow-library.sh"
2 $ hg init repo
2 $ hg init repo
3 $ cd repo
3 $ cd repo
4 $ cat << EOF > .hg/narrowspec
4 $ cat << EOF > .hg/narrowspec
5 > [includes]
5 > [include]
6 > path:foo
6 > path:foo
7 > [excludes]
7 > [exclude]
8 > EOF
8 > EOF
9 $ echo treemanifest >> .hg/requires
9 $ echo treemanifest >> .hg/requires
10 $ echo narrowhg-experimental >> .hg/requires
10 $ echo narrowhg-experimental >> .hg/requires
11 $ mkdir -p foo/bar
11 $ mkdir -p foo/bar
12 $ echo b > foo/f
12 $ echo b > foo/f
13 $ echo c > foo/bar/f
13 $ echo c > foo/bar/f
14 $ hg commit -Am hi
14 $ hg commit -Am hi
15 adding foo/bar/f
15 adding foo/bar/f
16 adding foo/f
16 adding foo/f
17 $ hg debugindex -m
17 $ hg debugindex -m
18 rev linkrev nodeid p1 p2
18 rev linkrev nodeid p1 p2
19 0 0 14a5d056d75a 000000000000 000000000000
19 0 0 14a5d056d75a 000000000000 000000000000
20 $ hg debugindex --dir foo
20 $ hg debugindex --dir foo
21 rev linkrev nodeid p1 p2
21 rev linkrev nodeid p1 p2
22 0 0 e635c7857aef 000000000000 000000000000
22 0 0 e635c7857aef 000000000000 000000000000
23 $ hg debugindex --dir foo/
23 $ hg debugindex --dir foo/
24 rev linkrev nodeid p1 p2
24 rev linkrev nodeid p1 p2
25 0 0 e635c7857aef 000000000000 000000000000
25 0 0 e635c7857aef 000000000000 000000000000
26 $ hg debugindex --dir foo/bar
26 $ hg debugindex --dir foo/bar
27 rev linkrev nodeid p1 p2
27 rev linkrev nodeid p1 p2
28 0 0 e091d4224761 000000000000 000000000000
28 0 0 e091d4224761 000000000000 000000000000
29 $ hg debugindex --dir foo/bar/
29 $ hg debugindex --dir foo/bar/
30 rev linkrev nodeid p1 p2
30 rev linkrev nodeid p1 p2
31 0 0 e091d4224761 000000000000 000000000000
31 0 0 e091d4224761 000000000000 000000000000
32 $ hg debugdata -m 0
32 $ hg debugdata -m 0
33 foo\x00e635c7857aef92ac761ce5741a99da159abbbb24t (esc)
33 foo\x00e635c7857aef92ac761ce5741a99da159abbbb24t (esc)
34 $ hg debugdata --dir foo 0
34 $ hg debugdata --dir foo 0
35 bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc)
35 bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc)
36 f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc)
36 f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc)
37 $ hg debugdata --dir foo/ 0
37 $ hg debugdata --dir foo/ 0
38 bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc)
38 bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc)
39 f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc)
39 f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc)
40 $ hg debugdata --dir foo/bar 0
40 $ hg debugdata --dir foo/bar 0
41 f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc)
41 f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc)
42 $ hg debugdata --dir foo/bar/ 0
42 $ hg debugdata --dir foo/bar/ 0
43 f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc)
43 f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc)
General Comments 0
You need to be logged in to leave comments. Login now