##// END OF EJS Templates
match: allow passing a context object to match core
Matt Mackall -
r14674:1c151b96 default
parent child Browse files
Show More
@@ -1,1115 +1,1116 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
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 node import nullid, nullrev, short, hex
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
10 import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
11 import match as matchmod
11 import match as matchmod
12 import os, errno, stat
12 import os, errno, stat
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15
15
16 class changectx(object):
16 class changectx(object):
17 """A changecontext object makes access to data related to a particular
17 """A changecontext object makes access to data related to a particular
18 changeset convenient."""
18 changeset convenient."""
19 def __init__(self, repo, changeid=''):
19 def __init__(self, repo, changeid=''):
20 """changeid is a revision number, node, or tag"""
20 """changeid is a revision number, node, or tag"""
21 if changeid == '':
21 if changeid == '':
22 changeid = '.'
22 changeid = '.'
23 self._repo = repo
23 self._repo = repo
24 if isinstance(changeid, (long, int)):
24 if isinstance(changeid, (long, int)):
25 self._rev = changeid
25 self._rev = changeid
26 self._node = self._repo.changelog.node(changeid)
26 self._node = self._repo.changelog.node(changeid)
27 else:
27 else:
28 self._node = self._repo.lookup(changeid)
28 self._node = self._repo.lookup(changeid)
29 self._rev = self._repo.changelog.rev(self._node)
29 self._rev = self._repo.changelog.rev(self._node)
30
30
31 def __str__(self):
31 def __str__(self):
32 return short(self.node())
32 return short(self.node())
33
33
34 def __int__(self):
34 def __int__(self):
35 return self.rev()
35 return self.rev()
36
36
37 def __repr__(self):
37 def __repr__(self):
38 return "<changectx %s>" % str(self)
38 return "<changectx %s>" % str(self)
39
39
40 def __hash__(self):
40 def __hash__(self):
41 try:
41 try:
42 return hash(self._rev)
42 return hash(self._rev)
43 except AttributeError:
43 except AttributeError:
44 return id(self)
44 return id(self)
45
45
46 def __eq__(self, other):
46 def __eq__(self, other):
47 try:
47 try:
48 return self._rev == other._rev
48 return self._rev == other._rev
49 except AttributeError:
49 except AttributeError:
50 return False
50 return False
51
51
52 def __ne__(self, other):
52 def __ne__(self, other):
53 return not (self == other)
53 return not (self == other)
54
54
55 def __nonzero__(self):
55 def __nonzero__(self):
56 return self._rev != nullrev
56 return self._rev != nullrev
57
57
58 @propertycache
58 @propertycache
59 def _changeset(self):
59 def _changeset(self):
60 return self._repo.changelog.read(self.node())
60 return self._repo.changelog.read(self.node())
61
61
62 @propertycache
62 @propertycache
63 def _manifest(self):
63 def _manifest(self):
64 return self._repo.manifest.read(self._changeset[0])
64 return self._repo.manifest.read(self._changeset[0])
65
65
66 @propertycache
66 @propertycache
67 def _manifestdelta(self):
67 def _manifestdelta(self):
68 return self._repo.manifest.readdelta(self._changeset[0])
68 return self._repo.manifest.readdelta(self._changeset[0])
69
69
70 @propertycache
70 @propertycache
71 def _parents(self):
71 def _parents(self):
72 p = self._repo.changelog.parentrevs(self._rev)
72 p = self._repo.changelog.parentrevs(self._rev)
73 if p[1] == nullrev:
73 if p[1] == nullrev:
74 p = p[:-1]
74 p = p[:-1]
75 return [changectx(self._repo, x) for x in p]
75 return [changectx(self._repo, x) for x in p]
76
76
77 @propertycache
77 @propertycache
78 def substate(self):
78 def substate(self):
79 return subrepo.state(self, self._repo.ui)
79 return subrepo.state(self, self._repo.ui)
80
80
81 def __contains__(self, key):
81 def __contains__(self, key):
82 return key in self._manifest
82 return key in self._manifest
83
83
84 def __getitem__(self, key):
84 def __getitem__(self, key):
85 return self.filectx(key)
85 return self.filectx(key)
86
86
87 def __iter__(self):
87 def __iter__(self):
88 for f in sorted(self._manifest):
88 for f in sorted(self._manifest):
89 yield f
89 yield f
90
90
91 def changeset(self):
91 def changeset(self):
92 return self._changeset
92 return self._changeset
93 def manifest(self):
93 def manifest(self):
94 return self._manifest
94 return self._manifest
95 def manifestnode(self):
95 def manifestnode(self):
96 return self._changeset[0]
96 return self._changeset[0]
97
97
98 def rev(self):
98 def rev(self):
99 return self._rev
99 return self._rev
100 def node(self):
100 def node(self):
101 return self._node
101 return self._node
102 def hex(self):
102 def hex(self):
103 return hex(self._node)
103 return hex(self._node)
104 def user(self):
104 def user(self):
105 return self._changeset[1]
105 return self._changeset[1]
106 def date(self):
106 def date(self):
107 return self._changeset[2]
107 return self._changeset[2]
108 def files(self):
108 def files(self):
109 return self._changeset[3]
109 return self._changeset[3]
110 def description(self):
110 def description(self):
111 return self._changeset[4]
111 return self._changeset[4]
112 def branch(self):
112 def branch(self):
113 return encoding.tolocal(self._changeset[5].get("branch"))
113 return encoding.tolocal(self._changeset[5].get("branch"))
114 def extra(self):
114 def extra(self):
115 return self._changeset[5]
115 return self._changeset[5]
116 def tags(self):
116 def tags(self):
117 return self._repo.nodetags(self._node)
117 return self._repo.nodetags(self._node)
118 def bookmarks(self):
118 def bookmarks(self):
119 return self._repo.nodebookmarks(self._node)
119 return self._repo.nodebookmarks(self._node)
120 def hidden(self):
120 def hidden(self):
121 return self._rev in self._repo.changelog.hiddenrevs
121 return self._rev in self._repo.changelog.hiddenrevs
122
122
123 def parents(self):
123 def parents(self):
124 """return contexts for each parent changeset"""
124 """return contexts for each parent changeset"""
125 return self._parents
125 return self._parents
126
126
127 def p1(self):
127 def p1(self):
128 return self._parents[0]
128 return self._parents[0]
129
129
130 def p2(self):
130 def p2(self):
131 if len(self._parents) == 2:
131 if len(self._parents) == 2:
132 return self._parents[1]
132 return self._parents[1]
133 return changectx(self._repo, -1)
133 return changectx(self._repo, -1)
134
134
135 def children(self):
135 def children(self):
136 """return contexts for each child changeset"""
136 """return contexts for each child changeset"""
137 c = self._repo.changelog.children(self._node)
137 c = self._repo.changelog.children(self._node)
138 return [changectx(self._repo, x) for x in c]
138 return [changectx(self._repo, x) for x in c]
139
139
140 def ancestors(self):
140 def ancestors(self):
141 for a in self._repo.changelog.ancestors(self._rev):
141 for a in self._repo.changelog.ancestors(self._rev):
142 yield changectx(self._repo, a)
142 yield changectx(self._repo, a)
143
143
144 def descendants(self):
144 def descendants(self):
145 for d in self._repo.changelog.descendants(self._rev):
145 for d in self._repo.changelog.descendants(self._rev):
146 yield changectx(self._repo, d)
146 yield changectx(self._repo, d)
147
147
148 def _fileinfo(self, path):
148 def _fileinfo(self, path):
149 if '_manifest' in self.__dict__:
149 if '_manifest' in self.__dict__:
150 try:
150 try:
151 return self._manifest[path], self._manifest.flags(path)
151 return self._manifest[path], self._manifest.flags(path)
152 except KeyError:
152 except KeyError:
153 raise error.LookupError(self._node, path,
153 raise error.LookupError(self._node, path,
154 _('not found in manifest'))
154 _('not found in manifest'))
155 if '_manifestdelta' in self.__dict__ or path in self.files():
155 if '_manifestdelta' in self.__dict__ or path in self.files():
156 if path in self._manifestdelta:
156 if path in self._manifestdelta:
157 return self._manifestdelta[path], self._manifestdelta.flags(path)
157 return self._manifestdelta[path], self._manifestdelta.flags(path)
158 node, flag = self._repo.manifest.find(self._changeset[0], path)
158 node, flag = self._repo.manifest.find(self._changeset[0], path)
159 if not node:
159 if not node:
160 raise error.LookupError(self._node, path,
160 raise error.LookupError(self._node, path,
161 _('not found in manifest'))
161 _('not found in manifest'))
162
162
163 return node, flag
163 return node, flag
164
164
165 def filenode(self, path):
165 def filenode(self, path):
166 return self._fileinfo(path)[0]
166 return self._fileinfo(path)[0]
167
167
168 def flags(self, path):
168 def flags(self, path):
169 try:
169 try:
170 return self._fileinfo(path)[1]
170 return self._fileinfo(path)[1]
171 except error.LookupError:
171 except error.LookupError:
172 return ''
172 return ''
173
173
174 def filectx(self, path, fileid=None, filelog=None):
174 def filectx(self, path, fileid=None, filelog=None):
175 """get a file context from this changeset"""
175 """get a file context from this changeset"""
176 if fileid is None:
176 if fileid is None:
177 fileid = self.filenode(path)
177 fileid = self.filenode(path)
178 return filectx(self._repo, path, fileid=fileid,
178 return filectx(self._repo, path, fileid=fileid,
179 changectx=self, filelog=filelog)
179 changectx=self, filelog=filelog)
180
180
181 def ancestor(self, c2):
181 def ancestor(self, c2):
182 """
182 """
183 return the ancestor context of self and c2
183 return the ancestor context of self and c2
184 """
184 """
185 # deal with workingctxs
185 # deal with workingctxs
186 n2 = c2._node
186 n2 = c2._node
187 if n2 is None:
187 if n2 is None:
188 n2 = c2._parents[0]._node
188 n2 = c2._parents[0]._node
189 n = self._repo.changelog.ancestor(self._node, n2)
189 n = self._repo.changelog.ancestor(self._node, n2)
190 return changectx(self._repo, n)
190 return changectx(self._repo, n)
191
191
192 def walk(self, match):
192 def walk(self, match):
193 fset = set(match.files())
193 fset = set(match.files())
194 # for dirstate.walk, files=['.'] means "walk the whole tree".
194 # for dirstate.walk, files=['.'] means "walk the whole tree".
195 # follow that here, too
195 # follow that here, too
196 fset.discard('.')
196 fset.discard('.')
197 for fn in self:
197 for fn in self:
198 for ffn in fset:
198 for ffn in fset:
199 # match if the file is the exact name or a directory
199 # match if the file is the exact name or a directory
200 if ffn == fn or fn.startswith("%s/" % ffn):
200 if ffn == fn or fn.startswith("%s/" % ffn):
201 fset.remove(ffn)
201 fset.remove(ffn)
202 break
202 break
203 if match(fn):
203 if match(fn):
204 yield fn
204 yield fn
205 for fn in sorted(fset):
205 for fn in sorted(fset):
206 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
206 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
207 yield fn
207 yield fn
208
208
209 def sub(self, path):
209 def sub(self, path):
210 return subrepo.subrepo(self, path)
210 return subrepo.subrepo(self, path)
211
211
212 def match(self, pats=[], include=None, exclude=None, default='glob'):
212 def match(self, pats=[], include=None, exclude=None, default='glob'):
213 r = self._repo
213 r = self._repo
214 return matchmod.match(r.root, r.getcwd(), pats,
214 return matchmod.match(r.root, r.getcwd(), pats,
215 include, exclude, default, auditor=r.auditor)
215 include, exclude, default,
216 auditor=r.auditor, ctx=self)
216
217
217 def diff(self, ctx2=None, match=None, **opts):
218 def diff(self, ctx2=None, match=None, **opts):
218 """Returns a diff generator for the given contexts and matcher"""
219 """Returns a diff generator for the given contexts and matcher"""
219 if ctx2 is None:
220 if ctx2 is None:
220 ctx2 = self.p1()
221 ctx2 = self.p1()
221 if ctx2 is not None and not isinstance(ctx2, changectx):
222 if ctx2 is not None and not isinstance(ctx2, changectx):
222 ctx2 = self._repo[ctx2]
223 ctx2 = self._repo[ctx2]
223 diffopts = patch.diffopts(self._repo.ui, opts)
224 diffopts = patch.diffopts(self._repo.ui, opts)
224 return patch.diff(self._repo, ctx2.node(), self.node(),
225 return patch.diff(self._repo, ctx2.node(), self.node(),
225 match=match, opts=diffopts)
226 match=match, opts=diffopts)
226
227
227 class filectx(object):
228 class filectx(object):
228 """A filecontext object makes access to data related to a particular
229 """A filecontext object makes access to data related to a particular
229 filerevision convenient."""
230 filerevision convenient."""
230 def __init__(self, repo, path, changeid=None, fileid=None,
231 def __init__(self, repo, path, changeid=None, fileid=None,
231 filelog=None, changectx=None):
232 filelog=None, changectx=None):
232 """changeid can be a changeset revision, node, or tag.
233 """changeid can be a changeset revision, node, or tag.
233 fileid can be a file revision or node."""
234 fileid can be a file revision or node."""
234 self._repo = repo
235 self._repo = repo
235 self._path = path
236 self._path = path
236
237
237 assert (changeid is not None
238 assert (changeid is not None
238 or fileid is not None
239 or fileid is not None
239 or changectx is not None), \
240 or changectx is not None), \
240 ("bad args: changeid=%r, fileid=%r, changectx=%r"
241 ("bad args: changeid=%r, fileid=%r, changectx=%r"
241 % (changeid, fileid, changectx))
242 % (changeid, fileid, changectx))
242
243
243 if filelog:
244 if filelog:
244 self._filelog = filelog
245 self._filelog = filelog
245
246
246 if changeid is not None:
247 if changeid is not None:
247 self._changeid = changeid
248 self._changeid = changeid
248 if changectx is not None:
249 if changectx is not None:
249 self._changectx = changectx
250 self._changectx = changectx
250 if fileid is not None:
251 if fileid is not None:
251 self._fileid = fileid
252 self._fileid = fileid
252
253
253 @propertycache
254 @propertycache
254 def _changectx(self):
255 def _changectx(self):
255 return changectx(self._repo, self._changeid)
256 return changectx(self._repo, self._changeid)
256
257
257 @propertycache
258 @propertycache
258 def _filelog(self):
259 def _filelog(self):
259 return self._repo.file(self._path)
260 return self._repo.file(self._path)
260
261
261 @propertycache
262 @propertycache
262 def _changeid(self):
263 def _changeid(self):
263 if '_changectx' in self.__dict__:
264 if '_changectx' in self.__dict__:
264 return self._changectx.rev()
265 return self._changectx.rev()
265 else:
266 else:
266 return self._filelog.linkrev(self._filerev)
267 return self._filelog.linkrev(self._filerev)
267
268
268 @propertycache
269 @propertycache
269 def _filenode(self):
270 def _filenode(self):
270 if '_fileid' in self.__dict__:
271 if '_fileid' in self.__dict__:
271 return self._filelog.lookup(self._fileid)
272 return self._filelog.lookup(self._fileid)
272 else:
273 else:
273 return self._changectx.filenode(self._path)
274 return self._changectx.filenode(self._path)
274
275
275 @propertycache
276 @propertycache
276 def _filerev(self):
277 def _filerev(self):
277 return self._filelog.rev(self._filenode)
278 return self._filelog.rev(self._filenode)
278
279
279 @propertycache
280 @propertycache
280 def _repopath(self):
281 def _repopath(self):
281 return self._path
282 return self._path
282
283
283 def __nonzero__(self):
284 def __nonzero__(self):
284 try:
285 try:
285 self._filenode
286 self._filenode
286 return True
287 return True
287 except error.LookupError:
288 except error.LookupError:
288 # file is missing
289 # file is missing
289 return False
290 return False
290
291
291 def __str__(self):
292 def __str__(self):
292 return "%s@%s" % (self.path(), short(self.node()))
293 return "%s@%s" % (self.path(), short(self.node()))
293
294
294 def __repr__(self):
295 def __repr__(self):
295 return "<filectx %s>" % str(self)
296 return "<filectx %s>" % str(self)
296
297
297 def __hash__(self):
298 def __hash__(self):
298 try:
299 try:
299 return hash((self._path, self._filenode))
300 return hash((self._path, self._filenode))
300 except AttributeError:
301 except AttributeError:
301 return id(self)
302 return id(self)
302
303
303 def __eq__(self, other):
304 def __eq__(self, other):
304 try:
305 try:
305 return (self._path == other._path
306 return (self._path == other._path
306 and self._filenode == other._filenode)
307 and self._filenode == other._filenode)
307 except AttributeError:
308 except AttributeError:
308 return False
309 return False
309
310
310 def __ne__(self, other):
311 def __ne__(self, other):
311 return not (self == other)
312 return not (self == other)
312
313
313 def filectx(self, fileid):
314 def filectx(self, fileid):
314 '''opens an arbitrary revision of the file without
315 '''opens an arbitrary revision of the file without
315 opening a new filelog'''
316 opening a new filelog'''
316 return filectx(self._repo, self._path, fileid=fileid,
317 return filectx(self._repo, self._path, fileid=fileid,
317 filelog=self._filelog)
318 filelog=self._filelog)
318
319
319 def filerev(self):
320 def filerev(self):
320 return self._filerev
321 return self._filerev
321 def filenode(self):
322 def filenode(self):
322 return self._filenode
323 return self._filenode
323 def flags(self):
324 def flags(self):
324 return self._changectx.flags(self._path)
325 return self._changectx.flags(self._path)
325 def filelog(self):
326 def filelog(self):
326 return self._filelog
327 return self._filelog
327
328
328 def rev(self):
329 def rev(self):
329 if '_changectx' in self.__dict__:
330 if '_changectx' in self.__dict__:
330 return self._changectx.rev()
331 return self._changectx.rev()
331 if '_changeid' in self.__dict__:
332 if '_changeid' in self.__dict__:
332 return self._changectx.rev()
333 return self._changectx.rev()
333 return self._filelog.linkrev(self._filerev)
334 return self._filelog.linkrev(self._filerev)
334
335
335 def linkrev(self):
336 def linkrev(self):
336 return self._filelog.linkrev(self._filerev)
337 return self._filelog.linkrev(self._filerev)
337 def node(self):
338 def node(self):
338 return self._changectx.node()
339 return self._changectx.node()
339 def hex(self):
340 def hex(self):
340 return hex(self.node())
341 return hex(self.node())
341 def user(self):
342 def user(self):
342 return self._changectx.user()
343 return self._changectx.user()
343 def date(self):
344 def date(self):
344 return self._changectx.date()
345 return self._changectx.date()
345 def files(self):
346 def files(self):
346 return self._changectx.files()
347 return self._changectx.files()
347 def description(self):
348 def description(self):
348 return self._changectx.description()
349 return self._changectx.description()
349 def branch(self):
350 def branch(self):
350 return self._changectx.branch()
351 return self._changectx.branch()
351 def extra(self):
352 def extra(self):
352 return self._changectx.extra()
353 return self._changectx.extra()
353 def manifest(self):
354 def manifest(self):
354 return self._changectx.manifest()
355 return self._changectx.manifest()
355 def changectx(self):
356 def changectx(self):
356 return self._changectx
357 return self._changectx
357
358
358 def data(self):
359 def data(self):
359 return self._filelog.read(self._filenode)
360 return self._filelog.read(self._filenode)
360 def path(self):
361 def path(self):
361 return self._path
362 return self._path
362 def size(self):
363 def size(self):
363 return self._filelog.size(self._filerev)
364 return self._filelog.size(self._filerev)
364
365
365 def cmp(self, fctx):
366 def cmp(self, fctx):
366 """compare with other file context
367 """compare with other file context
367
368
368 returns True if different than fctx.
369 returns True if different than fctx.
369 """
370 """
370 if (fctx._filerev is None and self._repo._encodefilterpats
371 if (fctx._filerev is None and self._repo._encodefilterpats
371 or self.size() == fctx.size()):
372 or self.size() == fctx.size()):
372 return self._filelog.cmp(self._filenode, fctx.data())
373 return self._filelog.cmp(self._filenode, fctx.data())
373
374
374 return True
375 return True
375
376
376 def renamed(self):
377 def renamed(self):
377 """check if file was actually renamed in this changeset revision
378 """check if file was actually renamed in this changeset revision
378
379
379 If rename logged in file revision, we report copy for changeset only
380 If rename logged in file revision, we report copy for changeset only
380 if file revisions linkrev points back to the changeset in question
381 if file revisions linkrev points back to the changeset in question
381 or both changeset parents contain different file revisions.
382 or both changeset parents contain different file revisions.
382 """
383 """
383
384
384 renamed = self._filelog.renamed(self._filenode)
385 renamed = self._filelog.renamed(self._filenode)
385 if not renamed:
386 if not renamed:
386 return renamed
387 return renamed
387
388
388 if self.rev() == self.linkrev():
389 if self.rev() == self.linkrev():
389 return renamed
390 return renamed
390
391
391 name = self.path()
392 name = self.path()
392 fnode = self._filenode
393 fnode = self._filenode
393 for p in self._changectx.parents():
394 for p in self._changectx.parents():
394 try:
395 try:
395 if fnode == p.filenode(name):
396 if fnode == p.filenode(name):
396 return None
397 return None
397 except error.LookupError:
398 except error.LookupError:
398 pass
399 pass
399 return renamed
400 return renamed
400
401
401 def parents(self):
402 def parents(self):
402 p = self._path
403 p = self._path
403 fl = self._filelog
404 fl = self._filelog
404 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
405 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
405
406
406 r = self._filelog.renamed(self._filenode)
407 r = self._filelog.renamed(self._filenode)
407 if r:
408 if r:
408 pl[0] = (r[0], r[1], None)
409 pl[0] = (r[0], r[1], None)
409
410
410 return [filectx(self._repo, p, fileid=n, filelog=l)
411 return [filectx(self._repo, p, fileid=n, filelog=l)
411 for p, n, l in pl if n != nullid]
412 for p, n, l in pl if n != nullid]
412
413
413 def p1(self):
414 def p1(self):
414 return self.parents()[0]
415 return self.parents()[0]
415
416
416 def p2(self):
417 def p2(self):
417 p = self.parents()
418 p = self.parents()
418 if len(p) == 2:
419 if len(p) == 2:
419 return p[1]
420 return p[1]
420 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
421 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
421
422
422 def children(self):
423 def children(self):
423 # hard for renames
424 # hard for renames
424 c = self._filelog.children(self._filenode)
425 c = self._filelog.children(self._filenode)
425 return [filectx(self._repo, self._path, fileid=x,
426 return [filectx(self._repo, self._path, fileid=x,
426 filelog=self._filelog) for x in c]
427 filelog=self._filelog) for x in c]
427
428
428 def annotate(self, follow=False, linenumber=None):
429 def annotate(self, follow=False, linenumber=None):
429 '''returns a list of tuples of (ctx, line) for each line
430 '''returns a list of tuples of (ctx, line) for each line
430 in the file, where ctx is the filectx of the node where
431 in the file, where ctx is the filectx of the node where
431 that line was last changed.
432 that line was last changed.
432 This returns tuples of ((ctx, linenumber), line) for each line,
433 This returns tuples of ((ctx, linenumber), line) for each line,
433 if "linenumber" parameter is NOT "None".
434 if "linenumber" parameter is NOT "None".
434 In such tuples, linenumber means one at the first appearance
435 In such tuples, linenumber means one at the first appearance
435 in the managed file.
436 in the managed file.
436 To reduce annotation cost,
437 To reduce annotation cost,
437 this returns fixed value(False is used) as linenumber,
438 this returns fixed value(False is used) as linenumber,
438 if "linenumber" parameter is "False".'''
439 if "linenumber" parameter is "False".'''
439
440
440 def decorate_compat(text, rev):
441 def decorate_compat(text, rev):
441 return ([rev] * len(text.splitlines()), text)
442 return ([rev] * len(text.splitlines()), text)
442
443
443 def without_linenumber(text, rev):
444 def without_linenumber(text, rev):
444 return ([(rev, False)] * len(text.splitlines()), text)
445 return ([(rev, False)] * len(text.splitlines()), text)
445
446
446 def with_linenumber(text, rev):
447 def with_linenumber(text, rev):
447 size = len(text.splitlines())
448 size = len(text.splitlines())
448 return ([(rev, i) for i in xrange(1, size + 1)], text)
449 return ([(rev, i) for i in xrange(1, size + 1)], text)
449
450
450 decorate = (((linenumber is None) and decorate_compat) or
451 decorate = (((linenumber is None) and decorate_compat) or
451 (linenumber and with_linenumber) or
452 (linenumber and with_linenumber) or
452 without_linenumber)
453 without_linenumber)
453
454
454 def pair(parent, child):
455 def pair(parent, child):
455 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
456 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
456 child[0][b1:b2] = parent[0][a1:a2]
457 child[0][b1:b2] = parent[0][a1:a2]
457 return child
458 return child
458
459
459 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
460 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
460 def getctx(path, fileid):
461 def getctx(path, fileid):
461 log = path == self._path and self._filelog or getlog(path)
462 log = path == self._path and self._filelog or getlog(path)
462 return filectx(self._repo, path, fileid=fileid, filelog=log)
463 return filectx(self._repo, path, fileid=fileid, filelog=log)
463 getctx = util.lrucachefunc(getctx)
464 getctx = util.lrucachefunc(getctx)
464
465
465 def parents(f):
466 def parents(f):
466 # we want to reuse filectx objects as much as possible
467 # we want to reuse filectx objects as much as possible
467 p = f._path
468 p = f._path
468 if f._filerev is None: # working dir
469 if f._filerev is None: # working dir
469 pl = [(n.path(), n.filerev()) for n in f.parents()]
470 pl = [(n.path(), n.filerev()) for n in f.parents()]
470 else:
471 else:
471 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
472 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
472
473
473 if follow:
474 if follow:
474 r = f.renamed()
475 r = f.renamed()
475 if r:
476 if r:
476 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
477 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
477
478
478 return [getctx(p, n) for p, n in pl if n != nullrev]
479 return [getctx(p, n) for p, n in pl if n != nullrev]
479
480
480 # use linkrev to find the first changeset where self appeared
481 # use linkrev to find the first changeset where self appeared
481 if self.rev() != self.linkrev():
482 if self.rev() != self.linkrev():
482 base = self.filectx(self.filerev())
483 base = self.filectx(self.filerev())
483 else:
484 else:
484 base = self
485 base = self
485
486
486 # This algorithm would prefer to be recursive, but Python is a
487 # This algorithm would prefer to be recursive, but Python is a
487 # bit recursion-hostile. Instead we do an iterative
488 # bit recursion-hostile. Instead we do an iterative
488 # depth-first search.
489 # depth-first search.
489
490
490 visit = [base]
491 visit = [base]
491 hist = {}
492 hist = {}
492 pcache = {}
493 pcache = {}
493 needed = {base: 1}
494 needed = {base: 1}
494 while visit:
495 while visit:
495 f = visit[-1]
496 f = visit[-1]
496 if f not in pcache:
497 if f not in pcache:
497 pcache[f] = parents(f)
498 pcache[f] = parents(f)
498
499
499 ready = True
500 ready = True
500 pl = pcache[f]
501 pl = pcache[f]
501 for p in pl:
502 for p in pl:
502 if p not in hist:
503 if p not in hist:
503 ready = False
504 ready = False
504 visit.append(p)
505 visit.append(p)
505 needed[p] = needed.get(p, 0) + 1
506 needed[p] = needed.get(p, 0) + 1
506 if ready:
507 if ready:
507 visit.pop()
508 visit.pop()
508 curr = decorate(f.data(), f)
509 curr = decorate(f.data(), f)
509 for p in pl:
510 for p in pl:
510 curr = pair(hist[p], curr)
511 curr = pair(hist[p], curr)
511 if needed[p] == 1:
512 if needed[p] == 1:
512 del hist[p]
513 del hist[p]
513 else:
514 else:
514 needed[p] -= 1
515 needed[p] -= 1
515
516
516 hist[f] = curr
517 hist[f] = curr
517 pcache[f] = []
518 pcache[f] = []
518
519
519 return zip(hist[base][0], hist[base][1].splitlines(True))
520 return zip(hist[base][0], hist[base][1].splitlines(True))
520
521
521 def ancestor(self, fc2, actx=None):
522 def ancestor(self, fc2, actx=None):
522 """
523 """
523 find the common ancestor file context, if any, of self, and fc2
524 find the common ancestor file context, if any, of self, and fc2
524
525
525 If actx is given, it must be the changectx of the common ancestor
526 If actx is given, it must be the changectx of the common ancestor
526 of self's and fc2's respective changesets.
527 of self's and fc2's respective changesets.
527 """
528 """
528
529
529 if actx is None:
530 if actx is None:
530 actx = self.changectx().ancestor(fc2.changectx())
531 actx = self.changectx().ancestor(fc2.changectx())
531
532
532 # the trivial case: changesets are unrelated, files must be too
533 # the trivial case: changesets are unrelated, files must be too
533 if not actx:
534 if not actx:
534 return None
535 return None
535
536
536 # the easy case: no (relevant) renames
537 # the easy case: no (relevant) renames
537 if fc2.path() == self.path() and self.path() in actx:
538 if fc2.path() == self.path() and self.path() in actx:
538 return actx[self.path()]
539 return actx[self.path()]
539 acache = {}
540 acache = {}
540
541
541 # prime the ancestor cache for the working directory
542 # prime the ancestor cache for the working directory
542 for c in (self, fc2):
543 for c in (self, fc2):
543 if c._filerev is None:
544 if c._filerev is None:
544 pl = [(n.path(), n.filenode()) for n in c.parents()]
545 pl = [(n.path(), n.filenode()) for n in c.parents()]
545 acache[(c._path, None)] = pl
546 acache[(c._path, None)] = pl
546
547
547 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
548 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
548 def parents(vertex):
549 def parents(vertex):
549 if vertex in acache:
550 if vertex in acache:
550 return acache[vertex]
551 return acache[vertex]
551 f, n = vertex
552 f, n = vertex
552 if f not in flcache:
553 if f not in flcache:
553 flcache[f] = self._repo.file(f)
554 flcache[f] = self._repo.file(f)
554 fl = flcache[f]
555 fl = flcache[f]
555 pl = [(f, p) for p in fl.parents(n) if p != nullid]
556 pl = [(f, p) for p in fl.parents(n) if p != nullid]
556 re = fl.renamed(n)
557 re = fl.renamed(n)
557 if re:
558 if re:
558 pl.append(re)
559 pl.append(re)
559 acache[vertex] = pl
560 acache[vertex] = pl
560 return pl
561 return pl
561
562
562 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
563 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
563 v = ancestor.ancestor(a, b, parents)
564 v = ancestor.ancestor(a, b, parents)
564 if v:
565 if v:
565 f, n = v
566 f, n = v
566 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
567 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
567
568
568 return None
569 return None
569
570
570 def ancestors(self):
571 def ancestors(self):
571 visit = {}
572 visit = {}
572 c = self
573 c = self
573 while True:
574 while True:
574 for parent in c.parents():
575 for parent in c.parents():
575 visit[(parent.rev(), parent.node())] = parent
576 visit[(parent.rev(), parent.node())] = parent
576 if not visit:
577 if not visit:
577 break
578 break
578 c = visit.pop(max(visit))
579 c = visit.pop(max(visit))
579 yield c
580 yield c
580
581
581 class workingctx(changectx):
582 class workingctx(changectx):
582 """A workingctx object makes access to data related to
583 """A workingctx object makes access to data related to
583 the current working directory convenient.
584 the current working directory convenient.
584 date - any valid date string or (unixtime, offset), or None.
585 date - any valid date string or (unixtime, offset), or None.
585 user - username string, or None.
586 user - username string, or None.
586 extra - a dictionary of extra values, or None.
587 extra - a dictionary of extra values, or None.
587 changes - a list of file lists as returned by localrepo.status()
588 changes - a list of file lists as returned by localrepo.status()
588 or None to use the repository status.
589 or None to use the repository status.
589 """
590 """
590 def __init__(self, repo, text="", user=None, date=None, extra=None,
591 def __init__(self, repo, text="", user=None, date=None, extra=None,
591 changes=None):
592 changes=None):
592 self._repo = repo
593 self._repo = repo
593 self._rev = None
594 self._rev = None
594 self._node = None
595 self._node = None
595 self._text = text
596 self._text = text
596 if date:
597 if date:
597 self._date = util.parsedate(date)
598 self._date = util.parsedate(date)
598 if user:
599 if user:
599 self._user = user
600 self._user = user
600 if changes:
601 if changes:
601 self._status = list(changes[:4])
602 self._status = list(changes[:4])
602 self._unknown = changes[4]
603 self._unknown = changes[4]
603 self._ignored = changes[5]
604 self._ignored = changes[5]
604 self._clean = changes[6]
605 self._clean = changes[6]
605 else:
606 else:
606 self._unknown = None
607 self._unknown = None
607 self._ignored = None
608 self._ignored = None
608 self._clean = None
609 self._clean = None
609
610
610 self._extra = {}
611 self._extra = {}
611 if extra:
612 if extra:
612 self._extra = extra.copy()
613 self._extra = extra.copy()
613 if 'branch' not in self._extra:
614 if 'branch' not in self._extra:
614 try:
615 try:
615 branch = encoding.fromlocal(self._repo.dirstate.branch())
616 branch = encoding.fromlocal(self._repo.dirstate.branch())
616 except UnicodeDecodeError:
617 except UnicodeDecodeError:
617 raise util.Abort(_('branch name not in UTF-8!'))
618 raise util.Abort(_('branch name not in UTF-8!'))
618 self._extra['branch'] = branch
619 self._extra['branch'] = branch
619 if self._extra['branch'] == '':
620 if self._extra['branch'] == '':
620 self._extra['branch'] = 'default'
621 self._extra['branch'] = 'default'
621
622
622 def __str__(self):
623 def __str__(self):
623 return str(self._parents[0]) + "+"
624 return str(self._parents[0]) + "+"
624
625
625 def __repr__(self):
626 def __repr__(self):
626 return "<workingctx %s>" % str(self)
627 return "<workingctx %s>" % str(self)
627
628
628 def __nonzero__(self):
629 def __nonzero__(self):
629 return True
630 return True
630
631
631 def __contains__(self, key):
632 def __contains__(self, key):
632 return self._repo.dirstate[key] not in "?r"
633 return self._repo.dirstate[key] not in "?r"
633
634
634 @propertycache
635 @propertycache
635 def _manifest(self):
636 def _manifest(self):
636 """generate a manifest corresponding to the working directory"""
637 """generate a manifest corresponding to the working directory"""
637
638
638 if self._unknown is None:
639 if self._unknown is None:
639 self.status(unknown=True)
640 self.status(unknown=True)
640
641
641 man = self._parents[0].manifest().copy()
642 man = self._parents[0].manifest().copy()
642 copied = self._repo.dirstate.copies()
643 copied = self._repo.dirstate.copies()
643 if len(self._parents) > 1:
644 if len(self._parents) > 1:
644 man2 = self.p2().manifest()
645 man2 = self.p2().manifest()
645 def getman(f):
646 def getman(f):
646 if f in man:
647 if f in man:
647 return man
648 return man
648 return man2
649 return man2
649 else:
650 else:
650 getman = lambda f: man
651 getman = lambda f: man
651 def cf(f):
652 def cf(f):
652 f = copied.get(f, f)
653 f = copied.get(f, f)
653 return getman(f).flags(f)
654 return getman(f).flags(f)
654 ff = self._repo.dirstate.flagfunc(cf)
655 ff = self._repo.dirstate.flagfunc(cf)
655 modified, added, removed, deleted = self._status
656 modified, added, removed, deleted = self._status
656 unknown = self._unknown
657 unknown = self._unknown
657 for i, l in (("a", added), ("m", modified), ("u", unknown)):
658 for i, l in (("a", added), ("m", modified), ("u", unknown)):
658 for f in l:
659 for f in l:
659 orig = copied.get(f, f)
660 orig = copied.get(f, f)
660 man[f] = getman(orig).get(orig, nullid) + i
661 man[f] = getman(orig).get(orig, nullid) + i
661 try:
662 try:
662 man.set(f, ff(f))
663 man.set(f, ff(f))
663 except OSError:
664 except OSError:
664 pass
665 pass
665
666
666 for f in deleted + removed:
667 for f in deleted + removed:
667 if f in man:
668 if f in man:
668 del man[f]
669 del man[f]
669
670
670 return man
671 return man
671
672
672 def __iter__(self):
673 def __iter__(self):
673 d = self._repo.dirstate
674 d = self._repo.dirstate
674 for f in d:
675 for f in d:
675 if d[f] != 'r':
676 if d[f] != 'r':
676 yield f
677 yield f
677
678
678 @propertycache
679 @propertycache
679 def _status(self):
680 def _status(self):
680 return self._repo.status()[:4]
681 return self._repo.status()[:4]
681
682
682 @propertycache
683 @propertycache
683 def _user(self):
684 def _user(self):
684 return self._repo.ui.username()
685 return self._repo.ui.username()
685
686
686 @propertycache
687 @propertycache
687 def _date(self):
688 def _date(self):
688 return util.makedate()
689 return util.makedate()
689
690
690 @propertycache
691 @propertycache
691 def _parents(self):
692 def _parents(self):
692 p = self._repo.dirstate.parents()
693 p = self._repo.dirstate.parents()
693 if p[1] == nullid:
694 if p[1] == nullid:
694 p = p[:-1]
695 p = p[:-1]
695 self._parents = [changectx(self._repo, x) for x in p]
696 self._parents = [changectx(self._repo, x) for x in p]
696 return self._parents
697 return self._parents
697
698
698 def status(self, ignored=False, clean=False, unknown=False):
699 def status(self, ignored=False, clean=False, unknown=False):
699 """Explicit status query
700 """Explicit status query
700 Unless this method is used to query the working copy status, the
701 Unless this method is used to query the working copy status, the
701 _status property will implicitly read the status using its default
702 _status property will implicitly read the status using its default
702 arguments."""
703 arguments."""
703 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
704 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
704 self._unknown = self._ignored = self._clean = None
705 self._unknown = self._ignored = self._clean = None
705 if unknown:
706 if unknown:
706 self._unknown = stat[4]
707 self._unknown = stat[4]
707 if ignored:
708 if ignored:
708 self._ignored = stat[5]
709 self._ignored = stat[5]
709 if clean:
710 if clean:
710 self._clean = stat[6]
711 self._clean = stat[6]
711 self._status = stat[:4]
712 self._status = stat[:4]
712 return stat
713 return stat
713
714
714 def manifest(self):
715 def manifest(self):
715 return self._manifest
716 return self._manifest
716 def user(self):
717 def user(self):
717 return self._user or self._repo.ui.username()
718 return self._user or self._repo.ui.username()
718 def date(self):
719 def date(self):
719 return self._date
720 return self._date
720 def description(self):
721 def description(self):
721 return self._text
722 return self._text
722 def files(self):
723 def files(self):
723 return sorted(self._status[0] + self._status[1] + self._status[2])
724 return sorted(self._status[0] + self._status[1] + self._status[2])
724
725
725 def modified(self):
726 def modified(self):
726 return self._status[0]
727 return self._status[0]
727 def added(self):
728 def added(self):
728 return self._status[1]
729 return self._status[1]
729 def removed(self):
730 def removed(self):
730 return self._status[2]
731 return self._status[2]
731 def deleted(self):
732 def deleted(self):
732 return self._status[3]
733 return self._status[3]
733 def unknown(self):
734 def unknown(self):
734 assert self._unknown is not None # must call status first
735 assert self._unknown is not None # must call status first
735 return self._unknown
736 return self._unknown
736 def ignored(self):
737 def ignored(self):
737 assert self._ignored is not None # must call status first
738 assert self._ignored is not None # must call status first
738 return self._ignored
739 return self._ignored
739 def clean(self):
740 def clean(self):
740 assert self._clean is not None # must call status first
741 assert self._clean is not None # must call status first
741 return self._clean
742 return self._clean
742 def branch(self):
743 def branch(self):
743 return encoding.tolocal(self._extra['branch'])
744 return encoding.tolocal(self._extra['branch'])
744 def extra(self):
745 def extra(self):
745 return self._extra
746 return self._extra
746
747
747 def tags(self):
748 def tags(self):
748 t = []
749 t = []
749 for p in self.parents():
750 for p in self.parents():
750 t.extend(p.tags())
751 t.extend(p.tags())
751 return t
752 return t
752
753
753 def bookmarks(self):
754 def bookmarks(self):
754 b = []
755 b = []
755 for p in self.parents():
756 for p in self.parents():
756 b.extend(p.bookmarks())
757 b.extend(p.bookmarks())
757 return b
758 return b
758
759
759 def children(self):
760 def children(self):
760 return []
761 return []
761
762
762 def flags(self, path):
763 def flags(self, path):
763 if '_manifest' in self.__dict__:
764 if '_manifest' in self.__dict__:
764 try:
765 try:
765 return self._manifest.flags(path)
766 return self._manifest.flags(path)
766 except KeyError:
767 except KeyError:
767 return ''
768 return ''
768
769
769 orig = self._repo.dirstate.copies().get(path, path)
770 orig = self._repo.dirstate.copies().get(path, path)
770
771
771 def findflag(ctx):
772 def findflag(ctx):
772 mnode = ctx.changeset()[0]
773 mnode = ctx.changeset()[0]
773 node, flag = self._repo.manifest.find(mnode, orig)
774 node, flag = self._repo.manifest.find(mnode, orig)
774 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
775 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
775 try:
776 try:
776 return ff(path)
777 return ff(path)
777 except OSError:
778 except OSError:
778 pass
779 pass
779
780
780 flag = findflag(self._parents[0])
781 flag = findflag(self._parents[0])
781 if flag is None and len(self.parents()) > 1:
782 if flag is None and len(self.parents()) > 1:
782 flag = findflag(self._parents[1])
783 flag = findflag(self._parents[1])
783 if flag is None or self._repo.dirstate[path] == 'r':
784 if flag is None or self._repo.dirstate[path] == 'r':
784 return ''
785 return ''
785 return flag
786 return flag
786
787
787 def filectx(self, path, filelog=None):
788 def filectx(self, path, filelog=None):
788 """get a file context from the working directory"""
789 """get a file context from the working directory"""
789 return workingfilectx(self._repo, path, workingctx=self,
790 return workingfilectx(self._repo, path, workingctx=self,
790 filelog=filelog)
791 filelog=filelog)
791
792
792 def ancestor(self, c2):
793 def ancestor(self, c2):
793 """return the ancestor context of self and c2"""
794 """return the ancestor context of self and c2"""
794 return self._parents[0].ancestor(c2) # punt on two parents for now
795 return self._parents[0].ancestor(c2) # punt on two parents for now
795
796
796 def walk(self, match):
797 def walk(self, match):
797 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
798 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
798 True, False))
799 True, False))
799
800
800 def dirty(self, missing=False):
801 def dirty(self, missing=False):
801 "check whether a working directory is modified"
802 "check whether a working directory is modified"
802 # check subrepos first
803 # check subrepos first
803 for s in self.substate:
804 for s in self.substate:
804 if self.sub(s).dirty():
805 if self.sub(s).dirty():
805 return True
806 return True
806 # check current working dir
807 # check current working dir
807 return (self.p2() or self.branch() != self.p1().branch() or
808 return (self.p2() or self.branch() != self.p1().branch() or
808 self.modified() or self.added() or self.removed() or
809 self.modified() or self.added() or self.removed() or
809 (missing and self.deleted()))
810 (missing and self.deleted()))
810
811
811 def add(self, list, prefix=""):
812 def add(self, list, prefix=""):
812 join = lambda f: os.path.join(prefix, f)
813 join = lambda f: os.path.join(prefix, f)
813 wlock = self._repo.wlock()
814 wlock = self._repo.wlock()
814 ui, ds = self._repo.ui, self._repo.dirstate
815 ui, ds = self._repo.ui, self._repo.dirstate
815 try:
816 try:
816 rejected = []
817 rejected = []
817 for f in list:
818 for f in list:
818 scmutil.checkportable(ui, join(f))
819 scmutil.checkportable(ui, join(f))
819 p = self._repo.wjoin(f)
820 p = self._repo.wjoin(f)
820 try:
821 try:
821 st = os.lstat(p)
822 st = os.lstat(p)
822 except OSError:
823 except OSError:
823 ui.warn(_("%s does not exist!\n") % join(f))
824 ui.warn(_("%s does not exist!\n") % join(f))
824 rejected.append(f)
825 rejected.append(f)
825 continue
826 continue
826 if st.st_size > 10000000:
827 if st.st_size > 10000000:
827 ui.warn(_("%s: up to %d MB of RAM may be required "
828 ui.warn(_("%s: up to %d MB of RAM may be required "
828 "to manage this file\n"
829 "to manage this file\n"
829 "(use 'hg revert %s' to cancel the "
830 "(use 'hg revert %s' to cancel the "
830 "pending addition)\n")
831 "pending addition)\n")
831 % (f, 3 * st.st_size // 1000000, join(f)))
832 % (f, 3 * st.st_size // 1000000, join(f)))
832 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
833 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
833 ui.warn(_("%s not added: only files and symlinks "
834 ui.warn(_("%s not added: only files and symlinks "
834 "supported currently\n") % join(f))
835 "supported currently\n") % join(f))
835 rejected.append(p)
836 rejected.append(p)
836 elif ds[f] in 'amn':
837 elif ds[f] in 'amn':
837 ui.warn(_("%s already tracked!\n") % join(f))
838 ui.warn(_("%s already tracked!\n") % join(f))
838 elif ds[f] == 'r':
839 elif ds[f] == 'r':
839 ds.normallookup(f)
840 ds.normallookup(f)
840 else:
841 else:
841 ds.add(f)
842 ds.add(f)
842 return rejected
843 return rejected
843 finally:
844 finally:
844 wlock.release()
845 wlock.release()
845
846
846 def forget(self, files):
847 def forget(self, files):
847 wlock = self._repo.wlock()
848 wlock = self._repo.wlock()
848 try:
849 try:
849 for f in files:
850 for f in files:
850 if self._repo.dirstate[f] != 'a':
851 if self._repo.dirstate[f] != 'a':
851 self._repo.dirstate.remove(f)
852 self._repo.dirstate.remove(f)
852 elif f not in self._repo.dirstate:
853 elif f not in self._repo.dirstate:
853 self._repo.ui.warn(_("%s not tracked!\n") % f)
854 self._repo.ui.warn(_("%s not tracked!\n") % f)
854 else:
855 else:
855 self._repo.dirstate.drop(f)
856 self._repo.dirstate.drop(f)
856 finally:
857 finally:
857 wlock.release()
858 wlock.release()
858
859
859 def ancestors(self):
860 def ancestors(self):
860 for a in self._repo.changelog.ancestors(
861 for a in self._repo.changelog.ancestors(
861 *[p.rev() for p in self._parents]):
862 *[p.rev() for p in self._parents]):
862 yield changectx(self._repo, a)
863 yield changectx(self._repo, a)
863
864
864 def undelete(self, list):
865 def undelete(self, list):
865 pctxs = self.parents()
866 pctxs = self.parents()
866 wlock = self._repo.wlock()
867 wlock = self._repo.wlock()
867 try:
868 try:
868 for f in list:
869 for f in list:
869 if self._repo.dirstate[f] != 'r':
870 if self._repo.dirstate[f] != 'r':
870 self._repo.ui.warn(_("%s not removed!\n") % f)
871 self._repo.ui.warn(_("%s not removed!\n") % f)
871 else:
872 else:
872 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
873 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
873 t = fctx.data()
874 t = fctx.data()
874 self._repo.wwrite(f, t, fctx.flags())
875 self._repo.wwrite(f, t, fctx.flags())
875 self._repo.dirstate.normal(f)
876 self._repo.dirstate.normal(f)
876 finally:
877 finally:
877 wlock.release()
878 wlock.release()
878
879
879 def copy(self, source, dest):
880 def copy(self, source, dest):
880 p = self._repo.wjoin(dest)
881 p = self._repo.wjoin(dest)
881 if not os.path.lexists(p):
882 if not os.path.lexists(p):
882 self._repo.ui.warn(_("%s does not exist!\n") % dest)
883 self._repo.ui.warn(_("%s does not exist!\n") % dest)
883 elif not (os.path.isfile(p) or os.path.islink(p)):
884 elif not (os.path.isfile(p) or os.path.islink(p)):
884 self._repo.ui.warn(_("copy failed: %s is not a file or a "
885 self._repo.ui.warn(_("copy failed: %s is not a file or a "
885 "symbolic link\n") % dest)
886 "symbolic link\n") % dest)
886 else:
887 else:
887 wlock = self._repo.wlock()
888 wlock = self._repo.wlock()
888 try:
889 try:
889 if self._repo.dirstate[dest] in '?r':
890 if self._repo.dirstate[dest] in '?r':
890 self._repo.dirstate.add(dest)
891 self._repo.dirstate.add(dest)
891 self._repo.dirstate.copy(source, dest)
892 self._repo.dirstate.copy(source, dest)
892 finally:
893 finally:
893 wlock.release()
894 wlock.release()
894
895
895 class workingfilectx(filectx):
896 class workingfilectx(filectx):
896 """A workingfilectx object makes access to data related to a particular
897 """A workingfilectx object makes access to data related to a particular
897 file in the working directory convenient."""
898 file in the working directory convenient."""
898 def __init__(self, repo, path, filelog=None, workingctx=None):
899 def __init__(self, repo, path, filelog=None, workingctx=None):
899 """changeid can be a changeset revision, node, or tag.
900 """changeid can be a changeset revision, node, or tag.
900 fileid can be a file revision or node."""
901 fileid can be a file revision or node."""
901 self._repo = repo
902 self._repo = repo
902 self._path = path
903 self._path = path
903 self._changeid = None
904 self._changeid = None
904 self._filerev = self._filenode = None
905 self._filerev = self._filenode = None
905
906
906 if filelog:
907 if filelog:
907 self._filelog = filelog
908 self._filelog = filelog
908 if workingctx:
909 if workingctx:
909 self._changectx = workingctx
910 self._changectx = workingctx
910
911
911 @propertycache
912 @propertycache
912 def _changectx(self):
913 def _changectx(self):
913 return workingctx(self._repo)
914 return workingctx(self._repo)
914
915
915 def __nonzero__(self):
916 def __nonzero__(self):
916 return True
917 return True
917
918
918 def __str__(self):
919 def __str__(self):
919 return "%s@%s" % (self.path(), self._changectx)
920 return "%s@%s" % (self.path(), self._changectx)
920
921
921 def __repr__(self):
922 def __repr__(self):
922 return "<workingfilectx %s>" % str(self)
923 return "<workingfilectx %s>" % str(self)
923
924
924 def data(self):
925 def data(self):
925 return self._repo.wread(self._path)
926 return self._repo.wread(self._path)
926 def renamed(self):
927 def renamed(self):
927 rp = self._repo.dirstate.copied(self._path)
928 rp = self._repo.dirstate.copied(self._path)
928 if not rp:
929 if not rp:
929 return None
930 return None
930 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
931 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
931
932
932 def parents(self):
933 def parents(self):
933 '''return parent filectxs, following copies if necessary'''
934 '''return parent filectxs, following copies if necessary'''
934 def filenode(ctx, path):
935 def filenode(ctx, path):
935 return ctx._manifest.get(path, nullid)
936 return ctx._manifest.get(path, nullid)
936
937
937 path = self._path
938 path = self._path
938 fl = self._filelog
939 fl = self._filelog
939 pcl = self._changectx._parents
940 pcl = self._changectx._parents
940 renamed = self.renamed()
941 renamed = self.renamed()
941
942
942 if renamed:
943 if renamed:
943 pl = [renamed + (None,)]
944 pl = [renamed + (None,)]
944 else:
945 else:
945 pl = [(path, filenode(pcl[0], path), fl)]
946 pl = [(path, filenode(pcl[0], path), fl)]
946
947
947 for pc in pcl[1:]:
948 for pc in pcl[1:]:
948 pl.append((path, filenode(pc, path), fl))
949 pl.append((path, filenode(pc, path), fl))
949
950
950 return [filectx(self._repo, p, fileid=n, filelog=l)
951 return [filectx(self._repo, p, fileid=n, filelog=l)
951 for p, n, l in pl if n != nullid]
952 for p, n, l in pl if n != nullid]
952
953
953 def children(self):
954 def children(self):
954 return []
955 return []
955
956
956 def size(self):
957 def size(self):
957 return os.lstat(self._repo.wjoin(self._path)).st_size
958 return os.lstat(self._repo.wjoin(self._path)).st_size
958 def date(self):
959 def date(self):
959 t, tz = self._changectx.date()
960 t, tz = self._changectx.date()
960 try:
961 try:
961 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
962 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
962 except OSError, err:
963 except OSError, err:
963 if err.errno != errno.ENOENT:
964 if err.errno != errno.ENOENT:
964 raise
965 raise
965 return (t, tz)
966 return (t, tz)
966
967
967 def cmp(self, fctx):
968 def cmp(self, fctx):
968 """compare with other file context
969 """compare with other file context
969
970
970 returns True if different than fctx.
971 returns True if different than fctx.
971 """
972 """
972 # fctx should be a filectx (not a wfctx)
973 # fctx should be a filectx (not a wfctx)
973 # invert comparison to reuse the same code path
974 # invert comparison to reuse the same code path
974 return fctx.cmp(self)
975 return fctx.cmp(self)
975
976
976 class memctx(object):
977 class memctx(object):
977 """Use memctx to perform in-memory commits via localrepo.commitctx().
978 """Use memctx to perform in-memory commits via localrepo.commitctx().
978
979
979 Revision information is supplied at initialization time while
980 Revision information is supplied at initialization time while
980 related files data and is made available through a callback
981 related files data and is made available through a callback
981 mechanism. 'repo' is the current localrepo, 'parents' is a
982 mechanism. 'repo' is the current localrepo, 'parents' is a
982 sequence of two parent revisions identifiers (pass None for every
983 sequence of two parent revisions identifiers (pass None for every
983 missing parent), 'text' is the commit message and 'files' lists
984 missing parent), 'text' is the commit message and 'files' lists
984 names of files touched by the revision (normalized and relative to
985 names of files touched by the revision (normalized and relative to
985 repository root).
986 repository root).
986
987
987 filectxfn(repo, memctx, path) is a callable receiving the
988 filectxfn(repo, memctx, path) is a callable receiving the
988 repository, the current memctx object and the normalized path of
989 repository, the current memctx object and the normalized path of
989 requested file, relative to repository root. It is fired by the
990 requested file, relative to repository root. It is fired by the
990 commit function for every file in 'files', but calls order is
991 commit function for every file in 'files', but calls order is
991 undefined. If the file is available in the revision being
992 undefined. If the file is available in the revision being
992 committed (updated or added), filectxfn returns a memfilectx
993 committed (updated or added), filectxfn returns a memfilectx
993 object. If the file was removed, filectxfn raises an
994 object. If the file was removed, filectxfn raises an
994 IOError. Moved files are represented by marking the source file
995 IOError. Moved files are represented by marking the source file
995 removed and the new file added with copy information (see
996 removed and the new file added with copy information (see
996 memfilectx).
997 memfilectx).
997
998
998 user receives the committer name and defaults to current
999 user receives the committer name and defaults to current
999 repository username, date is the commit date in any format
1000 repository username, date is the commit date in any format
1000 supported by util.parsedate() and defaults to current date, extra
1001 supported by util.parsedate() and defaults to current date, extra
1001 is a dictionary of metadata or is left empty.
1002 is a dictionary of metadata or is left empty.
1002 """
1003 """
1003 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1004 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1004 date=None, extra=None):
1005 date=None, extra=None):
1005 self._repo = repo
1006 self._repo = repo
1006 self._rev = None
1007 self._rev = None
1007 self._node = None
1008 self._node = None
1008 self._text = text
1009 self._text = text
1009 self._date = date and util.parsedate(date) or util.makedate()
1010 self._date = date and util.parsedate(date) or util.makedate()
1010 self._user = user
1011 self._user = user
1011 parents = [(p or nullid) for p in parents]
1012 parents = [(p or nullid) for p in parents]
1012 p1, p2 = parents
1013 p1, p2 = parents
1013 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1014 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1014 files = sorted(set(files))
1015 files = sorted(set(files))
1015 self._status = [files, [], [], [], []]
1016 self._status = [files, [], [], [], []]
1016 self._filectxfn = filectxfn
1017 self._filectxfn = filectxfn
1017
1018
1018 self._extra = extra and extra.copy() or {}
1019 self._extra = extra and extra.copy() or {}
1019 if self._extra.get('branch', '') == '':
1020 if self._extra.get('branch', '') == '':
1020 self._extra['branch'] = 'default'
1021 self._extra['branch'] = 'default'
1021
1022
1022 def __str__(self):
1023 def __str__(self):
1023 return str(self._parents[0]) + "+"
1024 return str(self._parents[0]) + "+"
1024
1025
1025 def __int__(self):
1026 def __int__(self):
1026 return self._rev
1027 return self._rev
1027
1028
1028 def __nonzero__(self):
1029 def __nonzero__(self):
1029 return True
1030 return True
1030
1031
1031 def __getitem__(self, key):
1032 def __getitem__(self, key):
1032 return self.filectx(key)
1033 return self.filectx(key)
1033
1034
1034 def p1(self):
1035 def p1(self):
1035 return self._parents[0]
1036 return self._parents[0]
1036 def p2(self):
1037 def p2(self):
1037 return self._parents[1]
1038 return self._parents[1]
1038
1039
1039 def user(self):
1040 def user(self):
1040 return self._user or self._repo.ui.username()
1041 return self._user or self._repo.ui.username()
1041 def date(self):
1042 def date(self):
1042 return self._date
1043 return self._date
1043 def description(self):
1044 def description(self):
1044 return self._text
1045 return self._text
1045 def files(self):
1046 def files(self):
1046 return self.modified()
1047 return self.modified()
1047 def modified(self):
1048 def modified(self):
1048 return self._status[0]
1049 return self._status[0]
1049 def added(self):
1050 def added(self):
1050 return self._status[1]
1051 return self._status[1]
1051 def removed(self):
1052 def removed(self):
1052 return self._status[2]
1053 return self._status[2]
1053 def deleted(self):
1054 def deleted(self):
1054 return self._status[3]
1055 return self._status[3]
1055 def unknown(self):
1056 def unknown(self):
1056 return self._status[4]
1057 return self._status[4]
1057 def ignored(self):
1058 def ignored(self):
1058 return self._status[5]
1059 return self._status[5]
1059 def clean(self):
1060 def clean(self):
1060 return self._status[6]
1061 return self._status[6]
1061 def branch(self):
1062 def branch(self):
1062 return encoding.tolocal(self._extra['branch'])
1063 return encoding.tolocal(self._extra['branch'])
1063 def extra(self):
1064 def extra(self):
1064 return self._extra
1065 return self._extra
1065 def flags(self, f):
1066 def flags(self, f):
1066 return self[f].flags()
1067 return self[f].flags()
1067
1068
1068 def parents(self):
1069 def parents(self):
1069 """return contexts for each parent changeset"""
1070 """return contexts for each parent changeset"""
1070 return self._parents
1071 return self._parents
1071
1072
1072 def filectx(self, path, filelog=None):
1073 def filectx(self, path, filelog=None):
1073 """get a file context from the working directory"""
1074 """get a file context from the working directory"""
1074 return self._filectxfn(self._repo, self, path)
1075 return self._filectxfn(self._repo, self, path)
1075
1076
1076 def commit(self):
1077 def commit(self):
1077 """commit context to the repo"""
1078 """commit context to the repo"""
1078 return self._repo.commitctx(self)
1079 return self._repo.commitctx(self)
1079
1080
1080 class memfilectx(object):
1081 class memfilectx(object):
1081 """memfilectx represents an in-memory file to commit.
1082 """memfilectx represents an in-memory file to commit.
1082
1083
1083 See memctx for more details.
1084 See memctx for more details.
1084 """
1085 """
1085 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1086 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1086 """
1087 """
1087 path is the normalized file path relative to repository root.
1088 path is the normalized file path relative to repository root.
1088 data is the file content as a string.
1089 data is the file content as a string.
1089 islink is True if the file is a symbolic link.
1090 islink is True if the file is a symbolic link.
1090 isexec is True if the file is executable.
1091 isexec is True if the file is executable.
1091 copied is the source file path if current file was copied in the
1092 copied is the source file path if current file was copied in the
1092 revision being committed, or None."""
1093 revision being committed, or None."""
1093 self._path = path
1094 self._path = path
1094 self._data = data
1095 self._data = data
1095 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1096 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1096 self._copied = None
1097 self._copied = None
1097 if copied:
1098 if copied:
1098 self._copied = (copied, nullid)
1099 self._copied = (copied, nullid)
1099
1100
1100 def __nonzero__(self):
1101 def __nonzero__(self):
1101 return True
1102 return True
1102 def __str__(self):
1103 def __str__(self):
1103 return "%s@%s" % (self.path(), self._changectx)
1104 return "%s@%s" % (self.path(), self._changectx)
1104 def path(self):
1105 def path(self):
1105 return self._path
1106 return self._path
1106 def data(self):
1107 def data(self):
1107 return self._data
1108 return self._data
1108 def flags(self):
1109 def flags(self):
1109 return self._flags
1110 return self._flags
1110 def isexec(self):
1111 def isexec(self):
1111 return 'x' in self._flags
1112 return 'x' in self._flags
1112 def islink(self):
1113 def islink(self):
1113 return 'l' in self._flags
1114 return 'l' in self._flags
1114 def renamed(self):
1115 def renamed(self):
1115 return self._copied
1116 return self._copied
@@ -1,310 +1,311 b''
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
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 import re
8 import re
9 import scmutil, util
9 import scmutil, util
10 from i18n import _
10 from i18n import _
11
11
12 class match(object):
12 class match(object):
13 def __init__(self, root, cwd, patterns, include=[], exclude=[],
13 def __init__(self, root, cwd, patterns, include=[], exclude=[],
14 default='glob', exact=False, auditor=None):
14 default='glob', exact=False, auditor=None, ctx=None):
15 """build an object to match a set of file patterns
15 """build an object to match a set of file patterns
16
16
17 arguments:
17 arguments:
18 root - the canonical root of the tree you're matching against
18 root - the canonical root of the tree you're matching against
19 cwd - the current working directory, if relevant
19 cwd - the current working directory, if relevant
20 patterns - patterns to find
20 patterns - patterns to find
21 include - patterns to include
21 include - patterns to include
22 exclude - patterns to exclude
22 exclude - patterns to exclude
23 default - if a pattern in names has no explicit type, assume this one
23 default - if a pattern in names has no explicit type, assume this one
24 exact - patterns are actually literals
24 exact - patterns are actually literals
25
25
26 a pattern is one of:
26 a pattern is one of:
27 'glob:<glob>' - a glob relative to cwd
27 'glob:<glob>' - a glob relative to cwd
28 're:<regexp>' - a regular expression
28 're:<regexp>' - a regular expression
29 'path:<path>' - a path relative to canonroot
29 'path:<path>' - a path relative to canonroot
30 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
30 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
31 'relpath:<path>' - a path relative to cwd
31 'relpath:<path>' - a path relative to cwd
32 'relre:<regexp>' - a regexp that needn't match the start of a name
32 'relre:<regexp>' - a regexp that needn't match the start of a name
33 '<something>' - a pattern of the specified default type
33 '<something>' - a pattern of the specified default type
34 """
34 """
35
35
36 self._root = root
36 self._root = root
37 self._cwd = cwd
37 self._cwd = cwd
38 self._files = []
38 self._files = []
39 self._anypats = bool(include or exclude)
39 self._anypats = bool(include or exclude)
40 self._ctx = ctx
40
41
41 if include:
42 if include:
42 pats = _normalize(include, 'glob', root, cwd, auditor)
43 pats = _normalize(include, 'glob', root, cwd, auditor)
43 self.includepat, im = _buildmatch(pats, '(?:/|$)')
44 self.includepat, im = _buildmatch(pats, '(?:/|$)')
44 if exclude:
45 if exclude:
45 pats = _normalize(exclude, 'glob', root, cwd, auditor)
46 pats = _normalize(exclude, 'glob', root, cwd, auditor)
46 self.excludepat, em = _buildmatch(pats, '(?:/|$)')
47 self.excludepat, em = _buildmatch(pats, '(?:/|$)')
47 if exact:
48 if exact:
48 self._files = patterns
49 self._files = patterns
49 pm = self.exact
50 pm = self.exact
50 elif patterns:
51 elif patterns:
51 pats = _normalize(patterns, default, root, cwd, auditor)
52 pats = _normalize(patterns, default, root, cwd, auditor)
52 self._files = _roots(pats)
53 self._files = _roots(pats)
53 self._anypats = self._anypats or _anypats(pats)
54 self._anypats = self._anypats or _anypats(pats)
54 self.patternspat, pm = _buildmatch(pats, '$')
55 self.patternspat, pm = _buildmatch(pats, '$')
55
56
56 if patterns or exact:
57 if patterns or exact:
57 if include:
58 if include:
58 if exclude:
59 if exclude:
59 m = lambda f: im(f) and not em(f) and pm(f)
60 m = lambda f: im(f) and not em(f) and pm(f)
60 else:
61 else:
61 m = lambda f: im(f) and pm(f)
62 m = lambda f: im(f) and pm(f)
62 else:
63 else:
63 if exclude:
64 if exclude:
64 m = lambda f: not em(f) and pm(f)
65 m = lambda f: not em(f) and pm(f)
65 else:
66 else:
66 m = pm
67 m = pm
67 else:
68 else:
68 if include:
69 if include:
69 if exclude:
70 if exclude:
70 m = lambda f: im(f) and not em(f)
71 m = lambda f: im(f) and not em(f)
71 else:
72 else:
72 m = im
73 m = im
73 else:
74 else:
74 if exclude:
75 if exclude:
75 m = lambda f: not em(f)
76 m = lambda f: not em(f)
76 else:
77 else:
77 m = lambda f: True
78 m = lambda f: True
78
79
79 self.matchfn = m
80 self.matchfn = m
80 self._fmap = set(self._files)
81 self._fmap = set(self._files)
81
82
82 def __call__(self, fn):
83 def __call__(self, fn):
83 return self.matchfn(fn)
84 return self.matchfn(fn)
84 def __iter__(self):
85 def __iter__(self):
85 for f in self._files:
86 for f in self._files:
86 yield f
87 yield f
87 def bad(self, f, msg):
88 def bad(self, f, msg):
88 '''callback for each explicit file that can't be
89 '''callback for each explicit file that can't be
89 found/accessed, with an error message
90 found/accessed, with an error message
90 '''
91 '''
91 pass
92 pass
92 def dir(self, f):
93 def dir(self, f):
93 pass
94 pass
94 def missing(self, f):
95 def missing(self, f):
95 pass
96 pass
96 def exact(self, f):
97 def exact(self, f):
97 return f in self._fmap
98 return f in self._fmap
98 def rel(self, f):
99 def rel(self, f):
99 return util.pathto(self._root, self._cwd, f)
100 return util.pathto(self._root, self._cwd, f)
100 def files(self):
101 def files(self):
101 return self._files
102 return self._files
102 def anypats(self):
103 def anypats(self):
103 return self._anypats
104 return self._anypats
104
105
105 class exact(match):
106 class exact(match):
106 def __init__(self, root, cwd, files):
107 def __init__(self, root, cwd, files):
107 match.__init__(self, root, cwd, files, exact = True)
108 match.__init__(self, root, cwd, files, exact = True)
108
109
109 class always(match):
110 class always(match):
110 def __init__(self, root, cwd):
111 def __init__(self, root, cwd):
111 match.__init__(self, root, cwd, [])
112 match.__init__(self, root, cwd, [])
112
113
113 class narrowmatcher(match):
114 class narrowmatcher(match):
114 """Adapt a matcher to work on a subdirectory only.
115 """Adapt a matcher to work on a subdirectory only.
115
116
116 The paths are remapped to remove/insert the path as needed:
117 The paths are remapped to remove/insert the path as needed:
117
118
118 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
119 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
119 >>> m2 = narrowmatcher('sub', m1)
120 >>> m2 = narrowmatcher('sub', m1)
120 >>> bool(m2('a.txt'))
121 >>> bool(m2('a.txt'))
121 False
122 False
122 >>> bool(m2('b.txt'))
123 >>> bool(m2('b.txt'))
123 True
124 True
124 >>> bool(m2.matchfn('a.txt'))
125 >>> bool(m2.matchfn('a.txt'))
125 False
126 False
126 >>> bool(m2.matchfn('b.txt'))
127 >>> bool(m2.matchfn('b.txt'))
127 True
128 True
128 >>> m2.files()
129 >>> m2.files()
129 ['b.txt']
130 ['b.txt']
130 >>> m2.exact('b.txt')
131 >>> m2.exact('b.txt')
131 True
132 True
132 >>> m2.rel('b.txt')
133 >>> m2.rel('b.txt')
133 'b.txt'
134 'b.txt'
134 >>> def bad(f, msg):
135 >>> def bad(f, msg):
135 ... print "%s: %s" % (f, msg)
136 ... print "%s: %s" % (f, msg)
136 >>> m1.bad = bad
137 >>> m1.bad = bad
137 >>> m2.bad('x.txt', 'No such file')
138 >>> m2.bad('x.txt', 'No such file')
138 sub/x.txt: No such file
139 sub/x.txt: No such file
139 """
140 """
140
141
141 def __init__(self, path, matcher):
142 def __init__(self, path, matcher):
142 self._root = matcher._root
143 self._root = matcher._root
143 self._cwd = matcher._cwd
144 self._cwd = matcher._cwd
144 self._path = path
145 self._path = path
145 self._matcher = matcher
146 self._matcher = matcher
146
147
147 self._files = [f[len(path) + 1:] for f in matcher._files
148 self._files = [f[len(path) + 1:] for f in matcher._files
148 if f.startswith(path + "/")]
149 if f.startswith(path + "/")]
149 self._anypats = matcher._anypats
150 self._anypats = matcher._anypats
150 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
151 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
151 self._fmap = set(self._files)
152 self._fmap = set(self._files)
152
153
153 def bad(self, f, msg):
154 def bad(self, f, msg):
154 self._matcher.bad(self._path + "/" + f, msg)
155 self._matcher.bad(self._path + "/" + f, msg)
155
156
156 def patkind(pat):
157 def patkind(pat):
157 return _patsplit(pat, None)[0]
158 return _patsplit(pat, None)[0]
158
159
159 def _patsplit(pat, default):
160 def _patsplit(pat, default):
160 """Split a string into an optional pattern kind prefix and the
161 """Split a string into an optional pattern kind prefix and the
161 actual pattern."""
162 actual pattern."""
162 if ':' in pat:
163 if ':' in pat:
163 kind, val = pat.split(':', 1)
164 kind, val = pat.split(':', 1)
164 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
165 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
165 'listfile', 'listfile0'):
166 'listfile', 'listfile0'):
166 return kind, val
167 return kind, val
167 return default, pat
168 return default, pat
168
169
169 def _globre(pat):
170 def _globre(pat):
170 "convert a glob pattern into a regexp"
171 "convert a glob pattern into a regexp"
171 i, n = 0, len(pat)
172 i, n = 0, len(pat)
172 res = ''
173 res = ''
173 group = 0
174 group = 0
174 escape = re.escape
175 escape = re.escape
175 def peek():
176 def peek():
176 return i < n and pat[i]
177 return i < n and pat[i]
177 while i < n:
178 while i < n:
178 c = pat[i]
179 c = pat[i]
179 i += 1
180 i += 1
180 if c not in '*?[{},\\':
181 if c not in '*?[{},\\':
181 res += escape(c)
182 res += escape(c)
182 elif c == '*':
183 elif c == '*':
183 if peek() == '*':
184 if peek() == '*':
184 i += 1
185 i += 1
185 res += '.*'
186 res += '.*'
186 else:
187 else:
187 res += '[^/]*'
188 res += '[^/]*'
188 elif c == '?':
189 elif c == '?':
189 res += '.'
190 res += '.'
190 elif c == '[':
191 elif c == '[':
191 j = i
192 j = i
192 if j < n and pat[j] in '!]':
193 if j < n and pat[j] in '!]':
193 j += 1
194 j += 1
194 while j < n and pat[j] != ']':
195 while j < n and pat[j] != ']':
195 j += 1
196 j += 1
196 if j >= n:
197 if j >= n:
197 res += '\\['
198 res += '\\['
198 else:
199 else:
199 stuff = pat[i:j].replace('\\','\\\\')
200 stuff = pat[i:j].replace('\\','\\\\')
200 i = j + 1
201 i = j + 1
201 if stuff[0] == '!':
202 if stuff[0] == '!':
202 stuff = '^' + stuff[1:]
203 stuff = '^' + stuff[1:]
203 elif stuff[0] == '^':
204 elif stuff[0] == '^':
204 stuff = '\\' + stuff
205 stuff = '\\' + stuff
205 res = '%s[%s]' % (res, stuff)
206 res = '%s[%s]' % (res, stuff)
206 elif c == '{':
207 elif c == '{':
207 group += 1
208 group += 1
208 res += '(?:'
209 res += '(?:'
209 elif c == '}' and group:
210 elif c == '}' and group:
210 res += ')'
211 res += ')'
211 group -= 1
212 group -= 1
212 elif c == ',' and group:
213 elif c == ',' and group:
213 res += '|'
214 res += '|'
214 elif c == '\\':
215 elif c == '\\':
215 p = peek()
216 p = peek()
216 if p:
217 if p:
217 i += 1
218 i += 1
218 res += escape(p)
219 res += escape(p)
219 else:
220 else:
220 res += escape(c)
221 res += escape(c)
221 else:
222 else:
222 res += escape(c)
223 res += escape(c)
223 return res
224 return res
224
225
225 def _regex(kind, name, tail):
226 def _regex(kind, name, tail):
226 '''convert a pattern into a regular expression'''
227 '''convert a pattern into a regular expression'''
227 if not name:
228 if not name:
228 return ''
229 return ''
229 if kind == 're':
230 if kind == 're':
230 return name
231 return name
231 elif kind == 'path':
232 elif kind == 'path':
232 return '^' + re.escape(name) + '(?:/|$)'
233 return '^' + re.escape(name) + '(?:/|$)'
233 elif kind == 'relglob':
234 elif kind == 'relglob':
234 return '(?:|.*/)' + _globre(name) + tail
235 return '(?:|.*/)' + _globre(name) + tail
235 elif kind == 'relpath':
236 elif kind == 'relpath':
236 return re.escape(name) + '(?:/|$)'
237 return re.escape(name) + '(?:/|$)'
237 elif kind == 'relre':
238 elif kind == 'relre':
238 if name.startswith('^'):
239 if name.startswith('^'):
239 return name
240 return name
240 return '.*' + name
241 return '.*' + name
241 return _globre(name) + tail
242 return _globre(name) + tail
242
243
243 def _buildmatch(pats, tail):
244 def _buildmatch(pats, tail):
244 """build a matching function from a set of patterns"""
245 """build a matching function from a set of patterns"""
245 try:
246 try:
246 pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats])
247 pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats])
247 if len(pat) > 20000:
248 if len(pat) > 20000:
248 raise OverflowError()
249 raise OverflowError()
249 return pat, re.compile(pat).match
250 return pat, re.compile(pat).match
250 except OverflowError:
251 except OverflowError:
251 # We're using a Python with a tiny regex engine and we
252 # We're using a Python with a tiny regex engine and we
252 # made it explode, so we'll divide the pattern list in two
253 # made it explode, so we'll divide the pattern list in two
253 # until it works
254 # until it works
254 l = len(pats)
255 l = len(pats)
255 if l < 2:
256 if l < 2:
256 raise
257 raise
257 pata, a = _buildmatch(pats[:l//2], tail)
258 pata, a = _buildmatch(pats[:l//2], tail)
258 patb, b = _buildmatch(pats[l//2:], tail)
259 patb, b = _buildmatch(pats[l//2:], tail)
259 return pat, lambda s: a(s) or b(s)
260 return pat, lambda s: a(s) or b(s)
260 except re.error:
261 except re.error:
261 for k, p in pats:
262 for k, p in pats:
262 try:
263 try:
263 re.compile('(?:%s)' % _regex(k, p, tail))
264 re.compile('(?:%s)' % _regex(k, p, tail))
264 except re.error:
265 except re.error:
265 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
266 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
266 raise util.Abort(_("invalid pattern"))
267 raise util.Abort(_("invalid pattern"))
267
268
268 def _normalize(names, default, root, cwd, auditor):
269 def _normalize(names, default, root, cwd, auditor):
269 pats = []
270 pats = []
270 for kind, name in [_patsplit(p, default) for p in names]:
271 for kind, name in [_patsplit(p, default) for p in names]:
271 if kind in ('glob', 'relpath'):
272 if kind in ('glob', 'relpath'):
272 name = scmutil.canonpath(root, cwd, name, auditor)
273 name = scmutil.canonpath(root, cwd, name, auditor)
273 elif kind in ('relglob', 'path'):
274 elif kind in ('relglob', 'path'):
274 name = util.normpath(name)
275 name = util.normpath(name)
275 elif kind in ('listfile', 'listfile0'):
276 elif kind in ('listfile', 'listfile0'):
276 try:
277 try:
277 files = util.readfile(name)
278 files = util.readfile(name)
278 if kind == 'listfile0':
279 if kind == 'listfile0':
279 files = files.split('\0')
280 files = files.split('\0')
280 else:
281 else:
281 files = files.splitlines()
282 files = files.splitlines()
282 files = [f for f in files if f]
283 files = [f for f in files if f]
283 except EnvironmentError:
284 except EnvironmentError:
284 raise util.Abort(_("unable to read file list (%s)") % name)
285 raise util.Abort(_("unable to read file list (%s)") % name)
285 pats += _normalize(files, default, root, cwd, auditor)
286 pats += _normalize(files, default, root, cwd, auditor)
286 continue
287 continue
287
288
288 pats.append((kind, name))
289 pats.append((kind, name))
289 return pats
290 return pats
290
291
291 def _roots(patterns):
292 def _roots(patterns):
292 r = []
293 r = []
293 for kind, name in patterns:
294 for kind, name in patterns:
294 if kind == 'glob': # find the non-glob prefix
295 if kind == 'glob': # find the non-glob prefix
295 root = []
296 root = []
296 for p in name.split('/'):
297 for p in name.split('/'):
297 if '[' in p or '{' in p or '*' in p or '?' in p:
298 if '[' in p or '{' in p or '*' in p or '?' in p:
298 break
299 break
299 root.append(p)
300 root.append(p)
300 r.append('/'.join(root) or '.')
301 r.append('/'.join(root) or '.')
301 elif kind in ('relpath', 'path'):
302 elif kind in ('relpath', 'path'):
302 r.append(name or '.')
303 r.append(name or '.')
303 elif kind == 'relglob':
304 elif kind == 'relglob':
304 r.append('.')
305 r.append('.')
305 return r
306 return r
306
307
307 def _anypats(patterns):
308 def _anypats(patterns):
308 for kind, name in patterns:
309 for kind, name in patterns:
309 if kind in ('glob', 're', 'relglob', 'relre'):
310 if kind in ('glob', 're', 'relglob', 'relre'):
310 return True
311 return True
General Comments 0
You need to be logged in to leave comments. Login now