##// END OF EJS Templates
context: add a match builder method...
Matt Mackall -
r14669:2d2604ad default
parent child Browse files
Show More
@@ -1,1109 +1,1115
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 os, errno, stat
12 import os, errno, stat
12
13
13 propertycache = util.propertycache
14 propertycache = util.propertycache
14
15
15 class changectx(object):
16 class changectx(object):
16 """A changecontext object makes access to data related to a particular
17 """A changecontext object makes access to data related to a particular
17 changeset convenient."""
18 changeset convenient."""
18 def __init__(self, repo, changeid=''):
19 def __init__(self, repo, changeid=''):
19 """changeid is a revision number, node, or tag"""
20 """changeid is a revision number, node, or tag"""
20 if changeid == '':
21 if changeid == '':
21 changeid = '.'
22 changeid = '.'
22 self._repo = repo
23 self._repo = repo
23 if isinstance(changeid, (long, int)):
24 if isinstance(changeid, (long, int)):
24 self._rev = changeid
25 self._rev = changeid
25 self._node = self._repo.changelog.node(changeid)
26 self._node = self._repo.changelog.node(changeid)
26 else:
27 else:
27 self._node = self._repo.lookup(changeid)
28 self._node = self._repo.lookup(changeid)
28 self._rev = self._repo.changelog.rev(self._node)
29 self._rev = self._repo.changelog.rev(self._node)
29
30
30 def __str__(self):
31 def __str__(self):
31 return short(self.node())
32 return short(self.node())
32
33
33 def __int__(self):
34 def __int__(self):
34 return self.rev()
35 return self.rev()
35
36
36 def __repr__(self):
37 def __repr__(self):
37 return "<changectx %s>" % str(self)
38 return "<changectx %s>" % str(self)
38
39
39 def __hash__(self):
40 def __hash__(self):
40 try:
41 try:
41 return hash(self._rev)
42 return hash(self._rev)
42 except AttributeError:
43 except AttributeError:
43 return id(self)
44 return id(self)
44
45
45 def __eq__(self, other):
46 def __eq__(self, other):
46 try:
47 try:
47 return self._rev == other._rev
48 return self._rev == other._rev
48 except AttributeError:
49 except AttributeError:
49 return False
50 return False
50
51
51 def __ne__(self, other):
52 def __ne__(self, other):
52 return not (self == other)
53 return not (self == other)
53
54
54 def __nonzero__(self):
55 def __nonzero__(self):
55 return self._rev != nullrev
56 return self._rev != nullrev
56
57
57 @propertycache
58 @propertycache
58 def _changeset(self):
59 def _changeset(self):
59 return self._repo.changelog.read(self.node())
60 return self._repo.changelog.read(self.node())
60
61
61 @propertycache
62 @propertycache
62 def _manifest(self):
63 def _manifest(self):
63 return self._repo.manifest.read(self._changeset[0])
64 return self._repo.manifest.read(self._changeset[0])
64
65
65 @propertycache
66 @propertycache
66 def _manifestdelta(self):
67 def _manifestdelta(self):
67 return self._repo.manifest.readdelta(self._changeset[0])
68 return self._repo.manifest.readdelta(self._changeset[0])
68
69
69 @propertycache
70 @propertycache
70 def _parents(self):
71 def _parents(self):
71 p = self._repo.changelog.parentrevs(self._rev)
72 p = self._repo.changelog.parentrevs(self._rev)
72 if p[1] == nullrev:
73 if p[1] == nullrev:
73 p = p[:-1]
74 p = p[:-1]
74 return [changectx(self._repo, x) for x in p]
75 return [changectx(self._repo, x) for x in p]
75
76
76 @propertycache
77 @propertycache
77 def substate(self):
78 def substate(self):
78 return subrepo.state(self, self._repo.ui)
79 return subrepo.state(self, self._repo.ui)
79
80
80 def __contains__(self, key):
81 def __contains__(self, key):
81 return key in self._manifest
82 return key in self._manifest
82
83
83 def __getitem__(self, key):
84 def __getitem__(self, key):
84 return self.filectx(key)
85 return self.filectx(key)
85
86
86 def __iter__(self):
87 def __iter__(self):
87 for f in sorted(self._manifest):
88 for f in sorted(self._manifest):
88 yield f
89 yield f
89
90
90 def changeset(self):
91 def changeset(self):
91 return self._changeset
92 return self._changeset
92 def manifest(self):
93 def manifest(self):
93 return self._manifest
94 return self._manifest
94 def manifestnode(self):
95 def manifestnode(self):
95 return self._changeset[0]
96 return self._changeset[0]
96
97
97 def rev(self):
98 def rev(self):
98 return self._rev
99 return self._rev
99 def node(self):
100 def node(self):
100 return self._node
101 return self._node
101 def hex(self):
102 def hex(self):
102 return hex(self._node)
103 return hex(self._node)
103 def user(self):
104 def user(self):
104 return self._changeset[1]
105 return self._changeset[1]
105 def date(self):
106 def date(self):
106 return self._changeset[2]
107 return self._changeset[2]
107 def files(self):
108 def files(self):
108 return self._changeset[3]
109 return self._changeset[3]
109 def description(self):
110 def description(self):
110 return self._changeset[4]
111 return self._changeset[4]
111 def branch(self):
112 def branch(self):
112 return encoding.tolocal(self._changeset[5].get("branch"))
113 return encoding.tolocal(self._changeset[5].get("branch"))
113 def extra(self):
114 def extra(self):
114 return self._changeset[5]
115 return self._changeset[5]
115 def tags(self):
116 def tags(self):
116 return self._repo.nodetags(self._node)
117 return self._repo.nodetags(self._node)
117 def bookmarks(self):
118 def bookmarks(self):
118 return self._repo.nodebookmarks(self._node)
119 return self._repo.nodebookmarks(self._node)
119 def hidden(self):
120 def hidden(self):
120 return self._rev in self._repo.changelog.hiddenrevs
121 return self._rev in self._repo.changelog.hiddenrevs
121
122
122 def parents(self):
123 def parents(self):
123 """return contexts for each parent changeset"""
124 """return contexts for each parent changeset"""
124 return self._parents
125 return self._parents
125
126
126 def p1(self):
127 def p1(self):
127 return self._parents[0]
128 return self._parents[0]
128
129
129 def p2(self):
130 def p2(self):
130 if len(self._parents) == 2:
131 if len(self._parents) == 2:
131 return self._parents[1]
132 return self._parents[1]
132 return changectx(self._repo, -1)
133 return changectx(self._repo, -1)
133
134
134 def children(self):
135 def children(self):
135 """return contexts for each child changeset"""
136 """return contexts for each child changeset"""
136 c = self._repo.changelog.children(self._node)
137 c = self._repo.changelog.children(self._node)
137 return [changectx(self._repo, x) for x in c]
138 return [changectx(self._repo, x) for x in c]
138
139
139 def ancestors(self):
140 def ancestors(self):
140 for a in self._repo.changelog.ancestors(self._rev):
141 for a in self._repo.changelog.ancestors(self._rev):
141 yield changectx(self._repo, a)
142 yield changectx(self._repo, a)
142
143
143 def descendants(self):
144 def descendants(self):
144 for d in self._repo.changelog.descendants(self._rev):
145 for d in self._repo.changelog.descendants(self._rev):
145 yield changectx(self._repo, d)
146 yield changectx(self._repo, d)
146
147
147 def _fileinfo(self, path):
148 def _fileinfo(self, path):
148 if '_manifest' in self.__dict__:
149 if '_manifest' in self.__dict__:
149 try:
150 try:
150 return self._manifest[path], self._manifest.flags(path)
151 return self._manifest[path], self._manifest.flags(path)
151 except KeyError:
152 except KeyError:
152 raise error.LookupError(self._node, path,
153 raise error.LookupError(self._node, path,
153 _('not found in manifest'))
154 _('not found in manifest'))
154 if '_manifestdelta' in self.__dict__ or path in self.files():
155 if '_manifestdelta' in self.__dict__ or path in self.files():
155 if path in self._manifestdelta:
156 if path in self._manifestdelta:
156 return self._manifestdelta[path], self._manifestdelta.flags(path)
157 return self._manifestdelta[path], self._manifestdelta.flags(path)
157 node, flag = self._repo.manifest.find(self._changeset[0], path)
158 node, flag = self._repo.manifest.find(self._changeset[0], path)
158 if not node:
159 if not node:
159 raise error.LookupError(self._node, path,
160 raise error.LookupError(self._node, path,
160 _('not found in manifest'))
161 _('not found in manifest'))
161
162
162 return node, flag
163 return node, flag
163
164
164 def filenode(self, path):
165 def filenode(self, path):
165 return self._fileinfo(path)[0]
166 return self._fileinfo(path)[0]
166
167
167 def flags(self, path):
168 def flags(self, path):
168 try:
169 try:
169 return self._fileinfo(path)[1]
170 return self._fileinfo(path)[1]
170 except error.LookupError:
171 except error.LookupError:
171 return ''
172 return ''
172
173
173 def filectx(self, path, fileid=None, filelog=None):
174 def filectx(self, path, fileid=None, filelog=None):
174 """get a file context from this changeset"""
175 """get a file context from this changeset"""
175 if fileid is None:
176 if fileid is None:
176 fileid = self.filenode(path)
177 fileid = self.filenode(path)
177 return filectx(self._repo, path, fileid=fileid,
178 return filectx(self._repo, path, fileid=fileid,
178 changectx=self, filelog=filelog)
179 changectx=self, filelog=filelog)
179
180
180 def ancestor(self, c2):
181 def ancestor(self, c2):
181 """
182 """
182 return the ancestor context of self and c2
183 return the ancestor context of self and c2
183 """
184 """
184 # deal with workingctxs
185 # deal with workingctxs
185 n2 = c2._node
186 n2 = c2._node
186 if n2 is None:
187 if n2 is None:
187 n2 = c2._parents[0]._node
188 n2 = c2._parents[0]._node
188 n = self._repo.changelog.ancestor(self._node, n2)
189 n = self._repo.changelog.ancestor(self._node, n2)
189 return changectx(self._repo, n)
190 return changectx(self._repo, n)
190
191
191 def walk(self, match):
192 def walk(self, match):
192 fset = set(match.files())
193 fset = set(match.files())
193 # for dirstate.walk, files=['.'] means "walk the whole tree".
194 # for dirstate.walk, files=['.'] means "walk the whole tree".
194 # follow that here, too
195 # follow that here, too
195 fset.discard('.')
196 fset.discard('.')
196 for fn in self:
197 for fn in self:
197 for ffn in fset:
198 for ffn in fset:
198 # match if the file is the exact name or a directory
199 # match if the file is the exact name or a directory
199 if ffn == fn or fn.startswith("%s/" % ffn):
200 if ffn == fn or fn.startswith("%s/" % ffn):
200 fset.remove(ffn)
201 fset.remove(ffn)
201 break
202 break
202 if match(fn):
203 if match(fn):
203 yield fn
204 yield fn
204 for fn in sorted(fset):
205 for fn in sorted(fset):
205 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):
206 yield fn
207 yield fn
207
208
208 def sub(self, path):
209 def sub(self, path):
209 return subrepo.subrepo(self, path)
210 return subrepo.subrepo(self, path)
210
211
212 def match(self, pats=[], include=None, exclude=None, default='glob'):
213 r = self._repo
214 return matchmod.match(r.root, r.getcwd(), pats,
215 include, exclude, default, auditor=r.auditor)
216
211 def diff(self, ctx2=None, match=None, **opts):
217 def diff(self, ctx2=None, match=None, **opts):
212 """Returns a diff generator for the given contexts and matcher"""
218 """Returns a diff generator for the given contexts and matcher"""
213 if ctx2 is None:
219 if ctx2 is None:
214 ctx2 = self.p1()
220 ctx2 = self.p1()
215 if ctx2 is not None and not isinstance(ctx2, changectx):
221 if ctx2 is not None and not isinstance(ctx2, changectx):
216 ctx2 = self._repo[ctx2]
222 ctx2 = self._repo[ctx2]
217 diffopts = patch.diffopts(self._repo.ui, opts)
223 diffopts = patch.diffopts(self._repo.ui, opts)
218 return patch.diff(self._repo, ctx2.node(), self.node(),
224 return patch.diff(self._repo, ctx2.node(), self.node(),
219 match=match, opts=diffopts)
225 match=match, opts=diffopts)
220
226
221 class filectx(object):
227 class filectx(object):
222 """A filecontext object makes access to data related to a particular
228 """A filecontext object makes access to data related to a particular
223 filerevision convenient."""
229 filerevision convenient."""
224 def __init__(self, repo, path, changeid=None, fileid=None,
230 def __init__(self, repo, path, changeid=None, fileid=None,
225 filelog=None, changectx=None):
231 filelog=None, changectx=None):
226 """changeid can be a changeset revision, node, or tag.
232 """changeid can be a changeset revision, node, or tag.
227 fileid can be a file revision or node."""
233 fileid can be a file revision or node."""
228 self._repo = repo
234 self._repo = repo
229 self._path = path
235 self._path = path
230
236
231 assert (changeid is not None
237 assert (changeid is not None
232 or fileid is not None
238 or fileid is not None
233 or changectx is not None), \
239 or changectx is not None), \
234 ("bad args: changeid=%r, fileid=%r, changectx=%r"
240 ("bad args: changeid=%r, fileid=%r, changectx=%r"
235 % (changeid, fileid, changectx))
241 % (changeid, fileid, changectx))
236
242
237 if filelog:
243 if filelog:
238 self._filelog = filelog
244 self._filelog = filelog
239
245
240 if changeid is not None:
246 if changeid is not None:
241 self._changeid = changeid
247 self._changeid = changeid
242 if changectx is not None:
248 if changectx is not None:
243 self._changectx = changectx
249 self._changectx = changectx
244 if fileid is not None:
250 if fileid is not None:
245 self._fileid = fileid
251 self._fileid = fileid
246
252
247 @propertycache
253 @propertycache
248 def _changectx(self):
254 def _changectx(self):
249 return changectx(self._repo, self._changeid)
255 return changectx(self._repo, self._changeid)
250
256
251 @propertycache
257 @propertycache
252 def _filelog(self):
258 def _filelog(self):
253 return self._repo.file(self._path)
259 return self._repo.file(self._path)
254
260
255 @propertycache
261 @propertycache
256 def _changeid(self):
262 def _changeid(self):
257 if '_changectx' in self.__dict__:
263 if '_changectx' in self.__dict__:
258 return self._changectx.rev()
264 return self._changectx.rev()
259 else:
265 else:
260 return self._filelog.linkrev(self._filerev)
266 return self._filelog.linkrev(self._filerev)
261
267
262 @propertycache
268 @propertycache
263 def _filenode(self):
269 def _filenode(self):
264 if '_fileid' in self.__dict__:
270 if '_fileid' in self.__dict__:
265 return self._filelog.lookup(self._fileid)
271 return self._filelog.lookup(self._fileid)
266 else:
272 else:
267 return self._changectx.filenode(self._path)
273 return self._changectx.filenode(self._path)
268
274
269 @propertycache
275 @propertycache
270 def _filerev(self):
276 def _filerev(self):
271 return self._filelog.rev(self._filenode)
277 return self._filelog.rev(self._filenode)
272
278
273 @propertycache
279 @propertycache
274 def _repopath(self):
280 def _repopath(self):
275 return self._path
281 return self._path
276
282
277 def __nonzero__(self):
283 def __nonzero__(self):
278 try:
284 try:
279 self._filenode
285 self._filenode
280 return True
286 return True
281 except error.LookupError:
287 except error.LookupError:
282 # file is missing
288 # file is missing
283 return False
289 return False
284
290
285 def __str__(self):
291 def __str__(self):
286 return "%s@%s" % (self.path(), short(self.node()))
292 return "%s@%s" % (self.path(), short(self.node()))
287
293
288 def __repr__(self):
294 def __repr__(self):
289 return "<filectx %s>" % str(self)
295 return "<filectx %s>" % str(self)
290
296
291 def __hash__(self):
297 def __hash__(self):
292 try:
298 try:
293 return hash((self._path, self._filenode))
299 return hash((self._path, self._filenode))
294 except AttributeError:
300 except AttributeError:
295 return id(self)
301 return id(self)
296
302
297 def __eq__(self, other):
303 def __eq__(self, other):
298 try:
304 try:
299 return (self._path == other._path
305 return (self._path == other._path
300 and self._filenode == other._filenode)
306 and self._filenode == other._filenode)
301 except AttributeError:
307 except AttributeError:
302 return False
308 return False
303
309
304 def __ne__(self, other):
310 def __ne__(self, other):
305 return not (self == other)
311 return not (self == other)
306
312
307 def filectx(self, fileid):
313 def filectx(self, fileid):
308 '''opens an arbitrary revision of the file without
314 '''opens an arbitrary revision of the file without
309 opening a new filelog'''
315 opening a new filelog'''
310 return filectx(self._repo, self._path, fileid=fileid,
316 return filectx(self._repo, self._path, fileid=fileid,
311 filelog=self._filelog)
317 filelog=self._filelog)
312
318
313 def filerev(self):
319 def filerev(self):
314 return self._filerev
320 return self._filerev
315 def filenode(self):
321 def filenode(self):
316 return self._filenode
322 return self._filenode
317 def flags(self):
323 def flags(self):
318 return self._changectx.flags(self._path)
324 return self._changectx.flags(self._path)
319 def filelog(self):
325 def filelog(self):
320 return self._filelog
326 return self._filelog
321
327
322 def rev(self):
328 def rev(self):
323 if '_changectx' in self.__dict__:
329 if '_changectx' in self.__dict__:
324 return self._changectx.rev()
330 return self._changectx.rev()
325 if '_changeid' in self.__dict__:
331 if '_changeid' in self.__dict__:
326 return self._changectx.rev()
332 return self._changectx.rev()
327 return self._filelog.linkrev(self._filerev)
333 return self._filelog.linkrev(self._filerev)
328
334
329 def linkrev(self):
335 def linkrev(self):
330 return self._filelog.linkrev(self._filerev)
336 return self._filelog.linkrev(self._filerev)
331 def node(self):
337 def node(self):
332 return self._changectx.node()
338 return self._changectx.node()
333 def hex(self):
339 def hex(self):
334 return hex(self.node())
340 return hex(self.node())
335 def user(self):
341 def user(self):
336 return self._changectx.user()
342 return self._changectx.user()
337 def date(self):
343 def date(self):
338 return self._changectx.date()
344 return self._changectx.date()
339 def files(self):
345 def files(self):
340 return self._changectx.files()
346 return self._changectx.files()
341 def description(self):
347 def description(self):
342 return self._changectx.description()
348 return self._changectx.description()
343 def branch(self):
349 def branch(self):
344 return self._changectx.branch()
350 return self._changectx.branch()
345 def extra(self):
351 def extra(self):
346 return self._changectx.extra()
352 return self._changectx.extra()
347 def manifest(self):
353 def manifest(self):
348 return self._changectx.manifest()
354 return self._changectx.manifest()
349 def changectx(self):
355 def changectx(self):
350 return self._changectx
356 return self._changectx
351
357
352 def data(self):
358 def data(self):
353 return self._filelog.read(self._filenode)
359 return self._filelog.read(self._filenode)
354 def path(self):
360 def path(self):
355 return self._path
361 return self._path
356 def size(self):
362 def size(self):
357 return self._filelog.size(self._filerev)
363 return self._filelog.size(self._filerev)
358
364
359 def cmp(self, fctx):
365 def cmp(self, fctx):
360 """compare with other file context
366 """compare with other file context
361
367
362 returns True if different than fctx.
368 returns True if different than fctx.
363 """
369 """
364 if (fctx._filerev is None and self._repo._encodefilterpats
370 if (fctx._filerev is None and self._repo._encodefilterpats
365 or self.size() == fctx.size()):
371 or self.size() == fctx.size()):
366 return self._filelog.cmp(self._filenode, fctx.data())
372 return self._filelog.cmp(self._filenode, fctx.data())
367
373
368 return True
374 return True
369
375
370 def renamed(self):
376 def renamed(self):
371 """check if file was actually renamed in this changeset revision
377 """check if file was actually renamed in this changeset revision
372
378
373 If rename logged in file revision, we report copy for changeset only
379 If rename logged in file revision, we report copy for changeset only
374 if file revisions linkrev points back to the changeset in question
380 if file revisions linkrev points back to the changeset in question
375 or both changeset parents contain different file revisions.
381 or both changeset parents contain different file revisions.
376 """
382 """
377
383
378 renamed = self._filelog.renamed(self._filenode)
384 renamed = self._filelog.renamed(self._filenode)
379 if not renamed:
385 if not renamed:
380 return renamed
386 return renamed
381
387
382 if self.rev() == self.linkrev():
388 if self.rev() == self.linkrev():
383 return renamed
389 return renamed
384
390
385 name = self.path()
391 name = self.path()
386 fnode = self._filenode
392 fnode = self._filenode
387 for p in self._changectx.parents():
393 for p in self._changectx.parents():
388 try:
394 try:
389 if fnode == p.filenode(name):
395 if fnode == p.filenode(name):
390 return None
396 return None
391 except error.LookupError:
397 except error.LookupError:
392 pass
398 pass
393 return renamed
399 return renamed
394
400
395 def parents(self):
401 def parents(self):
396 p = self._path
402 p = self._path
397 fl = self._filelog
403 fl = self._filelog
398 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
404 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
399
405
400 r = self._filelog.renamed(self._filenode)
406 r = self._filelog.renamed(self._filenode)
401 if r:
407 if r:
402 pl[0] = (r[0], r[1], None)
408 pl[0] = (r[0], r[1], None)
403
409
404 return [filectx(self._repo, p, fileid=n, filelog=l)
410 return [filectx(self._repo, p, fileid=n, filelog=l)
405 for p, n, l in pl if n != nullid]
411 for p, n, l in pl if n != nullid]
406
412
407 def p1(self):
413 def p1(self):
408 return self.parents()[0]
414 return self.parents()[0]
409
415
410 def p2(self):
416 def p2(self):
411 p = self.parents()
417 p = self.parents()
412 if len(p) == 2:
418 if len(p) == 2:
413 return p[1]
419 return p[1]
414 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
420 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
415
421
416 def children(self):
422 def children(self):
417 # hard for renames
423 # hard for renames
418 c = self._filelog.children(self._filenode)
424 c = self._filelog.children(self._filenode)
419 return [filectx(self._repo, self._path, fileid=x,
425 return [filectx(self._repo, self._path, fileid=x,
420 filelog=self._filelog) for x in c]
426 filelog=self._filelog) for x in c]
421
427
422 def annotate(self, follow=False, linenumber=None):
428 def annotate(self, follow=False, linenumber=None):
423 '''returns a list of tuples of (ctx, line) for each line
429 '''returns a list of tuples of (ctx, line) for each line
424 in the file, where ctx is the filectx of the node where
430 in the file, where ctx is the filectx of the node where
425 that line was last changed.
431 that line was last changed.
426 This returns tuples of ((ctx, linenumber), line) for each line,
432 This returns tuples of ((ctx, linenumber), line) for each line,
427 if "linenumber" parameter is NOT "None".
433 if "linenumber" parameter is NOT "None".
428 In such tuples, linenumber means one at the first appearance
434 In such tuples, linenumber means one at the first appearance
429 in the managed file.
435 in the managed file.
430 To reduce annotation cost,
436 To reduce annotation cost,
431 this returns fixed value(False is used) as linenumber,
437 this returns fixed value(False is used) as linenumber,
432 if "linenumber" parameter is "False".'''
438 if "linenumber" parameter is "False".'''
433
439
434 def decorate_compat(text, rev):
440 def decorate_compat(text, rev):
435 return ([rev] * len(text.splitlines()), text)
441 return ([rev] * len(text.splitlines()), text)
436
442
437 def without_linenumber(text, rev):
443 def without_linenumber(text, rev):
438 return ([(rev, False)] * len(text.splitlines()), text)
444 return ([(rev, False)] * len(text.splitlines()), text)
439
445
440 def with_linenumber(text, rev):
446 def with_linenumber(text, rev):
441 size = len(text.splitlines())
447 size = len(text.splitlines())
442 return ([(rev, i) for i in xrange(1, size + 1)], text)
448 return ([(rev, i) for i in xrange(1, size + 1)], text)
443
449
444 decorate = (((linenumber is None) and decorate_compat) or
450 decorate = (((linenumber is None) and decorate_compat) or
445 (linenumber and with_linenumber) or
451 (linenumber and with_linenumber) or
446 without_linenumber)
452 without_linenumber)
447
453
448 def pair(parent, child):
454 def pair(parent, child):
449 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
455 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
450 child[0][b1:b2] = parent[0][a1:a2]
456 child[0][b1:b2] = parent[0][a1:a2]
451 return child
457 return child
452
458
453 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
459 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
454 def getctx(path, fileid):
460 def getctx(path, fileid):
455 log = path == self._path and self._filelog or getlog(path)
461 log = path == self._path and self._filelog or getlog(path)
456 return filectx(self._repo, path, fileid=fileid, filelog=log)
462 return filectx(self._repo, path, fileid=fileid, filelog=log)
457 getctx = util.lrucachefunc(getctx)
463 getctx = util.lrucachefunc(getctx)
458
464
459 def parents(f):
465 def parents(f):
460 # we want to reuse filectx objects as much as possible
466 # we want to reuse filectx objects as much as possible
461 p = f._path
467 p = f._path
462 if f._filerev is None: # working dir
468 if f._filerev is None: # working dir
463 pl = [(n.path(), n.filerev()) for n in f.parents()]
469 pl = [(n.path(), n.filerev()) for n in f.parents()]
464 else:
470 else:
465 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
471 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
466
472
467 if follow:
473 if follow:
468 r = f.renamed()
474 r = f.renamed()
469 if r:
475 if r:
470 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
476 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
471
477
472 return [getctx(p, n) for p, n in pl if n != nullrev]
478 return [getctx(p, n) for p, n in pl if n != nullrev]
473
479
474 # use linkrev to find the first changeset where self appeared
480 # use linkrev to find the first changeset where self appeared
475 if self.rev() != self.linkrev():
481 if self.rev() != self.linkrev():
476 base = self.filectx(self.filerev())
482 base = self.filectx(self.filerev())
477 else:
483 else:
478 base = self
484 base = self
479
485
480 # This algorithm would prefer to be recursive, but Python is a
486 # This algorithm would prefer to be recursive, but Python is a
481 # bit recursion-hostile. Instead we do an iterative
487 # bit recursion-hostile. Instead we do an iterative
482 # depth-first search.
488 # depth-first search.
483
489
484 visit = [base]
490 visit = [base]
485 hist = {}
491 hist = {}
486 pcache = {}
492 pcache = {}
487 needed = {base: 1}
493 needed = {base: 1}
488 while visit:
494 while visit:
489 f = visit[-1]
495 f = visit[-1]
490 if f not in pcache:
496 if f not in pcache:
491 pcache[f] = parents(f)
497 pcache[f] = parents(f)
492
498
493 ready = True
499 ready = True
494 pl = pcache[f]
500 pl = pcache[f]
495 for p in pl:
501 for p in pl:
496 if p not in hist:
502 if p not in hist:
497 ready = False
503 ready = False
498 visit.append(p)
504 visit.append(p)
499 needed[p] = needed.get(p, 0) + 1
505 needed[p] = needed.get(p, 0) + 1
500 if ready:
506 if ready:
501 visit.pop()
507 visit.pop()
502 curr = decorate(f.data(), f)
508 curr = decorate(f.data(), f)
503 for p in pl:
509 for p in pl:
504 curr = pair(hist[p], curr)
510 curr = pair(hist[p], curr)
505 if needed[p] == 1:
511 if needed[p] == 1:
506 del hist[p]
512 del hist[p]
507 else:
513 else:
508 needed[p] -= 1
514 needed[p] -= 1
509
515
510 hist[f] = curr
516 hist[f] = curr
511 pcache[f] = []
517 pcache[f] = []
512
518
513 return zip(hist[base][0], hist[base][1].splitlines(True))
519 return zip(hist[base][0], hist[base][1].splitlines(True))
514
520
515 def ancestor(self, fc2, actx=None):
521 def ancestor(self, fc2, actx=None):
516 """
522 """
517 find the common ancestor file context, if any, of self, and fc2
523 find the common ancestor file context, if any, of self, and fc2
518
524
519 If actx is given, it must be the changectx of the common ancestor
525 If actx is given, it must be the changectx of the common ancestor
520 of self's and fc2's respective changesets.
526 of self's and fc2's respective changesets.
521 """
527 """
522
528
523 if actx is None:
529 if actx is None:
524 actx = self.changectx().ancestor(fc2.changectx())
530 actx = self.changectx().ancestor(fc2.changectx())
525
531
526 # the trivial case: changesets are unrelated, files must be too
532 # the trivial case: changesets are unrelated, files must be too
527 if not actx:
533 if not actx:
528 return None
534 return None
529
535
530 # the easy case: no (relevant) renames
536 # the easy case: no (relevant) renames
531 if fc2.path() == self.path() and self.path() in actx:
537 if fc2.path() == self.path() and self.path() in actx:
532 return actx[self.path()]
538 return actx[self.path()]
533 acache = {}
539 acache = {}
534
540
535 # prime the ancestor cache for the working directory
541 # prime the ancestor cache for the working directory
536 for c in (self, fc2):
542 for c in (self, fc2):
537 if c._filerev is None:
543 if c._filerev is None:
538 pl = [(n.path(), n.filenode()) for n in c.parents()]
544 pl = [(n.path(), n.filenode()) for n in c.parents()]
539 acache[(c._path, None)] = pl
545 acache[(c._path, None)] = pl
540
546
541 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
547 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
542 def parents(vertex):
548 def parents(vertex):
543 if vertex in acache:
549 if vertex in acache:
544 return acache[vertex]
550 return acache[vertex]
545 f, n = vertex
551 f, n = vertex
546 if f not in flcache:
552 if f not in flcache:
547 flcache[f] = self._repo.file(f)
553 flcache[f] = self._repo.file(f)
548 fl = flcache[f]
554 fl = flcache[f]
549 pl = [(f, p) for p in fl.parents(n) if p != nullid]
555 pl = [(f, p) for p in fl.parents(n) if p != nullid]
550 re = fl.renamed(n)
556 re = fl.renamed(n)
551 if re:
557 if re:
552 pl.append(re)
558 pl.append(re)
553 acache[vertex] = pl
559 acache[vertex] = pl
554 return pl
560 return pl
555
561
556 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
562 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
557 v = ancestor.ancestor(a, b, parents)
563 v = ancestor.ancestor(a, b, parents)
558 if v:
564 if v:
559 f, n = v
565 f, n = v
560 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
566 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
561
567
562 return None
568 return None
563
569
564 def ancestors(self):
570 def ancestors(self):
565 visit = {}
571 visit = {}
566 c = self
572 c = self
567 while True:
573 while True:
568 for parent in c.parents():
574 for parent in c.parents():
569 visit[(parent.rev(), parent.node())] = parent
575 visit[(parent.rev(), parent.node())] = parent
570 if not visit:
576 if not visit:
571 break
577 break
572 c = visit.pop(max(visit))
578 c = visit.pop(max(visit))
573 yield c
579 yield c
574
580
575 class workingctx(changectx):
581 class workingctx(changectx):
576 """A workingctx object makes access to data related to
582 """A workingctx object makes access to data related to
577 the current working directory convenient.
583 the current working directory convenient.
578 date - any valid date string or (unixtime, offset), or None.
584 date - any valid date string or (unixtime, offset), or None.
579 user - username string, or None.
585 user - username string, or None.
580 extra - a dictionary of extra values, or None.
586 extra - a dictionary of extra values, or None.
581 changes - a list of file lists as returned by localrepo.status()
587 changes - a list of file lists as returned by localrepo.status()
582 or None to use the repository status.
588 or None to use the repository status.
583 """
589 """
584 def __init__(self, repo, text="", user=None, date=None, extra=None,
590 def __init__(self, repo, text="", user=None, date=None, extra=None,
585 changes=None):
591 changes=None):
586 self._repo = repo
592 self._repo = repo
587 self._rev = None
593 self._rev = None
588 self._node = None
594 self._node = None
589 self._text = text
595 self._text = text
590 if date:
596 if date:
591 self._date = util.parsedate(date)
597 self._date = util.parsedate(date)
592 if user:
598 if user:
593 self._user = user
599 self._user = user
594 if changes:
600 if changes:
595 self._status = list(changes[:4])
601 self._status = list(changes[:4])
596 self._unknown = changes[4]
602 self._unknown = changes[4]
597 self._ignored = changes[5]
603 self._ignored = changes[5]
598 self._clean = changes[6]
604 self._clean = changes[6]
599 else:
605 else:
600 self._unknown = None
606 self._unknown = None
601 self._ignored = None
607 self._ignored = None
602 self._clean = None
608 self._clean = None
603
609
604 self._extra = {}
610 self._extra = {}
605 if extra:
611 if extra:
606 self._extra = extra.copy()
612 self._extra = extra.copy()
607 if 'branch' not in self._extra:
613 if 'branch' not in self._extra:
608 try:
614 try:
609 branch = encoding.fromlocal(self._repo.dirstate.branch())
615 branch = encoding.fromlocal(self._repo.dirstate.branch())
610 except UnicodeDecodeError:
616 except UnicodeDecodeError:
611 raise util.Abort(_('branch name not in UTF-8!'))
617 raise util.Abort(_('branch name not in UTF-8!'))
612 self._extra['branch'] = branch
618 self._extra['branch'] = branch
613 if self._extra['branch'] == '':
619 if self._extra['branch'] == '':
614 self._extra['branch'] = 'default'
620 self._extra['branch'] = 'default'
615
621
616 def __str__(self):
622 def __str__(self):
617 return str(self._parents[0]) + "+"
623 return str(self._parents[0]) + "+"
618
624
619 def __repr__(self):
625 def __repr__(self):
620 return "<workingctx %s>" % str(self)
626 return "<workingctx %s>" % str(self)
621
627
622 def __nonzero__(self):
628 def __nonzero__(self):
623 return True
629 return True
624
630
625 def __contains__(self, key):
631 def __contains__(self, key):
626 return self._repo.dirstate[key] not in "?r"
632 return self._repo.dirstate[key] not in "?r"
627
633
628 @propertycache
634 @propertycache
629 def _manifest(self):
635 def _manifest(self):
630 """generate a manifest corresponding to the working directory"""
636 """generate a manifest corresponding to the working directory"""
631
637
632 if self._unknown is None:
638 if self._unknown is None:
633 self.status(unknown=True)
639 self.status(unknown=True)
634
640
635 man = self._parents[0].manifest().copy()
641 man = self._parents[0].manifest().copy()
636 copied = self._repo.dirstate.copies()
642 copied = self._repo.dirstate.copies()
637 if len(self._parents) > 1:
643 if len(self._parents) > 1:
638 man2 = self.p2().manifest()
644 man2 = self.p2().manifest()
639 def getman(f):
645 def getman(f):
640 if f in man:
646 if f in man:
641 return man
647 return man
642 return man2
648 return man2
643 else:
649 else:
644 getman = lambda f: man
650 getman = lambda f: man
645 def cf(f):
651 def cf(f):
646 f = copied.get(f, f)
652 f = copied.get(f, f)
647 return getman(f).flags(f)
653 return getman(f).flags(f)
648 ff = self._repo.dirstate.flagfunc(cf)
654 ff = self._repo.dirstate.flagfunc(cf)
649 modified, added, removed, deleted = self._status
655 modified, added, removed, deleted = self._status
650 unknown = self._unknown
656 unknown = self._unknown
651 for i, l in (("a", added), ("m", modified), ("u", unknown)):
657 for i, l in (("a", added), ("m", modified), ("u", unknown)):
652 for f in l:
658 for f in l:
653 orig = copied.get(f, f)
659 orig = copied.get(f, f)
654 man[f] = getman(orig).get(orig, nullid) + i
660 man[f] = getman(orig).get(orig, nullid) + i
655 try:
661 try:
656 man.set(f, ff(f))
662 man.set(f, ff(f))
657 except OSError:
663 except OSError:
658 pass
664 pass
659
665
660 for f in deleted + removed:
666 for f in deleted + removed:
661 if f in man:
667 if f in man:
662 del man[f]
668 del man[f]
663
669
664 return man
670 return man
665
671
666 def __iter__(self):
672 def __iter__(self):
667 d = self._repo.dirstate
673 d = self._repo.dirstate
668 for f in d:
674 for f in d:
669 if d[f] != 'r':
675 if d[f] != 'r':
670 yield f
676 yield f
671
677
672 @propertycache
678 @propertycache
673 def _status(self):
679 def _status(self):
674 return self._repo.status()[:4]
680 return self._repo.status()[:4]
675
681
676 @propertycache
682 @propertycache
677 def _user(self):
683 def _user(self):
678 return self._repo.ui.username()
684 return self._repo.ui.username()
679
685
680 @propertycache
686 @propertycache
681 def _date(self):
687 def _date(self):
682 return util.makedate()
688 return util.makedate()
683
689
684 @propertycache
690 @propertycache
685 def _parents(self):
691 def _parents(self):
686 p = self._repo.dirstate.parents()
692 p = self._repo.dirstate.parents()
687 if p[1] == nullid:
693 if p[1] == nullid:
688 p = p[:-1]
694 p = p[:-1]
689 self._parents = [changectx(self._repo, x) for x in p]
695 self._parents = [changectx(self._repo, x) for x in p]
690 return self._parents
696 return self._parents
691
697
692 def status(self, ignored=False, clean=False, unknown=False):
698 def status(self, ignored=False, clean=False, unknown=False):
693 """Explicit status query
699 """Explicit status query
694 Unless this method is used to query the working copy status, the
700 Unless this method is used to query the working copy status, the
695 _status property will implicitly read the status using its default
701 _status property will implicitly read the status using its default
696 arguments."""
702 arguments."""
697 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
703 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
698 self._unknown = self._ignored = self._clean = None
704 self._unknown = self._ignored = self._clean = None
699 if unknown:
705 if unknown:
700 self._unknown = stat[4]
706 self._unknown = stat[4]
701 if ignored:
707 if ignored:
702 self._ignored = stat[5]
708 self._ignored = stat[5]
703 if clean:
709 if clean:
704 self._clean = stat[6]
710 self._clean = stat[6]
705 self._status = stat[:4]
711 self._status = stat[:4]
706 return stat
712 return stat
707
713
708 def manifest(self):
714 def manifest(self):
709 return self._manifest
715 return self._manifest
710 def user(self):
716 def user(self):
711 return self._user or self._repo.ui.username()
717 return self._user or self._repo.ui.username()
712 def date(self):
718 def date(self):
713 return self._date
719 return self._date
714 def description(self):
720 def description(self):
715 return self._text
721 return self._text
716 def files(self):
722 def files(self):
717 return sorted(self._status[0] + self._status[1] + self._status[2])
723 return sorted(self._status[0] + self._status[1] + self._status[2])
718
724
719 def modified(self):
725 def modified(self):
720 return self._status[0]
726 return self._status[0]
721 def added(self):
727 def added(self):
722 return self._status[1]
728 return self._status[1]
723 def removed(self):
729 def removed(self):
724 return self._status[2]
730 return self._status[2]
725 def deleted(self):
731 def deleted(self):
726 return self._status[3]
732 return self._status[3]
727 def unknown(self):
733 def unknown(self):
728 assert self._unknown is not None # must call status first
734 assert self._unknown is not None # must call status first
729 return self._unknown
735 return self._unknown
730 def ignored(self):
736 def ignored(self):
731 assert self._ignored is not None # must call status first
737 assert self._ignored is not None # must call status first
732 return self._ignored
738 return self._ignored
733 def clean(self):
739 def clean(self):
734 assert self._clean is not None # must call status first
740 assert self._clean is not None # must call status first
735 return self._clean
741 return self._clean
736 def branch(self):
742 def branch(self):
737 return encoding.tolocal(self._extra['branch'])
743 return encoding.tolocal(self._extra['branch'])
738 def extra(self):
744 def extra(self):
739 return self._extra
745 return self._extra
740
746
741 def tags(self):
747 def tags(self):
742 t = []
748 t = []
743 for p in self.parents():
749 for p in self.parents():
744 t.extend(p.tags())
750 t.extend(p.tags())
745 return t
751 return t
746
752
747 def bookmarks(self):
753 def bookmarks(self):
748 b = []
754 b = []
749 for p in self.parents():
755 for p in self.parents():
750 b.extend(p.bookmarks())
756 b.extend(p.bookmarks())
751 return b
757 return b
752
758
753 def children(self):
759 def children(self):
754 return []
760 return []
755
761
756 def flags(self, path):
762 def flags(self, path):
757 if '_manifest' in self.__dict__:
763 if '_manifest' in self.__dict__:
758 try:
764 try:
759 return self._manifest.flags(path)
765 return self._manifest.flags(path)
760 except KeyError:
766 except KeyError:
761 return ''
767 return ''
762
768
763 orig = self._repo.dirstate.copies().get(path, path)
769 orig = self._repo.dirstate.copies().get(path, path)
764
770
765 def findflag(ctx):
771 def findflag(ctx):
766 mnode = ctx.changeset()[0]
772 mnode = ctx.changeset()[0]
767 node, flag = self._repo.manifest.find(mnode, orig)
773 node, flag = self._repo.manifest.find(mnode, orig)
768 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
774 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
769 try:
775 try:
770 return ff(path)
776 return ff(path)
771 except OSError:
777 except OSError:
772 pass
778 pass
773
779
774 flag = findflag(self._parents[0])
780 flag = findflag(self._parents[0])
775 if flag is None and len(self.parents()) > 1:
781 if flag is None and len(self.parents()) > 1:
776 flag = findflag(self._parents[1])
782 flag = findflag(self._parents[1])
777 if flag is None or self._repo.dirstate[path] == 'r':
783 if flag is None or self._repo.dirstate[path] == 'r':
778 return ''
784 return ''
779 return flag
785 return flag
780
786
781 def filectx(self, path, filelog=None):
787 def filectx(self, path, filelog=None):
782 """get a file context from the working directory"""
788 """get a file context from the working directory"""
783 return workingfilectx(self._repo, path, workingctx=self,
789 return workingfilectx(self._repo, path, workingctx=self,
784 filelog=filelog)
790 filelog=filelog)
785
791
786 def ancestor(self, c2):
792 def ancestor(self, c2):
787 """return the ancestor context of self and c2"""
793 """return the ancestor context of self and c2"""
788 return self._parents[0].ancestor(c2) # punt on two parents for now
794 return self._parents[0].ancestor(c2) # punt on two parents for now
789
795
790 def walk(self, match):
796 def walk(self, match):
791 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
797 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
792 True, False))
798 True, False))
793
799
794 def dirty(self, missing=False):
800 def dirty(self, missing=False):
795 "check whether a working directory is modified"
801 "check whether a working directory is modified"
796 # check subrepos first
802 # check subrepos first
797 for s in self.substate:
803 for s in self.substate:
798 if self.sub(s).dirty():
804 if self.sub(s).dirty():
799 return True
805 return True
800 # check current working dir
806 # check current working dir
801 return (self.p2() or self.branch() != self.p1().branch() or
807 return (self.p2() or self.branch() != self.p1().branch() or
802 self.modified() or self.added() or self.removed() or
808 self.modified() or self.added() or self.removed() or
803 (missing and self.deleted()))
809 (missing and self.deleted()))
804
810
805 def add(self, list, prefix=""):
811 def add(self, list, prefix=""):
806 join = lambda f: os.path.join(prefix, f)
812 join = lambda f: os.path.join(prefix, f)
807 wlock = self._repo.wlock()
813 wlock = self._repo.wlock()
808 ui, ds = self._repo.ui, self._repo.dirstate
814 ui, ds = self._repo.ui, self._repo.dirstate
809 try:
815 try:
810 rejected = []
816 rejected = []
811 for f in list:
817 for f in list:
812 scmutil.checkportable(ui, join(f))
818 scmutil.checkportable(ui, join(f))
813 p = self._repo.wjoin(f)
819 p = self._repo.wjoin(f)
814 try:
820 try:
815 st = os.lstat(p)
821 st = os.lstat(p)
816 except OSError:
822 except OSError:
817 ui.warn(_("%s does not exist!\n") % join(f))
823 ui.warn(_("%s does not exist!\n") % join(f))
818 rejected.append(f)
824 rejected.append(f)
819 continue
825 continue
820 if st.st_size > 10000000:
826 if st.st_size > 10000000:
821 ui.warn(_("%s: up to %d MB of RAM may be required "
827 ui.warn(_("%s: up to %d MB of RAM may be required "
822 "to manage this file\n"
828 "to manage this file\n"
823 "(use 'hg revert %s' to cancel the "
829 "(use 'hg revert %s' to cancel the "
824 "pending addition)\n")
830 "pending addition)\n")
825 % (f, 3 * st.st_size // 1000000, join(f)))
831 % (f, 3 * st.st_size // 1000000, join(f)))
826 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
832 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
827 ui.warn(_("%s not added: only files and symlinks "
833 ui.warn(_("%s not added: only files and symlinks "
828 "supported currently\n") % join(f))
834 "supported currently\n") % join(f))
829 rejected.append(p)
835 rejected.append(p)
830 elif ds[f] in 'amn':
836 elif ds[f] in 'amn':
831 ui.warn(_("%s already tracked!\n") % join(f))
837 ui.warn(_("%s already tracked!\n") % join(f))
832 elif ds[f] == 'r':
838 elif ds[f] == 'r':
833 ds.normallookup(f)
839 ds.normallookup(f)
834 else:
840 else:
835 ds.add(f)
841 ds.add(f)
836 return rejected
842 return rejected
837 finally:
843 finally:
838 wlock.release()
844 wlock.release()
839
845
840 def forget(self, files):
846 def forget(self, files):
841 wlock = self._repo.wlock()
847 wlock = self._repo.wlock()
842 try:
848 try:
843 for f in files:
849 for f in files:
844 if self._repo.dirstate[f] != 'a':
850 if self._repo.dirstate[f] != 'a':
845 self._repo.dirstate.remove(f)
851 self._repo.dirstate.remove(f)
846 elif f not in self._repo.dirstate:
852 elif f not in self._repo.dirstate:
847 self._repo.ui.warn(_("%s not tracked!\n") % f)
853 self._repo.ui.warn(_("%s not tracked!\n") % f)
848 else:
854 else:
849 self._repo.dirstate.drop(f)
855 self._repo.dirstate.drop(f)
850 finally:
856 finally:
851 wlock.release()
857 wlock.release()
852
858
853 def ancestors(self):
859 def ancestors(self):
854 for a in self._repo.changelog.ancestors(
860 for a in self._repo.changelog.ancestors(
855 *[p.rev() for p in self._parents]):
861 *[p.rev() for p in self._parents]):
856 yield changectx(self._repo, a)
862 yield changectx(self._repo, a)
857
863
858 def undelete(self, list):
864 def undelete(self, list):
859 pctxs = self.parents()
865 pctxs = self.parents()
860 wlock = self._repo.wlock()
866 wlock = self._repo.wlock()
861 try:
867 try:
862 for f in list:
868 for f in list:
863 if self._repo.dirstate[f] != 'r':
869 if self._repo.dirstate[f] != 'r':
864 self._repo.ui.warn(_("%s not removed!\n") % f)
870 self._repo.ui.warn(_("%s not removed!\n") % f)
865 else:
871 else:
866 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
872 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
867 t = fctx.data()
873 t = fctx.data()
868 self._repo.wwrite(f, t, fctx.flags())
874 self._repo.wwrite(f, t, fctx.flags())
869 self._repo.dirstate.normal(f)
875 self._repo.dirstate.normal(f)
870 finally:
876 finally:
871 wlock.release()
877 wlock.release()
872
878
873 def copy(self, source, dest):
879 def copy(self, source, dest):
874 p = self._repo.wjoin(dest)
880 p = self._repo.wjoin(dest)
875 if not os.path.lexists(p):
881 if not os.path.lexists(p):
876 self._repo.ui.warn(_("%s does not exist!\n") % dest)
882 self._repo.ui.warn(_("%s does not exist!\n") % dest)
877 elif not (os.path.isfile(p) or os.path.islink(p)):
883 elif not (os.path.isfile(p) or os.path.islink(p)):
878 self._repo.ui.warn(_("copy failed: %s is not a file or a "
884 self._repo.ui.warn(_("copy failed: %s is not a file or a "
879 "symbolic link\n") % dest)
885 "symbolic link\n") % dest)
880 else:
886 else:
881 wlock = self._repo.wlock()
887 wlock = self._repo.wlock()
882 try:
888 try:
883 if self._repo.dirstate[dest] in '?r':
889 if self._repo.dirstate[dest] in '?r':
884 self._repo.dirstate.add(dest)
890 self._repo.dirstate.add(dest)
885 self._repo.dirstate.copy(source, dest)
891 self._repo.dirstate.copy(source, dest)
886 finally:
892 finally:
887 wlock.release()
893 wlock.release()
888
894
889 class workingfilectx(filectx):
895 class workingfilectx(filectx):
890 """A workingfilectx object makes access to data related to a particular
896 """A workingfilectx object makes access to data related to a particular
891 file in the working directory convenient."""
897 file in the working directory convenient."""
892 def __init__(self, repo, path, filelog=None, workingctx=None):
898 def __init__(self, repo, path, filelog=None, workingctx=None):
893 """changeid can be a changeset revision, node, or tag.
899 """changeid can be a changeset revision, node, or tag.
894 fileid can be a file revision or node."""
900 fileid can be a file revision or node."""
895 self._repo = repo
901 self._repo = repo
896 self._path = path
902 self._path = path
897 self._changeid = None
903 self._changeid = None
898 self._filerev = self._filenode = None
904 self._filerev = self._filenode = None
899
905
900 if filelog:
906 if filelog:
901 self._filelog = filelog
907 self._filelog = filelog
902 if workingctx:
908 if workingctx:
903 self._changectx = workingctx
909 self._changectx = workingctx
904
910
905 @propertycache
911 @propertycache
906 def _changectx(self):
912 def _changectx(self):
907 return workingctx(self._repo)
913 return workingctx(self._repo)
908
914
909 def __nonzero__(self):
915 def __nonzero__(self):
910 return True
916 return True
911
917
912 def __str__(self):
918 def __str__(self):
913 return "%s@%s" % (self.path(), self._changectx)
919 return "%s@%s" % (self.path(), self._changectx)
914
920
915 def __repr__(self):
921 def __repr__(self):
916 return "<workingfilectx %s>" % str(self)
922 return "<workingfilectx %s>" % str(self)
917
923
918 def data(self):
924 def data(self):
919 return self._repo.wread(self._path)
925 return self._repo.wread(self._path)
920 def renamed(self):
926 def renamed(self):
921 rp = self._repo.dirstate.copied(self._path)
927 rp = self._repo.dirstate.copied(self._path)
922 if not rp:
928 if not rp:
923 return None
929 return None
924 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
930 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
925
931
926 def parents(self):
932 def parents(self):
927 '''return parent filectxs, following copies if necessary'''
933 '''return parent filectxs, following copies if necessary'''
928 def filenode(ctx, path):
934 def filenode(ctx, path):
929 return ctx._manifest.get(path, nullid)
935 return ctx._manifest.get(path, nullid)
930
936
931 path = self._path
937 path = self._path
932 fl = self._filelog
938 fl = self._filelog
933 pcl = self._changectx._parents
939 pcl = self._changectx._parents
934 renamed = self.renamed()
940 renamed = self.renamed()
935
941
936 if renamed:
942 if renamed:
937 pl = [renamed + (None,)]
943 pl = [renamed + (None,)]
938 else:
944 else:
939 pl = [(path, filenode(pcl[0], path), fl)]
945 pl = [(path, filenode(pcl[0], path), fl)]
940
946
941 for pc in pcl[1:]:
947 for pc in pcl[1:]:
942 pl.append((path, filenode(pc, path), fl))
948 pl.append((path, filenode(pc, path), fl))
943
949
944 return [filectx(self._repo, p, fileid=n, filelog=l)
950 return [filectx(self._repo, p, fileid=n, filelog=l)
945 for p, n, l in pl if n != nullid]
951 for p, n, l in pl if n != nullid]
946
952
947 def children(self):
953 def children(self):
948 return []
954 return []
949
955
950 def size(self):
956 def size(self):
951 return os.lstat(self._repo.wjoin(self._path)).st_size
957 return os.lstat(self._repo.wjoin(self._path)).st_size
952 def date(self):
958 def date(self):
953 t, tz = self._changectx.date()
959 t, tz = self._changectx.date()
954 try:
960 try:
955 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
961 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
956 except OSError, err:
962 except OSError, err:
957 if err.errno != errno.ENOENT:
963 if err.errno != errno.ENOENT:
958 raise
964 raise
959 return (t, tz)
965 return (t, tz)
960
966
961 def cmp(self, fctx):
967 def cmp(self, fctx):
962 """compare with other file context
968 """compare with other file context
963
969
964 returns True if different than fctx.
970 returns True if different than fctx.
965 """
971 """
966 # fctx should be a filectx (not a wfctx)
972 # fctx should be a filectx (not a wfctx)
967 # invert comparison to reuse the same code path
973 # invert comparison to reuse the same code path
968 return fctx.cmp(self)
974 return fctx.cmp(self)
969
975
970 class memctx(object):
976 class memctx(object):
971 """Use memctx to perform in-memory commits via localrepo.commitctx().
977 """Use memctx to perform in-memory commits via localrepo.commitctx().
972
978
973 Revision information is supplied at initialization time while
979 Revision information is supplied at initialization time while
974 related files data and is made available through a callback
980 related files data and is made available through a callback
975 mechanism. 'repo' is the current localrepo, 'parents' is a
981 mechanism. 'repo' is the current localrepo, 'parents' is a
976 sequence of two parent revisions identifiers (pass None for every
982 sequence of two parent revisions identifiers (pass None for every
977 missing parent), 'text' is the commit message and 'files' lists
983 missing parent), 'text' is the commit message and 'files' lists
978 names of files touched by the revision (normalized and relative to
984 names of files touched by the revision (normalized and relative to
979 repository root).
985 repository root).
980
986
981 filectxfn(repo, memctx, path) is a callable receiving the
987 filectxfn(repo, memctx, path) is a callable receiving the
982 repository, the current memctx object and the normalized path of
988 repository, the current memctx object and the normalized path of
983 requested file, relative to repository root. It is fired by the
989 requested file, relative to repository root. It is fired by the
984 commit function for every file in 'files', but calls order is
990 commit function for every file in 'files', but calls order is
985 undefined. If the file is available in the revision being
991 undefined. If the file is available in the revision being
986 committed (updated or added), filectxfn returns a memfilectx
992 committed (updated or added), filectxfn returns a memfilectx
987 object. If the file was removed, filectxfn raises an
993 object. If the file was removed, filectxfn raises an
988 IOError. Moved files are represented by marking the source file
994 IOError. Moved files are represented by marking the source file
989 removed and the new file added with copy information (see
995 removed and the new file added with copy information (see
990 memfilectx).
996 memfilectx).
991
997
992 user receives the committer name and defaults to current
998 user receives the committer name and defaults to current
993 repository username, date is the commit date in any format
999 repository username, date is the commit date in any format
994 supported by util.parsedate() and defaults to current date, extra
1000 supported by util.parsedate() and defaults to current date, extra
995 is a dictionary of metadata or is left empty.
1001 is a dictionary of metadata or is left empty.
996 """
1002 """
997 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1003 def __init__(self, repo, parents, text, files, filectxfn, user=None,
998 date=None, extra=None):
1004 date=None, extra=None):
999 self._repo = repo
1005 self._repo = repo
1000 self._rev = None
1006 self._rev = None
1001 self._node = None
1007 self._node = None
1002 self._text = text
1008 self._text = text
1003 self._date = date and util.parsedate(date) or util.makedate()
1009 self._date = date and util.parsedate(date) or util.makedate()
1004 self._user = user
1010 self._user = user
1005 parents = [(p or nullid) for p in parents]
1011 parents = [(p or nullid) for p in parents]
1006 p1, p2 = parents
1012 p1, p2 = parents
1007 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1013 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1008 files = sorted(set(files))
1014 files = sorted(set(files))
1009 self._status = [files, [], [], [], []]
1015 self._status = [files, [], [], [], []]
1010 self._filectxfn = filectxfn
1016 self._filectxfn = filectxfn
1011
1017
1012 self._extra = extra and extra.copy() or {}
1018 self._extra = extra and extra.copy() or {}
1013 if self._extra.get('branch', '') == '':
1019 if self._extra.get('branch', '') == '':
1014 self._extra['branch'] = 'default'
1020 self._extra['branch'] = 'default'
1015
1021
1016 def __str__(self):
1022 def __str__(self):
1017 return str(self._parents[0]) + "+"
1023 return str(self._parents[0]) + "+"
1018
1024
1019 def __int__(self):
1025 def __int__(self):
1020 return self._rev
1026 return self._rev
1021
1027
1022 def __nonzero__(self):
1028 def __nonzero__(self):
1023 return True
1029 return True
1024
1030
1025 def __getitem__(self, key):
1031 def __getitem__(self, key):
1026 return self.filectx(key)
1032 return self.filectx(key)
1027
1033
1028 def p1(self):
1034 def p1(self):
1029 return self._parents[0]
1035 return self._parents[0]
1030 def p2(self):
1036 def p2(self):
1031 return self._parents[1]
1037 return self._parents[1]
1032
1038
1033 def user(self):
1039 def user(self):
1034 return self._user or self._repo.ui.username()
1040 return self._user or self._repo.ui.username()
1035 def date(self):
1041 def date(self):
1036 return self._date
1042 return self._date
1037 def description(self):
1043 def description(self):
1038 return self._text
1044 return self._text
1039 def files(self):
1045 def files(self):
1040 return self.modified()
1046 return self.modified()
1041 def modified(self):
1047 def modified(self):
1042 return self._status[0]
1048 return self._status[0]
1043 def added(self):
1049 def added(self):
1044 return self._status[1]
1050 return self._status[1]
1045 def removed(self):
1051 def removed(self):
1046 return self._status[2]
1052 return self._status[2]
1047 def deleted(self):
1053 def deleted(self):
1048 return self._status[3]
1054 return self._status[3]
1049 def unknown(self):
1055 def unknown(self):
1050 return self._status[4]
1056 return self._status[4]
1051 def ignored(self):
1057 def ignored(self):
1052 return self._status[5]
1058 return self._status[5]
1053 def clean(self):
1059 def clean(self):
1054 return self._status[6]
1060 return self._status[6]
1055 def branch(self):
1061 def branch(self):
1056 return encoding.tolocal(self._extra['branch'])
1062 return encoding.tolocal(self._extra['branch'])
1057 def extra(self):
1063 def extra(self):
1058 return self._extra
1064 return self._extra
1059 def flags(self, f):
1065 def flags(self, f):
1060 return self[f].flags()
1066 return self[f].flags()
1061
1067
1062 def parents(self):
1068 def parents(self):
1063 """return contexts for each parent changeset"""
1069 """return contexts for each parent changeset"""
1064 return self._parents
1070 return self._parents
1065
1071
1066 def filectx(self, path, filelog=None):
1072 def filectx(self, path, filelog=None):
1067 """get a file context from the working directory"""
1073 """get a file context from the working directory"""
1068 return self._filectxfn(self._repo, self, path)
1074 return self._filectxfn(self._repo, self, path)
1069
1075
1070 def commit(self):
1076 def commit(self):
1071 """commit context to the repo"""
1077 """commit context to the repo"""
1072 return self._repo.commitctx(self)
1078 return self._repo.commitctx(self)
1073
1079
1074 class memfilectx(object):
1080 class memfilectx(object):
1075 """memfilectx represents an in-memory file to commit.
1081 """memfilectx represents an in-memory file to commit.
1076
1082
1077 See memctx for more details.
1083 See memctx for more details.
1078 """
1084 """
1079 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1085 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1080 """
1086 """
1081 path is the normalized file path relative to repository root.
1087 path is the normalized file path relative to repository root.
1082 data is the file content as a string.
1088 data is the file content as a string.
1083 islink is True if the file is a symbolic link.
1089 islink is True if the file is a symbolic link.
1084 isexec is True if the file is executable.
1090 isexec is True if the file is executable.
1085 copied is the source file path if current file was copied in the
1091 copied is the source file path if current file was copied in the
1086 revision being committed, or None."""
1092 revision being committed, or None."""
1087 self._path = path
1093 self._path = path
1088 self._data = data
1094 self._data = data
1089 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1095 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1090 self._copied = None
1096 self._copied = None
1091 if copied:
1097 if copied:
1092 self._copied = (copied, nullid)
1098 self._copied = (copied, nullid)
1093
1099
1094 def __nonzero__(self):
1100 def __nonzero__(self):
1095 return True
1101 return True
1096 def __str__(self):
1102 def __str__(self):
1097 return "%s@%s" % (self.path(), self._changectx)
1103 return "%s@%s" % (self.path(), self._changectx)
1098 def path(self):
1104 def path(self):
1099 return self._path
1105 return self._path
1100 def data(self):
1106 def data(self):
1101 return self._data
1107 return self._data
1102 def flags(self):
1108 def flags(self):
1103 return self._flags
1109 return self._flags
1104 def isexec(self):
1110 def isexec(self):
1105 return 'x' in self._flags
1111 return 'x' in self._flags
1106 def islink(self):
1112 def islink(self):
1107 return 'l' in self._flags
1113 return 'l' in self._flags
1108 def renamed(self):
1114 def renamed(self):
1109 return self._copied
1115 return self._copied
@@ -1,705 +1,704
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright 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 i18n import _
8 from i18n import _
9 import util, error, osutil, revset, similar
9 import util, error, osutil, revset, similar
10 import match as matchmod
10 import match as matchmod
11 import os, errno, stat, sys, glob
11 import os, errno, stat, sys, glob
12
12
13 def checkfilename(f):
13 def checkfilename(f):
14 '''Check that the filename f is an acceptable filename for a tracked file'''
14 '''Check that the filename f is an acceptable filename for a tracked file'''
15 if '\r' in f or '\n' in f:
15 if '\r' in f or '\n' in f:
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
17
17
18 def checkportable(ui, f):
18 def checkportable(ui, f):
19 '''Check if filename f is portable and warn or abort depending on config'''
19 '''Check if filename f is portable and warn or abort depending on config'''
20 checkfilename(f)
20 checkfilename(f)
21 abort, warn = checkportabilityalert(ui)
21 abort, warn = checkportabilityalert(ui)
22 if abort or warn:
22 if abort or warn:
23 msg = util.checkwinfilename(f)
23 msg = util.checkwinfilename(f)
24 if msg:
24 if msg:
25 msg = "%s: %r" % (msg, f)
25 msg = "%s: %r" % (msg, f)
26 if abort:
26 if abort:
27 raise util.Abort(msg)
27 raise util.Abort(msg)
28 ui.warn(_("warning: %s\n") % msg)
28 ui.warn(_("warning: %s\n") % msg)
29
29
30 def checkportabilityalert(ui):
30 def checkportabilityalert(ui):
31 '''check if the user's config requests nothing, a warning, or abort for
31 '''check if the user's config requests nothing, a warning, or abort for
32 non-portable filenames'''
32 non-portable filenames'''
33 val = ui.config('ui', 'portablefilenames', 'warn')
33 val = ui.config('ui', 'portablefilenames', 'warn')
34 lval = val.lower()
34 lval = val.lower()
35 bval = util.parsebool(val)
35 bval = util.parsebool(val)
36 abort = os.name == 'nt' or lval == 'abort'
36 abort = os.name == 'nt' or lval == 'abort'
37 warn = bval or lval == 'warn'
37 warn = bval or lval == 'warn'
38 if bval is None and not (warn or abort or lval == 'ignore'):
38 if bval is None and not (warn or abort or lval == 'ignore'):
39 raise error.ConfigError(
39 raise error.ConfigError(
40 _("ui.portablefilenames value is invalid ('%s')") % val)
40 _("ui.portablefilenames value is invalid ('%s')") % val)
41 return abort, warn
41 return abort, warn
42
42
43 class casecollisionauditor(object):
43 class casecollisionauditor(object):
44 def __init__(self, ui, abort, existingiter):
44 def __init__(self, ui, abort, existingiter):
45 self._ui = ui
45 self._ui = ui
46 self._abort = abort
46 self._abort = abort
47 self._map = {}
47 self._map = {}
48 for f in existingiter:
48 for f in existingiter:
49 self._map[f.lower()] = f
49 self._map[f.lower()] = f
50
50
51 def __call__(self, f):
51 def __call__(self, f):
52 fl = f.lower()
52 fl = f.lower()
53 map = self._map
53 map = self._map
54 if fl in map and map[fl] != f:
54 if fl in map and map[fl] != f:
55 msg = _('possible case-folding collision for %s') % f
55 msg = _('possible case-folding collision for %s') % f
56 if self._abort:
56 if self._abort:
57 raise util.Abort(msg)
57 raise util.Abort(msg)
58 self._ui.warn(_("warning: %s\n") % msg)
58 self._ui.warn(_("warning: %s\n") % msg)
59 map[fl] = f
59 map[fl] = f
60
60
61 class pathauditor(object):
61 class pathauditor(object):
62 '''ensure that a filesystem path contains no banned components.
62 '''ensure that a filesystem path contains no banned components.
63 the following properties of a path are checked:
63 the following properties of a path are checked:
64
64
65 - ends with a directory separator
65 - ends with a directory separator
66 - under top-level .hg
66 - under top-level .hg
67 - starts at the root of a windows drive
67 - starts at the root of a windows drive
68 - contains ".."
68 - contains ".."
69 - traverses a symlink (e.g. a/symlink_here/b)
69 - traverses a symlink (e.g. a/symlink_here/b)
70 - inside a nested repository (a callback can be used to approve
70 - inside a nested repository (a callback can be used to approve
71 some nested repositories, e.g., subrepositories)
71 some nested repositories, e.g., subrepositories)
72 '''
72 '''
73
73
74 def __init__(self, root, callback=None):
74 def __init__(self, root, callback=None):
75 self.audited = set()
75 self.audited = set()
76 self.auditeddir = set()
76 self.auditeddir = set()
77 self.root = root
77 self.root = root
78 self.callback = callback
78 self.callback = callback
79
79
80 def __call__(self, path):
80 def __call__(self, path):
81 '''Check the relative path.
81 '''Check the relative path.
82 path may contain a pattern (e.g. foodir/**.txt)'''
82 path may contain a pattern (e.g. foodir/**.txt)'''
83
83
84 if path in self.audited:
84 if path in self.audited:
85 return
85 return
86 # AIX ignores "/" at end of path, others raise EISDIR.
86 # AIX ignores "/" at end of path, others raise EISDIR.
87 if util.endswithsep(path):
87 if util.endswithsep(path):
88 raise util.Abort(_("path ends in directory separator: %s") % path)
88 raise util.Abort(_("path ends in directory separator: %s") % path)
89 normpath = os.path.normcase(path)
89 normpath = os.path.normcase(path)
90 parts = util.splitpath(normpath)
90 parts = util.splitpath(normpath)
91 if (os.path.splitdrive(path)[0]
91 if (os.path.splitdrive(path)[0]
92 or parts[0].lower() in ('.hg', '.hg.', '')
92 or parts[0].lower() in ('.hg', '.hg.', '')
93 or os.pardir in parts):
93 or os.pardir in parts):
94 raise util.Abort(_("path contains illegal component: %s") % path)
94 raise util.Abort(_("path contains illegal component: %s") % path)
95 if '.hg' in path.lower():
95 if '.hg' in path.lower():
96 lparts = [p.lower() for p in parts]
96 lparts = [p.lower() for p in parts]
97 for p in '.hg', '.hg.':
97 for p in '.hg', '.hg.':
98 if p in lparts[1:]:
98 if p in lparts[1:]:
99 pos = lparts.index(p)
99 pos = lparts.index(p)
100 base = os.path.join(*parts[:pos])
100 base = os.path.join(*parts[:pos])
101 raise util.Abort(_('path %r is inside nested repo %r')
101 raise util.Abort(_('path %r is inside nested repo %r')
102 % (path, base))
102 % (path, base))
103
103
104 parts.pop()
104 parts.pop()
105 prefixes = []
105 prefixes = []
106 while parts:
106 while parts:
107 prefix = os.sep.join(parts)
107 prefix = os.sep.join(parts)
108 if prefix in self.auditeddir:
108 if prefix in self.auditeddir:
109 break
109 break
110 curpath = os.path.join(self.root, prefix)
110 curpath = os.path.join(self.root, prefix)
111 try:
111 try:
112 st = os.lstat(curpath)
112 st = os.lstat(curpath)
113 except OSError, err:
113 except OSError, err:
114 # EINVAL can be raised as invalid path syntax under win32.
114 # EINVAL can be raised as invalid path syntax under win32.
115 # They must be ignored for patterns can be checked too.
115 # They must be ignored for patterns can be checked too.
116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
117 raise
117 raise
118 else:
118 else:
119 if stat.S_ISLNK(st.st_mode):
119 if stat.S_ISLNK(st.st_mode):
120 raise util.Abort(
120 raise util.Abort(
121 _('path %r traverses symbolic link %r')
121 _('path %r traverses symbolic link %r')
122 % (path, prefix))
122 % (path, prefix))
123 elif (stat.S_ISDIR(st.st_mode) and
123 elif (stat.S_ISDIR(st.st_mode) and
124 os.path.isdir(os.path.join(curpath, '.hg'))):
124 os.path.isdir(os.path.join(curpath, '.hg'))):
125 if not self.callback or not self.callback(curpath):
125 if not self.callback or not self.callback(curpath):
126 raise util.Abort(_('path %r is inside nested repo %r') %
126 raise util.Abort(_('path %r is inside nested repo %r') %
127 (path, prefix))
127 (path, prefix))
128 prefixes.append(prefix)
128 prefixes.append(prefix)
129 parts.pop()
129 parts.pop()
130
130
131 self.audited.add(path)
131 self.audited.add(path)
132 # only add prefixes to the cache after checking everything: we don't
132 # only add prefixes to the cache after checking everything: we don't
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 self.auditeddir.update(prefixes)
134 self.auditeddir.update(prefixes)
135
135
136 class abstractopener(object):
136 class abstractopener(object):
137 """Abstract base class; cannot be instantiated"""
137 """Abstract base class; cannot be instantiated"""
138
138
139 def __init__(self, *args, **kwargs):
139 def __init__(self, *args, **kwargs):
140 '''Prevent instantiation; don't call this from subclasses.'''
140 '''Prevent instantiation; don't call this from subclasses.'''
141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
142
142
143 def read(self, path):
143 def read(self, path):
144 fp = self(path, 'rb')
144 fp = self(path, 'rb')
145 try:
145 try:
146 return fp.read()
146 return fp.read()
147 finally:
147 finally:
148 fp.close()
148 fp.close()
149
149
150 def write(self, path, data):
150 def write(self, path, data):
151 fp = self(path, 'wb')
151 fp = self(path, 'wb')
152 try:
152 try:
153 return fp.write(data)
153 return fp.write(data)
154 finally:
154 finally:
155 fp.close()
155 fp.close()
156
156
157 def append(self, path, data):
157 def append(self, path, data):
158 fp = self(path, 'ab')
158 fp = self(path, 'ab')
159 try:
159 try:
160 return fp.write(data)
160 return fp.write(data)
161 finally:
161 finally:
162 fp.close()
162 fp.close()
163
163
164 class opener(abstractopener):
164 class opener(abstractopener):
165 '''Open files relative to a base directory
165 '''Open files relative to a base directory
166
166
167 This class is used to hide the details of COW semantics and
167 This class is used to hide the details of COW semantics and
168 remote file access from higher level code.
168 remote file access from higher level code.
169 '''
169 '''
170 def __init__(self, base, audit=True):
170 def __init__(self, base, audit=True):
171 self.base = base
171 self.base = base
172 if audit:
172 if audit:
173 self.auditor = pathauditor(base)
173 self.auditor = pathauditor(base)
174 else:
174 else:
175 self.auditor = util.always
175 self.auditor = util.always
176 self.createmode = None
176 self.createmode = None
177 self._trustnlink = None
177 self._trustnlink = None
178
178
179 @util.propertycache
179 @util.propertycache
180 def _cansymlink(self):
180 def _cansymlink(self):
181 return util.checklink(self.base)
181 return util.checklink(self.base)
182
182
183 def _fixfilemode(self, name):
183 def _fixfilemode(self, name):
184 if self.createmode is None:
184 if self.createmode is None:
185 return
185 return
186 os.chmod(name, self.createmode & 0666)
186 os.chmod(name, self.createmode & 0666)
187
187
188 def __call__(self, path, mode="r", text=False, atomictemp=False):
188 def __call__(self, path, mode="r", text=False, atomictemp=False):
189 r = util.checkosfilename(path)
189 r = util.checkosfilename(path)
190 if r:
190 if r:
191 raise util.Abort("%s: %r" % (r, path))
191 raise util.Abort("%s: %r" % (r, path))
192 self.auditor(path)
192 self.auditor(path)
193 f = os.path.join(self.base, path)
193 f = os.path.join(self.base, path)
194
194
195 if not text and "b" not in mode:
195 if not text and "b" not in mode:
196 mode += "b" # for that other OS
196 mode += "b" # for that other OS
197
197
198 nlink = -1
198 nlink = -1
199 dirname, basename = os.path.split(f)
199 dirname, basename = os.path.split(f)
200 # If basename is empty, then the path is malformed because it points
200 # If basename is empty, then the path is malformed because it points
201 # to a directory. Let the posixfile() call below raise IOError.
201 # to a directory. Let the posixfile() call below raise IOError.
202 if basename and mode not in ('r', 'rb'):
202 if basename and mode not in ('r', 'rb'):
203 if atomictemp:
203 if atomictemp:
204 if not os.path.isdir(dirname):
204 if not os.path.isdir(dirname):
205 util.makedirs(dirname, self.createmode)
205 util.makedirs(dirname, self.createmode)
206 return util.atomictempfile(f, mode, self.createmode)
206 return util.atomictempfile(f, mode, self.createmode)
207 try:
207 try:
208 if 'w' in mode:
208 if 'w' in mode:
209 util.unlink(f)
209 util.unlink(f)
210 nlink = 0
210 nlink = 0
211 else:
211 else:
212 # nlinks() may behave differently for files on Windows
212 # nlinks() may behave differently for files on Windows
213 # shares if the file is open.
213 # shares if the file is open.
214 fd = util.posixfile(f)
214 fd = util.posixfile(f)
215 nlink = util.nlinks(f)
215 nlink = util.nlinks(f)
216 if nlink < 1:
216 if nlink < 1:
217 nlink = 2 # force mktempcopy (issue1922)
217 nlink = 2 # force mktempcopy (issue1922)
218 fd.close()
218 fd.close()
219 except (OSError, IOError), e:
219 except (OSError, IOError), e:
220 if e.errno != errno.ENOENT:
220 if e.errno != errno.ENOENT:
221 raise
221 raise
222 nlink = 0
222 nlink = 0
223 if not os.path.isdir(dirname):
223 if not os.path.isdir(dirname):
224 util.makedirs(dirname, self.createmode)
224 util.makedirs(dirname, self.createmode)
225 if nlink > 0:
225 if nlink > 0:
226 if self._trustnlink is None:
226 if self._trustnlink is None:
227 self._trustnlink = nlink > 1 or util.checknlink(f)
227 self._trustnlink = nlink > 1 or util.checknlink(f)
228 if nlink > 1 or not self._trustnlink:
228 if nlink > 1 or not self._trustnlink:
229 util.rename(util.mktempcopy(f), f)
229 util.rename(util.mktempcopy(f), f)
230 fp = util.posixfile(f, mode)
230 fp = util.posixfile(f, mode)
231 if nlink == 0:
231 if nlink == 0:
232 self._fixfilemode(f)
232 self._fixfilemode(f)
233 return fp
233 return fp
234
234
235 def symlink(self, src, dst):
235 def symlink(self, src, dst):
236 self.auditor(dst)
236 self.auditor(dst)
237 linkname = os.path.join(self.base, dst)
237 linkname = os.path.join(self.base, dst)
238 try:
238 try:
239 os.unlink(linkname)
239 os.unlink(linkname)
240 except OSError:
240 except OSError:
241 pass
241 pass
242
242
243 dirname = os.path.dirname(linkname)
243 dirname = os.path.dirname(linkname)
244 if not os.path.exists(dirname):
244 if not os.path.exists(dirname):
245 util.makedirs(dirname, self.createmode)
245 util.makedirs(dirname, self.createmode)
246
246
247 if self._cansymlink:
247 if self._cansymlink:
248 try:
248 try:
249 os.symlink(src, linkname)
249 os.symlink(src, linkname)
250 except OSError, err:
250 except OSError, err:
251 raise OSError(err.errno, _('could not symlink to %r: %s') %
251 raise OSError(err.errno, _('could not symlink to %r: %s') %
252 (src, err.strerror), linkname)
252 (src, err.strerror), linkname)
253 else:
253 else:
254 f = self(dst, "w")
254 f = self(dst, "w")
255 f.write(src)
255 f.write(src)
256 f.close()
256 f.close()
257 self._fixfilemode(dst)
257 self._fixfilemode(dst)
258
258
259 def audit(self, path):
259 def audit(self, path):
260 self.auditor(path)
260 self.auditor(path)
261
261
262 class filteropener(abstractopener):
262 class filteropener(abstractopener):
263 '''Wrapper opener for filtering filenames with a function.'''
263 '''Wrapper opener for filtering filenames with a function.'''
264
264
265 def __init__(self, opener, filter):
265 def __init__(self, opener, filter):
266 self._filter = filter
266 self._filter = filter
267 self._orig = opener
267 self._orig = opener
268
268
269 def __call__(self, path, *args, **kwargs):
269 def __call__(self, path, *args, **kwargs):
270 return self._orig(self._filter(path), *args, **kwargs)
270 return self._orig(self._filter(path), *args, **kwargs)
271
271
272 def canonpath(root, cwd, myname, auditor=None):
272 def canonpath(root, cwd, myname, auditor=None):
273 '''return the canonical path of myname, given cwd and root'''
273 '''return the canonical path of myname, given cwd and root'''
274 if util.endswithsep(root):
274 if util.endswithsep(root):
275 rootsep = root
275 rootsep = root
276 else:
276 else:
277 rootsep = root + os.sep
277 rootsep = root + os.sep
278 name = myname
278 name = myname
279 if not os.path.isabs(name):
279 if not os.path.isabs(name):
280 name = os.path.join(root, cwd, name)
280 name = os.path.join(root, cwd, name)
281 name = os.path.normpath(name)
281 name = os.path.normpath(name)
282 if auditor is None:
282 if auditor is None:
283 auditor = pathauditor(root)
283 auditor = pathauditor(root)
284 if name != rootsep and name.startswith(rootsep):
284 if name != rootsep and name.startswith(rootsep):
285 name = name[len(rootsep):]
285 name = name[len(rootsep):]
286 auditor(name)
286 auditor(name)
287 return util.pconvert(name)
287 return util.pconvert(name)
288 elif name == root:
288 elif name == root:
289 return ''
289 return ''
290 else:
290 else:
291 # Determine whether `name' is in the hierarchy at or beneath `root',
291 # Determine whether `name' is in the hierarchy at or beneath `root',
292 # by iterating name=dirname(name) until that causes no change (can't
292 # by iterating name=dirname(name) until that causes no change (can't
293 # check name == '/', because that doesn't work on windows). For each
293 # check name == '/', because that doesn't work on windows). For each
294 # `name', compare dev/inode numbers. If they match, the list `rel'
294 # `name', compare dev/inode numbers. If they match, the list `rel'
295 # holds the reversed list of components making up the relative file
295 # holds the reversed list of components making up the relative file
296 # name we want.
296 # name we want.
297 root_st = os.stat(root)
297 root_st = os.stat(root)
298 rel = []
298 rel = []
299 while True:
299 while True:
300 try:
300 try:
301 name_st = os.stat(name)
301 name_st = os.stat(name)
302 except OSError:
302 except OSError:
303 break
303 break
304 if util.samestat(name_st, root_st):
304 if util.samestat(name_st, root_st):
305 if not rel:
305 if not rel:
306 # name was actually the same as root (maybe a symlink)
306 # name was actually the same as root (maybe a symlink)
307 return ''
307 return ''
308 rel.reverse()
308 rel.reverse()
309 name = os.path.join(*rel)
309 name = os.path.join(*rel)
310 auditor(name)
310 auditor(name)
311 return util.pconvert(name)
311 return util.pconvert(name)
312 dirname, basename = os.path.split(name)
312 dirname, basename = os.path.split(name)
313 rel.append(basename)
313 rel.append(basename)
314 if dirname == name:
314 if dirname == name:
315 break
315 break
316 name = dirname
316 name = dirname
317
317
318 raise util.Abort('%s not under root' % myname)
318 raise util.Abort('%s not under root' % myname)
319
319
320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
321 '''yield every hg repository under path, recursively.'''
321 '''yield every hg repository under path, recursively.'''
322 def errhandler(err):
322 def errhandler(err):
323 if err.filename == path:
323 if err.filename == path:
324 raise err
324 raise err
325 if followsym and hasattr(os.path, 'samestat'):
325 if followsym and hasattr(os.path, 'samestat'):
326 def adddir(dirlst, dirname):
326 def adddir(dirlst, dirname):
327 match = False
327 match = False
328 samestat = os.path.samestat
328 samestat = os.path.samestat
329 dirstat = os.stat(dirname)
329 dirstat = os.stat(dirname)
330 for lstdirstat in dirlst:
330 for lstdirstat in dirlst:
331 if samestat(dirstat, lstdirstat):
331 if samestat(dirstat, lstdirstat):
332 match = True
332 match = True
333 break
333 break
334 if not match:
334 if not match:
335 dirlst.append(dirstat)
335 dirlst.append(dirstat)
336 return not match
336 return not match
337 else:
337 else:
338 followsym = False
338 followsym = False
339
339
340 if (seen_dirs is None) and followsym:
340 if (seen_dirs is None) and followsym:
341 seen_dirs = []
341 seen_dirs = []
342 adddir(seen_dirs, path)
342 adddir(seen_dirs, path)
343 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
343 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
344 dirs.sort()
344 dirs.sort()
345 if '.hg' in dirs:
345 if '.hg' in dirs:
346 yield root # found a repository
346 yield root # found a repository
347 qroot = os.path.join(root, '.hg', 'patches')
347 qroot = os.path.join(root, '.hg', 'patches')
348 if os.path.isdir(os.path.join(qroot, '.hg')):
348 if os.path.isdir(os.path.join(qroot, '.hg')):
349 yield qroot # we have a patch queue repo here
349 yield qroot # we have a patch queue repo here
350 if recurse:
350 if recurse:
351 # avoid recursing inside the .hg directory
351 # avoid recursing inside the .hg directory
352 dirs.remove('.hg')
352 dirs.remove('.hg')
353 else:
353 else:
354 dirs[:] = [] # don't descend further
354 dirs[:] = [] # don't descend further
355 elif followsym:
355 elif followsym:
356 newdirs = []
356 newdirs = []
357 for d in dirs:
357 for d in dirs:
358 fname = os.path.join(root, d)
358 fname = os.path.join(root, d)
359 if adddir(seen_dirs, fname):
359 if adddir(seen_dirs, fname):
360 if os.path.islink(fname):
360 if os.path.islink(fname):
361 for hgname in walkrepos(fname, True, seen_dirs):
361 for hgname in walkrepos(fname, True, seen_dirs):
362 yield hgname
362 yield hgname
363 else:
363 else:
364 newdirs.append(d)
364 newdirs.append(d)
365 dirs[:] = newdirs
365 dirs[:] = newdirs
366
366
367 def osrcpath():
367 def osrcpath():
368 '''return default os-specific hgrc search path'''
368 '''return default os-specific hgrc search path'''
369 path = systemrcpath()
369 path = systemrcpath()
370 path.extend(userrcpath())
370 path.extend(userrcpath())
371 path = [os.path.normpath(f) for f in path]
371 path = [os.path.normpath(f) for f in path]
372 return path
372 return path
373
373
374 _rcpath = None
374 _rcpath = None
375
375
376 def rcpath():
376 def rcpath():
377 '''return hgrc search path. if env var HGRCPATH is set, use it.
377 '''return hgrc search path. if env var HGRCPATH is set, use it.
378 for each item in path, if directory, use files ending in .rc,
378 for each item in path, if directory, use files ending in .rc,
379 else use item.
379 else use item.
380 make HGRCPATH empty to only look in .hg/hgrc of current repo.
380 make HGRCPATH empty to only look in .hg/hgrc of current repo.
381 if no HGRCPATH, use default os-specific path.'''
381 if no HGRCPATH, use default os-specific path.'''
382 global _rcpath
382 global _rcpath
383 if _rcpath is None:
383 if _rcpath is None:
384 if 'HGRCPATH' in os.environ:
384 if 'HGRCPATH' in os.environ:
385 _rcpath = []
385 _rcpath = []
386 for p in os.environ['HGRCPATH'].split(os.pathsep):
386 for p in os.environ['HGRCPATH'].split(os.pathsep):
387 if not p:
387 if not p:
388 continue
388 continue
389 p = util.expandpath(p)
389 p = util.expandpath(p)
390 if os.path.isdir(p):
390 if os.path.isdir(p):
391 for f, kind in osutil.listdir(p):
391 for f, kind in osutil.listdir(p):
392 if f.endswith('.rc'):
392 if f.endswith('.rc'):
393 _rcpath.append(os.path.join(p, f))
393 _rcpath.append(os.path.join(p, f))
394 else:
394 else:
395 _rcpath.append(p)
395 _rcpath.append(p)
396 else:
396 else:
397 _rcpath = osrcpath()
397 _rcpath = osrcpath()
398 return _rcpath
398 return _rcpath
399
399
400 if os.name != 'nt':
400 if os.name != 'nt':
401
401
402 def rcfiles(path):
402 def rcfiles(path):
403 rcs = [os.path.join(path, 'hgrc')]
403 rcs = [os.path.join(path, 'hgrc')]
404 rcdir = os.path.join(path, 'hgrc.d')
404 rcdir = os.path.join(path, 'hgrc.d')
405 try:
405 try:
406 rcs.extend([os.path.join(rcdir, f)
406 rcs.extend([os.path.join(rcdir, f)
407 for f, kind in osutil.listdir(rcdir)
407 for f, kind in osutil.listdir(rcdir)
408 if f.endswith(".rc")])
408 if f.endswith(".rc")])
409 except OSError:
409 except OSError:
410 pass
410 pass
411 return rcs
411 return rcs
412
412
413 def systemrcpath():
413 def systemrcpath():
414 path = []
414 path = []
415 # old mod_python does not set sys.argv
415 # old mod_python does not set sys.argv
416 if len(getattr(sys, 'argv', [])) > 0:
416 if len(getattr(sys, 'argv', [])) > 0:
417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
419 path.extend(rcfiles('/etc/mercurial'))
419 path.extend(rcfiles('/etc/mercurial'))
420 return path
420 return path
421
421
422 def userrcpath():
422 def userrcpath():
423 return [os.path.expanduser('~/.hgrc')]
423 return [os.path.expanduser('~/.hgrc')]
424
424
425 else:
425 else:
426
426
427 _HKEY_LOCAL_MACHINE = 0x80000002L
427 _HKEY_LOCAL_MACHINE = 0x80000002L
428
428
429 def systemrcpath():
429 def systemrcpath():
430 '''return default os-specific hgrc search path'''
430 '''return default os-specific hgrc search path'''
431 rcpath = []
431 rcpath = []
432 filename = util.executablepath()
432 filename = util.executablepath()
433 # Use mercurial.ini found in directory with hg.exe
433 # Use mercurial.ini found in directory with hg.exe
434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
435 if os.path.isfile(progrc):
435 if os.path.isfile(progrc):
436 rcpath.append(progrc)
436 rcpath.append(progrc)
437 return rcpath
437 return rcpath
438 # Use hgrc.d found in directory with hg.exe
438 # Use hgrc.d found in directory with hg.exe
439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
440 if os.path.isdir(progrcd):
440 if os.path.isdir(progrcd):
441 for f, kind in osutil.listdir(progrcd):
441 for f, kind in osutil.listdir(progrcd):
442 if f.endswith('.rc'):
442 if f.endswith('.rc'):
443 rcpath.append(os.path.join(progrcd, f))
443 rcpath.append(os.path.join(progrcd, f))
444 return rcpath
444 return rcpath
445 # else look for a system rcpath in the registry
445 # else look for a system rcpath in the registry
446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
447 _HKEY_LOCAL_MACHINE)
447 _HKEY_LOCAL_MACHINE)
448 if not isinstance(value, str) or not value:
448 if not isinstance(value, str) or not value:
449 return rcpath
449 return rcpath
450 value = value.replace('/', os.sep)
450 value = value.replace('/', os.sep)
451 for p in value.split(os.pathsep):
451 for p in value.split(os.pathsep):
452 if p.lower().endswith('mercurial.ini'):
452 if p.lower().endswith('mercurial.ini'):
453 rcpath.append(p)
453 rcpath.append(p)
454 elif os.path.isdir(p):
454 elif os.path.isdir(p):
455 for f, kind in osutil.listdir(p):
455 for f, kind in osutil.listdir(p):
456 if f.endswith('.rc'):
456 if f.endswith('.rc'):
457 rcpath.append(os.path.join(p, f))
457 rcpath.append(os.path.join(p, f))
458 return rcpath
458 return rcpath
459
459
460 def userrcpath():
460 def userrcpath():
461 '''return os-specific hgrc search path to the user dir'''
461 '''return os-specific hgrc search path to the user dir'''
462 home = os.path.expanduser('~')
462 home = os.path.expanduser('~')
463 path = [os.path.join(home, 'mercurial.ini'),
463 path = [os.path.join(home, 'mercurial.ini'),
464 os.path.join(home, '.hgrc')]
464 os.path.join(home, '.hgrc')]
465 userprofile = os.environ.get('USERPROFILE')
465 userprofile = os.environ.get('USERPROFILE')
466 if userprofile:
466 if userprofile:
467 path.append(os.path.join(userprofile, 'mercurial.ini'))
467 path.append(os.path.join(userprofile, 'mercurial.ini'))
468 path.append(os.path.join(userprofile, '.hgrc'))
468 path.append(os.path.join(userprofile, '.hgrc'))
469 return path
469 return path
470
470
471 def revsingle(repo, revspec, default='.'):
471 def revsingle(repo, revspec, default='.'):
472 if not revspec:
472 if not revspec:
473 return repo[default]
473 return repo[default]
474
474
475 l = revrange(repo, [revspec])
475 l = revrange(repo, [revspec])
476 if len(l) < 1:
476 if len(l) < 1:
477 raise util.Abort(_('empty revision set'))
477 raise util.Abort(_('empty revision set'))
478 return repo[l[-1]]
478 return repo[l[-1]]
479
479
480 def revpair(repo, revs):
480 def revpair(repo, revs):
481 if not revs:
481 if not revs:
482 return repo.dirstate.p1(), None
482 return repo.dirstate.p1(), None
483
483
484 l = revrange(repo, revs)
484 l = revrange(repo, revs)
485
485
486 if len(l) == 0:
486 if len(l) == 0:
487 return repo.dirstate.p1(), None
487 return repo.dirstate.p1(), None
488
488
489 if len(l) == 1:
489 if len(l) == 1:
490 return repo.lookup(l[0]), None
490 return repo.lookup(l[0]), None
491
491
492 return repo.lookup(l[0]), repo.lookup(l[-1])
492 return repo.lookup(l[0]), repo.lookup(l[-1])
493
493
494 _revrangesep = ':'
494 _revrangesep = ':'
495
495
496 def revrange(repo, revs):
496 def revrange(repo, revs):
497 """Yield revision as strings from a list of revision specifications."""
497 """Yield revision as strings from a list of revision specifications."""
498
498
499 def revfix(repo, val, defval):
499 def revfix(repo, val, defval):
500 if not val and val != 0 and defval is not None:
500 if not val and val != 0 and defval is not None:
501 return defval
501 return defval
502 return repo.changelog.rev(repo.lookup(val))
502 return repo.changelog.rev(repo.lookup(val))
503
503
504 seen, l = set(), []
504 seen, l = set(), []
505 for spec in revs:
505 for spec in revs:
506 # attempt to parse old-style ranges first to deal with
506 # attempt to parse old-style ranges first to deal with
507 # things like old-tag which contain query metacharacters
507 # things like old-tag which contain query metacharacters
508 try:
508 try:
509 if isinstance(spec, int):
509 if isinstance(spec, int):
510 seen.add(spec)
510 seen.add(spec)
511 l.append(spec)
511 l.append(spec)
512 continue
512 continue
513
513
514 if _revrangesep in spec:
514 if _revrangesep in spec:
515 start, end = spec.split(_revrangesep, 1)
515 start, end = spec.split(_revrangesep, 1)
516 start = revfix(repo, start, 0)
516 start = revfix(repo, start, 0)
517 end = revfix(repo, end, len(repo) - 1)
517 end = revfix(repo, end, len(repo) - 1)
518 step = start > end and -1 or 1
518 step = start > end and -1 or 1
519 for rev in xrange(start, end + step, step):
519 for rev in xrange(start, end + step, step):
520 if rev in seen:
520 if rev in seen:
521 continue
521 continue
522 seen.add(rev)
522 seen.add(rev)
523 l.append(rev)
523 l.append(rev)
524 continue
524 continue
525 elif spec and spec in repo: # single unquoted rev
525 elif spec and spec in repo: # single unquoted rev
526 rev = revfix(repo, spec, None)
526 rev = revfix(repo, spec, None)
527 if rev in seen:
527 if rev in seen:
528 continue
528 continue
529 seen.add(rev)
529 seen.add(rev)
530 l.append(rev)
530 l.append(rev)
531 continue
531 continue
532 except error.RepoLookupError:
532 except error.RepoLookupError:
533 pass
533 pass
534
534
535 # fall through to new-style queries if old-style fails
535 # fall through to new-style queries if old-style fails
536 m = revset.match(repo.ui, spec)
536 m = revset.match(repo.ui, spec)
537 for r in m(repo, range(len(repo))):
537 for r in m(repo, range(len(repo))):
538 if r not in seen:
538 if r not in seen:
539 l.append(r)
539 l.append(r)
540 seen.update(l)
540 seen.update(l)
541
541
542 return l
542 return l
543
543
544 def expandpats(pats):
544 def expandpats(pats):
545 if not util.expandglobs:
545 if not util.expandglobs:
546 return list(pats)
546 return list(pats)
547 ret = []
547 ret = []
548 for p in pats:
548 for p in pats:
549 kind, name = matchmod._patsplit(p, None)
549 kind, name = matchmod._patsplit(p, None)
550 if kind is None:
550 if kind is None:
551 try:
551 try:
552 globbed = glob.glob(name)
552 globbed = glob.glob(name)
553 except re.error:
553 except re.error:
554 globbed = [name]
554 globbed = [name]
555 if globbed:
555 if globbed:
556 ret.extend(globbed)
556 ret.extend(globbed)
557 continue
557 continue
558 ret.append(p)
558 ret.append(p)
559 return ret
559 return ret
560
560
561 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
561 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
562 if pats == ("",):
562 if pats == ("",):
563 pats = []
563 pats = []
564 if not globbed and default == 'relpath':
564 if not globbed and default == 'relpath':
565 pats = expandpats(pats or [])
565 pats = expandpats(pats or [])
566 m = matchmod.match(repo.root, repo.getcwd(), pats,
566 m = repo[None].match(pats, opts.get('include'), opts.get('exclude'),
567 opts.get('include'), opts.get('exclude'), default,
567 default)
568 auditor=repo.auditor)
569 def badfn(f, msg):
568 def badfn(f, msg):
570 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
569 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
571 m.bad = badfn
570 m.bad = badfn
572 return m
571 return m
573
572
574 def matchall(repo):
573 def matchall(repo):
575 return matchmod.always(repo.root, repo.getcwd())
574 return matchmod.always(repo.root, repo.getcwd())
576
575
577 def matchfiles(repo, files):
576 def matchfiles(repo, files):
578 return matchmod.exact(repo.root, repo.getcwd(), files)
577 return matchmod.exact(repo.root, repo.getcwd(), files)
579
578
580 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
579 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
581 if dry_run is None:
580 if dry_run is None:
582 dry_run = opts.get('dry_run')
581 dry_run = opts.get('dry_run')
583 if similarity is None:
582 if similarity is None:
584 similarity = float(opts.get('similarity') or 0)
583 similarity = float(opts.get('similarity') or 0)
585 # we'd use status here, except handling of symlinks and ignore is tricky
584 # we'd use status here, except handling of symlinks and ignore is tricky
586 added, unknown, deleted, removed = [], [], [], []
585 added, unknown, deleted, removed = [], [], [], []
587 audit_path = pathauditor(repo.root)
586 audit_path = pathauditor(repo.root)
588 m = match(repo, pats, opts)
587 m = match(repo, pats, opts)
589 for abs in repo.walk(m):
588 for abs in repo.walk(m):
590 target = repo.wjoin(abs)
589 target = repo.wjoin(abs)
591 good = True
590 good = True
592 try:
591 try:
593 audit_path(abs)
592 audit_path(abs)
594 except (OSError, util.Abort):
593 except (OSError, util.Abort):
595 good = False
594 good = False
596 rel = m.rel(abs)
595 rel = m.rel(abs)
597 exact = m.exact(abs)
596 exact = m.exact(abs)
598 if good and abs not in repo.dirstate:
597 if good and abs not in repo.dirstate:
599 unknown.append(abs)
598 unknown.append(abs)
600 if repo.ui.verbose or not exact:
599 if repo.ui.verbose or not exact:
601 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
600 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
602 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
601 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
603 or (os.path.isdir(target) and not os.path.islink(target))):
602 or (os.path.isdir(target) and not os.path.islink(target))):
604 deleted.append(abs)
603 deleted.append(abs)
605 if repo.ui.verbose or not exact:
604 if repo.ui.verbose or not exact:
606 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
605 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
607 # for finding renames
606 # for finding renames
608 elif repo.dirstate[abs] == 'r':
607 elif repo.dirstate[abs] == 'r':
609 removed.append(abs)
608 removed.append(abs)
610 elif repo.dirstate[abs] == 'a':
609 elif repo.dirstate[abs] == 'a':
611 added.append(abs)
610 added.append(abs)
612 copies = {}
611 copies = {}
613 if similarity > 0:
612 if similarity > 0:
614 for old, new, score in similar.findrenames(repo,
613 for old, new, score in similar.findrenames(repo,
615 added + unknown, removed + deleted, similarity):
614 added + unknown, removed + deleted, similarity):
616 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
615 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
617 repo.ui.status(_('recording removal of %s as rename to %s '
616 repo.ui.status(_('recording removal of %s as rename to %s '
618 '(%d%% similar)\n') %
617 '(%d%% similar)\n') %
619 (m.rel(old), m.rel(new), score * 100))
618 (m.rel(old), m.rel(new), score * 100))
620 copies[new] = old
619 copies[new] = old
621
620
622 if not dry_run:
621 if not dry_run:
623 wctx = repo[None]
622 wctx = repo[None]
624 wlock = repo.wlock()
623 wlock = repo.wlock()
625 try:
624 try:
626 wctx.forget(deleted)
625 wctx.forget(deleted)
627 wctx.add(unknown)
626 wctx.add(unknown)
628 for new, old in copies.iteritems():
627 for new, old in copies.iteritems():
629 wctx.copy(old, new)
628 wctx.copy(old, new)
630 finally:
629 finally:
631 wlock.release()
630 wlock.release()
632
631
633 def updatedir(ui, repo, patches, similarity=0):
632 def updatedir(ui, repo, patches, similarity=0):
634 '''Update dirstate after patch application according to metadata'''
633 '''Update dirstate after patch application according to metadata'''
635 if not patches:
634 if not patches:
636 return []
635 return []
637 copies = []
636 copies = []
638 removes = set()
637 removes = set()
639 cfiles = patches.keys()
638 cfiles = patches.keys()
640 cwd = repo.getcwd()
639 cwd = repo.getcwd()
641 if cwd:
640 if cwd:
642 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
641 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
643 for f in patches:
642 for f in patches:
644 gp = patches[f]
643 gp = patches[f]
645 if not gp:
644 if not gp:
646 continue
645 continue
647 if gp.op == 'RENAME':
646 if gp.op == 'RENAME':
648 copies.append((gp.oldpath, gp.path))
647 copies.append((gp.oldpath, gp.path))
649 removes.add(gp.oldpath)
648 removes.add(gp.oldpath)
650 elif gp.op == 'COPY':
649 elif gp.op == 'COPY':
651 copies.append((gp.oldpath, gp.path))
650 copies.append((gp.oldpath, gp.path))
652 elif gp.op == 'DELETE':
651 elif gp.op == 'DELETE':
653 removes.add(gp.path)
652 removes.add(gp.path)
654
653
655 wctx = repo[None]
654 wctx = repo[None]
656 for src, dst in copies:
655 for src, dst in copies:
657 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
656 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
658 if (not similarity) and removes:
657 if (not similarity) and removes:
659 wctx.remove(sorted(removes), True)
658 wctx.remove(sorted(removes), True)
660
659
661 for f in patches:
660 for f in patches:
662 gp = patches[f]
661 gp = patches[f]
663 if gp and gp.mode:
662 if gp and gp.mode:
664 islink, isexec = gp.mode
663 islink, isexec = gp.mode
665 dst = repo.wjoin(gp.path)
664 dst = repo.wjoin(gp.path)
666 # patch won't create empty files
665 # patch won't create empty files
667 if gp.op == 'ADD' and not os.path.lexists(dst):
666 if gp.op == 'ADD' and not os.path.lexists(dst):
668 flags = (isexec and 'x' or '') + (islink and 'l' or '')
667 flags = (isexec and 'x' or '') + (islink and 'l' or '')
669 repo.wwrite(gp.path, '', flags)
668 repo.wwrite(gp.path, '', flags)
670 util.setflags(dst, islink, isexec)
669 util.setflags(dst, islink, isexec)
671 addremove(repo, cfiles, similarity=similarity)
670 addremove(repo, cfiles, similarity=similarity)
672 files = patches.keys()
671 files = patches.keys()
673 files.extend([r for r in removes if r not in files])
672 files.extend([r for r in removes if r not in files])
674 return sorted(files)
673 return sorted(files)
675
674
676 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
675 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
677 """Update the dirstate to reflect the intent of copying src to dst. For
676 """Update the dirstate to reflect the intent of copying src to dst. For
678 different reasons it might not end with dst being marked as copied from src.
677 different reasons it might not end with dst being marked as copied from src.
679 """
678 """
680 origsrc = repo.dirstate.copied(src) or src
679 origsrc = repo.dirstate.copied(src) or src
681 if dst == origsrc: # copying back a copy?
680 if dst == origsrc: # copying back a copy?
682 if repo.dirstate[dst] not in 'mn' and not dryrun:
681 if repo.dirstate[dst] not in 'mn' and not dryrun:
683 repo.dirstate.normallookup(dst)
682 repo.dirstate.normallookup(dst)
684 else:
683 else:
685 if repo.dirstate[origsrc] == 'a' and origsrc == src:
684 if repo.dirstate[origsrc] == 'a' and origsrc == src:
686 if not ui.quiet:
685 if not ui.quiet:
687 ui.warn(_("%s has not been committed yet, so no copy "
686 ui.warn(_("%s has not been committed yet, so no copy "
688 "data will be stored for %s.\n")
687 "data will be stored for %s.\n")
689 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
688 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
690 if repo.dirstate[dst] in '?r' and not dryrun:
689 if repo.dirstate[dst] in '?r' and not dryrun:
691 wctx.add([dst])
690 wctx.add([dst])
692 elif not dryrun:
691 elif not dryrun:
693 wctx.copy(origsrc, dst)
692 wctx.copy(origsrc, dst)
694
693
695 def readrequires(opener, supported):
694 def readrequires(opener, supported):
696 '''Reads and parses .hg/requires and checks if all entries found
695 '''Reads and parses .hg/requires and checks if all entries found
697 are in the list of supported features.'''
696 are in the list of supported features.'''
698 requirements = set(opener.read("requires").splitlines())
697 requirements = set(opener.read("requires").splitlines())
699 for r in requirements:
698 for r in requirements:
700 if r not in supported:
699 if r not in supported:
701 if not r or not r[0].isalnum():
700 if not r or not r[0].isalnum():
702 raise error.RequirementError(_(".hg/requires file is corrupt"))
701 raise error.RequirementError(_(".hg/requires file is corrupt"))
703 raise error.RequirementError(_("unknown repository format: "
702 raise error.RequirementError(_("unknown repository format: "
704 "requires feature '%s' (upgrade Mercurial)") % r)
703 "requires feature '%s' (upgrade Mercurial)") % r)
705 return requirements
704 return requirements
General Comments 0
You need to be logged in to leave comments. Login now