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