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