##// END OF EJS Templates
util: extract stub function to get mtime with second accuracy...
Yuya Nishihara -
r26492:3a0bb613 default
parent child Browse files
Show More
@@ -1,1929 +1,1929 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, wdirid, short, hex, bin
8 from node import nullid, nullrev, wdirid, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import match as matchmod
11 import match as matchmod
12 import os, errno, stat
12 import os, errno, stat
13 import obsolete as obsmod
13 import obsolete as obsmod
14 import repoview
14 import repoview
15 import fileset
15 import fileset
16 import revlog
16 import revlog
17
17
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 # Phony node value to stand-in for new files in some uses of
20 # Phony node value to stand-in for new files in some uses of
21 # manifests. Manifests support 21-byte hashes for nodes which are
21 # manifests. Manifests support 21-byte hashes for nodes which are
22 # dirty in the working copy.
22 # dirty in the working copy.
23 _newnode = '!' * 21
23 _newnode = '!' * 21
24
24
25 class basectx(object):
25 class basectx(object):
26 """A basectx object represents the common logic for its children:
26 """A basectx object represents the common logic for its children:
27 changectx: read-only context that is already present in the repo,
27 changectx: read-only context that is already present in the repo,
28 workingctx: a context that represents the working directory and can
28 workingctx: a context that represents the working directory and can
29 be committed,
29 be committed,
30 memctx: a context that represents changes in-memory and can also
30 memctx: a context that represents changes in-memory and can also
31 be committed."""
31 be committed."""
32 def __new__(cls, repo, changeid='', *args, **kwargs):
32 def __new__(cls, repo, changeid='', *args, **kwargs):
33 if isinstance(changeid, basectx):
33 if isinstance(changeid, basectx):
34 return changeid
34 return changeid
35
35
36 o = super(basectx, cls).__new__(cls)
36 o = super(basectx, cls).__new__(cls)
37
37
38 o._repo = repo
38 o._repo = repo
39 o._rev = nullrev
39 o._rev = nullrev
40 o._node = nullid
40 o._node = nullid
41
41
42 return o
42 return o
43
43
44 def __str__(self):
44 def __str__(self):
45 return short(self.node())
45 return short(self.node())
46
46
47 def __int__(self):
47 def __int__(self):
48 return self.rev()
48 return self.rev()
49
49
50 def __repr__(self):
50 def __repr__(self):
51 return "<%s %s>" % (type(self).__name__, str(self))
51 return "<%s %s>" % (type(self).__name__, str(self))
52
52
53 def __eq__(self, other):
53 def __eq__(self, other):
54 try:
54 try:
55 return type(self) == type(other) and self._rev == other._rev
55 return type(self) == type(other) and self._rev == other._rev
56 except AttributeError:
56 except AttributeError:
57 return False
57 return False
58
58
59 def __ne__(self, other):
59 def __ne__(self, other):
60 return not (self == other)
60 return not (self == other)
61
61
62 def __contains__(self, key):
62 def __contains__(self, key):
63 return key in self._manifest
63 return key in self._manifest
64
64
65 def __getitem__(self, key):
65 def __getitem__(self, key):
66 return self.filectx(key)
66 return self.filectx(key)
67
67
68 def __iter__(self):
68 def __iter__(self):
69 return iter(self._manifest)
69 return iter(self._manifest)
70
70
71 def _manifestmatches(self, match, s):
71 def _manifestmatches(self, match, s):
72 """generate a new manifest filtered by the match argument
72 """generate a new manifest filtered by the match argument
73
73
74 This method is for internal use only and mainly exists to provide an
74 This method is for internal use only and mainly exists to provide an
75 object oriented way for other contexts to customize the manifest
75 object oriented way for other contexts to customize the manifest
76 generation.
76 generation.
77 """
77 """
78 return self.manifest().matches(match)
78 return self.manifest().matches(match)
79
79
80 def _matchstatus(self, other, match):
80 def _matchstatus(self, other, match):
81 """return match.always if match is none
81 """return match.always if match is none
82
82
83 This internal method provides a way for child objects to override the
83 This internal method provides a way for child objects to override the
84 match operator.
84 match operator.
85 """
85 """
86 return match or matchmod.always(self._repo.root, self._repo.getcwd())
86 return match or matchmod.always(self._repo.root, self._repo.getcwd())
87
87
88 def _buildstatus(self, other, s, match, listignored, listclean,
88 def _buildstatus(self, other, s, match, listignored, listclean,
89 listunknown):
89 listunknown):
90 """build a status with respect to another context"""
90 """build a status with respect to another context"""
91 # Load earliest manifest first for caching reasons. More specifically,
91 # Load earliest manifest first for caching reasons. More specifically,
92 # if you have revisions 1000 and 1001, 1001 is probably stored as a
92 # if you have revisions 1000 and 1001, 1001 is probably stored as a
93 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
93 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
94 # 1000 and cache it so that when you read 1001, we just need to apply a
94 # 1000 and cache it so that when you read 1001, we just need to apply a
95 # delta to what's in the cache. So that's one full reconstruction + one
95 # delta to what's in the cache. So that's one full reconstruction + one
96 # delta application.
96 # delta application.
97 if self.rev() is not None and self.rev() < other.rev():
97 if self.rev() is not None and self.rev() < other.rev():
98 self.manifest()
98 self.manifest()
99 mf1 = other._manifestmatches(match, s)
99 mf1 = other._manifestmatches(match, s)
100 mf2 = self._manifestmatches(match, s)
100 mf2 = self._manifestmatches(match, s)
101
101
102 modified, added = [], []
102 modified, added = [], []
103 removed = []
103 removed = []
104 clean = []
104 clean = []
105 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
105 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
106 deletedset = set(deleted)
106 deletedset = set(deleted)
107 d = mf1.diff(mf2, clean=listclean)
107 d = mf1.diff(mf2, clean=listclean)
108 for fn, value in d.iteritems():
108 for fn, value in d.iteritems():
109 if fn in deletedset:
109 if fn in deletedset:
110 continue
110 continue
111 if value is None:
111 if value is None:
112 clean.append(fn)
112 clean.append(fn)
113 continue
113 continue
114 (node1, flag1), (node2, flag2) = value
114 (node1, flag1), (node2, flag2) = value
115 if node1 is None:
115 if node1 is None:
116 added.append(fn)
116 added.append(fn)
117 elif node2 is None:
117 elif node2 is None:
118 removed.append(fn)
118 removed.append(fn)
119 elif node2 != _newnode:
119 elif node2 != _newnode:
120 # The file was not a new file in mf2, so an entry
120 # The file was not a new file in mf2, so an entry
121 # from diff is really a difference.
121 # from diff is really a difference.
122 modified.append(fn)
122 modified.append(fn)
123 elif self[fn].cmp(other[fn]):
123 elif self[fn].cmp(other[fn]):
124 # node2 was newnode, but the working file doesn't
124 # node2 was newnode, but the working file doesn't
125 # match the one in mf1.
125 # match the one in mf1.
126 modified.append(fn)
126 modified.append(fn)
127 else:
127 else:
128 clean.append(fn)
128 clean.append(fn)
129
129
130 if removed:
130 if removed:
131 # need to filter files if they are already reported as removed
131 # need to filter files if they are already reported as removed
132 unknown = [fn for fn in unknown if fn not in mf1]
132 unknown = [fn for fn in unknown if fn not in mf1]
133 ignored = [fn for fn in ignored if fn not in mf1]
133 ignored = [fn for fn in ignored if fn not in mf1]
134 # if they're deleted, don't report them as removed
134 # if they're deleted, don't report them as removed
135 removed = [fn for fn in removed if fn not in deletedset]
135 removed = [fn for fn in removed if fn not in deletedset]
136
136
137 return scmutil.status(modified, added, removed, deleted, unknown,
137 return scmutil.status(modified, added, removed, deleted, unknown,
138 ignored, clean)
138 ignored, clean)
139
139
140 @propertycache
140 @propertycache
141 def substate(self):
141 def substate(self):
142 return subrepo.state(self, self._repo.ui)
142 return subrepo.state(self, self._repo.ui)
143
143
144 def subrev(self, subpath):
144 def subrev(self, subpath):
145 return self.substate[subpath][1]
145 return self.substate[subpath][1]
146
146
147 def rev(self):
147 def rev(self):
148 return self._rev
148 return self._rev
149 def node(self):
149 def node(self):
150 return self._node
150 return self._node
151 def hex(self):
151 def hex(self):
152 return hex(self.node())
152 return hex(self.node())
153 def manifest(self):
153 def manifest(self):
154 return self._manifest
154 return self._manifest
155 def repo(self):
155 def repo(self):
156 return self._repo
156 return self._repo
157 def phasestr(self):
157 def phasestr(self):
158 return phases.phasenames[self.phase()]
158 return phases.phasenames[self.phase()]
159 def mutable(self):
159 def mutable(self):
160 return self.phase() > phases.public
160 return self.phase() > phases.public
161
161
162 def getfileset(self, expr):
162 def getfileset(self, expr):
163 return fileset.getfileset(self, expr)
163 return fileset.getfileset(self, expr)
164
164
165 def obsolete(self):
165 def obsolete(self):
166 """True if the changeset is obsolete"""
166 """True if the changeset is obsolete"""
167 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
167 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
168
168
169 def extinct(self):
169 def extinct(self):
170 """True if the changeset is extinct"""
170 """True if the changeset is extinct"""
171 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
171 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
172
172
173 def unstable(self):
173 def unstable(self):
174 """True if the changeset is not obsolete but it's ancestor are"""
174 """True if the changeset is not obsolete but it's ancestor are"""
175 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
175 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
176
176
177 def bumped(self):
177 def bumped(self):
178 """True if the changeset try to be a successor of a public changeset
178 """True if the changeset try to be a successor of a public changeset
179
179
180 Only non-public and non-obsolete changesets may be bumped.
180 Only non-public and non-obsolete changesets may be bumped.
181 """
181 """
182 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
182 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
183
183
184 def divergent(self):
184 def divergent(self):
185 """Is a successors of a changeset with multiple possible successors set
185 """Is a successors of a changeset with multiple possible successors set
186
186
187 Only non-public and non-obsolete changesets may be divergent.
187 Only non-public and non-obsolete changesets may be divergent.
188 """
188 """
189 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
189 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
190
190
191 def troubled(self):
191 def troubled(self):
192 """True if the changeset is either unstable, bumped or divergent"""
192 """True if the changeset is either unstable, bumped or divergent"""
193 return self.unstable() or self.bumped() or self.divergent()
193 return self.unstable() or self.bumped() or self.divergent()
194
194
195 def troubles(self):
195 def troubles(self):
196 """return the list of troubles affecting this changesets.
196 """return the list of troubles affecting this changesets.
197
197
198 Troubles are returned as strings. possible values are:
198 Troubles are returned as strings. possible values are:
199 - unstable,
199 - unstable,
200 - bumped,
200 - bumped,
201 - divergent.
201 - divergent.
202 """
202 """
203 troubles = []
203 troubles = []
204 if self.unstable():
204 if self.unstable():
205 troubles.append('unstable')
205 troubles.append('unstable')
206 if self.bumped():
206 if self.bumped():
207 troubles.append('bumped')
207 troubles.append('bumped')
208 if self.divergent():
208 if self.divergent():
209 troubles.append('divergent')
209 troubles.append('divergent')
210 return troubles
210 return troubles
211
211
212 def parents(self):
212 def parents(self):
213 """return contexts for each parent changeset"""
213 """return contexts for each parent changeset"""
214 return self._parents
214 return self._parents
215
215
216 def p1(self):
216 def p1(self):
217 return self._parents[0]
217 return self._parents[0]
218
218
219 def p2(self):
219 def p2(self):
220 if len(self._parents) == 2:
220 if len(self._parents) == 2:
221 return self._parents[1]
221 return self._parents[1]
222 return changectx(self._repo, -1)
222 return changectx(self._repo, -1)
223
223
224 def _fileinfo(self, path):
224 def _fileinfo(self, path):
225 if '_manifest' in self.__dict__:
225 if '_manifest' in self.__dict__:
226 try:
226 try:
227 return self._manifest[path], self._manifest.flags(path)
227 return self._manifest[path], self._manifest.flags(path)
228 except KeyError:
228 except KeyError:
229 raise error.ManifestLookupError(self._node, path,
229 raise error.ManifestLookupError(self._node, path,
230 _('not found in manifest'))
230 _('not found in manifest'))
231 if '_manifestdelta' in self.__dict__ or path in self.files():
231 if '_manifestdelta' in self.__dict__ or path in self.files():
232 if path in self._manifestdelta:
232 if path in self._manifestdelta:
233 return (self._manifestdelta[path],
233 return (self._manifestdelta[path],
234 self._manifestdelta.flags(path))
234 self._manifestdelta.flags(path))
235 node, flag = self._repo.manifest.find(self._changeset[0], path)
235 node, flag = self._repo.manifest.find(self._changeset[0], path)
236 if not node:
236 if not node:
237 raise error.ManifestLookupError(self._node, path,
237 raise error.ManifestLookupError(self._node, path,
238 _('not found in manifest'))
238 _('not found in manifest'))
239
239
240 return node, flag
240 return node, flag
241
241
242 def filenode(self, path):
242 def filenode(self, path):
243 return self._fileinfo(path)[0]
243 return self._fileinfo(path)[0]
244
244
245 def flags(self, path):
245 def flags(self, path):
246 try:
246 try:
247 return self._fileinfo(path)[1]
247 return self._fileinfo(path)[1]
248 except error.LookupError:
248 except error.LookupError:
249 return ''
249 return ''
250
250
251 def sub(self, path):
251 def sub(self, path):
252 '''return a subrepo for the stored revision of path, never wdir()'''
252 '''return a subrepo for the stored revision of path, never wdir()'''
253 return subrepo.subrepo(self, path)
253 return subrepo.subrepo(self, path)
254
254
255 def nullsub(self, path, pctx):
255 def nullsub(self, path, pctx):
256 return subrepo.nullsubrepo(self, path, pctx)
256 return subrepo.nullsubrepo(self, path, pctx)
257
257
258 def workingsub(self, path):
258 def workingsub(self, path):
259 '''return a subrepo for the stored revision, or wdir if this is a wdir
259 '''return a subrepo for the stored revision, or wdir if this is a wdir
260 context.
260 context.
261 '''
261 '''
262 return subrepo.subrepo(self, path, allowwdir=True)
262 return subrepo.subrepo(self, path, allowwdir=True)
263
263
264 def match(self, pats=[], include=None, exclude=None, default='glob',
264 def match(self, pats=[], include=None, exclude=None, default='glob',
265 listsubrepos=False, badfn=None):
265 listsubrepos=False, badfn=None):
266 r = self._repo
266 r = self._repo
267 return matchmod.match(r.root, r.getcwd(), pats,
267 return matchmod.match(r.root, r.getcwd(), pats,
268 include, exclude, default,
268 include, exclude, default,
269 auditor=r.auditor, ctx=self,
269 auditor=r.auditor, ctx=self,
270 listsubrepos=listsubrepos, badfn=badfn)
270 listsubrepos=listsubrepos, badfn=badfn)
271
271
272 def diff(self, ctx2=None, match=None, **opts):
272 def diff(self, ctx2=None, match=None, **opts):
273 """Returns a diff generator for the given contexts and matcher"""
273 """Returns a diff generator for the given contexts and matcher"""
274 if ctx2 is None:
274 if ctx2 is None:
275 ctx2 = self.p1()
275 ctx2 = self.p1()
276 if ctx2 is not None:
276 if ctx2 is not None:
277 ctx2 = self._repo[ctx2]
277 ctx2 = self._repo[ctx2]
278 diffopts = patch.diffopts(self._repo.ui, opts)
278 diffopts = patch.diffopts(self._repo.ui, opts)
279 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
279 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
280
280
281 def dirs(self):
281 def dirs(self):
282 return self._manifest.dirs()
282 return self._manifest.dirs()
283
283
284 def hasdir(self, dir):
284 def hasdir(self, dir):
285 return self._manifest.hasdir(dir)
285 return self._manifest.hasdir(dir)
286
286
287 def dirty(self, missing=False, merge=True, branch=True):
287 def dirty(self, missing=False, merge=True, branch=True):
288 return False
288 return False
289
289
290 def status(self, other=None, match=None, listignored=False,
290 def status(self, other=None, match=None, listignored=False,
291 listclean=False, listunknown=False, listsubrepos=False):
291 listclean=False, listunknown=False, listsubrepos=False):
292 """return status of files between two nodes or node and working
292 """return status of files between two nodes or node and working
293 directory.
293 directory.
294
294
295 If other is None, compare this node with working directory.
295 If other is None, compare this node with working directory.
296
296
297 returns (modified, added, removed, deleted, unknown, ignored, clean)
297 returns (modified, added, removed, deleted, unknown, ignored, clean)
298 """
298 """
299
299
300 ctx1 = self
300 ctx1 = self
301 ctx2 = self._repo[other]
301 ctx2 = self._repo[other]
302
302
303 # This next code block is, admittedly, fragile logic that tests for
303 # This next code block is, admittedly, fragile logic that tests for
304 # reversing the contexts and wouldn't need to exist if it weren't for
304 # reversing the contexts and wouldn't need to exist if it weren't for
305 # the fast (and common) code path of comparing the working directory
305 # the fast (and common) code path of comparing the working directory
306 # with its first parent.
306 # with its first parent.
307 #
307 #
308 # What we're aiming for here is the ability to call:
308 # What we're aiming for here is the ability to call:
309 #
309 #
310 # workingctx.status(parentctx)
310 # workingctx.status(parentctx)
311 #
311 #
312 # If we always built the manifest for each context and compared those,
312 # If we always built the manifest for each context and compared those,
313 # then we'd be done. But the special case of the above call means we
313 # then we'd be done. But the special case of the above call means we
314 # just copy the manifest of the parent.
314 # just copy the manifest of the parent.
315 reversed = False
315 reversed = False
316 if (not isinstance(ctx1, changectx)
316 if (not isinstance(ctx1, changectx)
317 and isinstance(ctx2, changectx)):
317 and isinstance(ctx2, changectx)):
318 reversed = True
318 reversed = True
319 ctx1, ctx2 = ctx2, ctx1
319 ctx1, ctx2 = ctx2, ctx1
320
320
321 match = ctx2._matchstatus(ctx1, match)
321 match = ctx2._matchstatus(ctx1, match)
322 r = scmutil.status([], [], [], [], [], [], [])
322 r = scmutil.status([], [], [], [], [], [], [])
323 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
323 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
324 listunknown)
324 listunknown)
325
325
326 if reversed:
326 if reversed:
327 # Reverse added and removed. Clear deleted, unknown and ignored as
327 # Reverse added and removed. Clear deleted, unknown and ignored as
328 # these make no sense to reverse.
328 # these make no sense to reverse.
329 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
329 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
330 r.clean)
330 r.clean)
331
331
332 if listsubrepos:
332 if listsubrepos:
333 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
333 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
334 rev2 = ctx2.subrev(subpath)
334 rev2 = ctx2.subrev(subpath)
335 try:
335 try:
336 submatch = matchmod.narrowmatcher(subpath, match)
336 submatch = matchmod.narrowmatcher(subpath, match)
337 s = sub.status(rev2, match=submatch, ignored=listignored,
337 s = sub.status(rev2, match=submatch, ignored=listignored,
338 clean=listclean, unknown=listunknown,
338 clean=listclean, unknown=listunknown,
339 listsubrepos=True)
339 listsubrepos=True)
340 for rfiles, sfiles in zip(r, s):
340 for rfiles, sfiles in zip(r, s):
341 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
341 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
342 except error.LookupError:
342 except error.LookupError:
343 self._repo.ui.status(_("skipping missing "
343 self._repo.ui.status(_("skipping missing "
344 "subrepository: %s\n") % subpath)
344 "subrepository: %s\n") % subpath)
345
345
346 for l in r:
346 for l in r:
347 l.sort()
347 l.sort()
348
348
349 return r
349 return r
350
350
351
351
352 def makememctx(repo, parents, text, user, date, branch, files, store,
352 def makememctx(repo, parents, text, user, date, branch, files, store,
353 editor=None, extra=None):
353 editor=None, extra=None):
354 def getfilectx(repo, memctx, path):
354 def getfilectx(repo, memctx, path):
355 data, mode, copied = store.getfile(path)
355 data, mode, copied = store.getfile(path)
356 if data is None:
356 if data is None:
357 return None
357 return None
358 islink, isexec = mode
358 islink, isexec = mode
359 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
359 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
360 copied=copied, memctx=memctx)
360 copied=copied, memctx=memctx)
361 if extra is None:
361 if extra is None:
362 extra = {}
362 extra = {}
363 if branch:
363 if branch:
364 extra['branch'] = encoding.fromlocal(branch)
364 extra['branch'] = encoding.fromlocal(branch)
365 ctx = memctx(repo, parents, text, files, getfilectx, user,
365 ctx = memctx(repo, parents, text, files, getfilectx, user,
366 date, extra, editor)
366 date, extra, editor)
367 return ctx
367 return ctx
368
368
369 class changectx(basectx):
369 class changectx(basectx):
370 """A changecontext object makes access to data related to a particular
370 """A changecontext object makes access to data related to a particular
371 changeset convenient. It represents a read-only context already present in
371 changeset convenient. It represents a read-only context already present in
372 the repo."""
372 the repo."""
373 def __init__(self, repo, changeid=''):
373 def __init__(self, repo, changeid=''):
374 """changeid is a revision number, node, or tag"""
374 """changeid is a revision number, node, or tag"""
375
375
376 # since basectx.__new__ already took care of copying the object, we
376 # since basectx.__new__ already took care of copying the object, we
377 # don't need to do anything in __init__, so we just exit here
377 # don't need to do anything in __init__, so we just exit here
378 if isinstance(changeid, basectx):
378 if isinstance(changeid, basectx):
379 return
379 return
380
380
381 if changeid == '':
381 if changeid == '':
382 changeid = '.'
382 changeid = '.'
383 self._repo = repo
383 self._repo = repo
384
384
385 try:
385 try:
386 if isinstance(changeid, int):
386 if isinstance(changeid, int):
387 self._node = repo.changelog.node(changeid)
387 self._node = repo.changelog.node(changeid)
388 self._rev = changeid
388 self._rev = changeid
389 return
389 return
390 if isinstance(changeid, long):
390 if isinstance(changeid, long):
391 changeid = str(changeid)
391 changeid = str(changeid)
392 if changeid == 'null':
392 if changeid == 'null':
393 self._node = nullid
393 self._node = nullid
394 self._rev = nullrev
394 self._rev = nullrev
395 return
395 return
396 if changeid == 'tip':
396 if changeid == 'tip':
397 self._node = repo.changelog.tip()
397 self._node = repo.changelog.tip()
398 self._rev = repo.changelog.rev(self._node)
398 self._rev = repo.changelog.rev(self._node)
399 return
399 return
400 if changeid == '.' or changeid == repo.dirstate.p1():
400 if changeid == '.' or changeid == repo.dirstate.p1():
401 # this is a hack to delay/avoid loading obsmarkers
401 # this is a hack to delay/avoid loading obsmarkers
402 # when we know that '.' won't be hidden
402 # when we know that '.' won't be hidden
403 self._node = repo.dirstate.p1()
403 self._node = repo.dirstate.p1()
404 self._rev = repo.unfiltered().changelog.rev(self._node)
404 self._rev = repo.unfiltered().changelog.rev(self._node)
405 return
405 return
406 if len(changeid) == 20:
406 if len(changeid) == 20:
407 try:
407 try:
408 self._node = changeid
408 self._node = changeid
409 self._rev = repo.changelog.rev(changeid)
409 self._rev = repo.changelog.rev(changeid)
410 return
410 return
411 except error.FilteredRepoLookupError:
411 except error.FilteredRepoLookupError:
412 raise
412 raise
413 except LookupError:
413 except LookupError:
414 pass
414 pass
415
415
416 try:
416 try:
417 r = int(changeid)
417 r = int(changeid)
418 if str(r) != changeid:
418 if str(r) != changeid:
419 raise ValueError
419 raise ValueError
420 l = len(repo.changelog)
420 l = len(repo.changelog)
421 if r < 0:
421 if r < 0:
422 r += l
422 r += l
423 if r < 0 or r >= l:
423 if r < 0 or r >= l:
424 raise ValueError
424 raise ValueError
425 self._rev = r
425 self._rev = r
426 self._node = repo.changelog.node(r)
426 self._node = repo.changelog.node(r)
427 return
427 return
428 except error.FilteredIndexError:
428 except error.FilteredIndexError:
429 raise
429 raise
430 except (ValueError, OverflowError, IndexError):
430 except (ValueError, OverflowError, IndexError):
431 pass
431 pass
432
432
433 if len(changeid) == 40:
433 if len(changeid) == 40:
434 try:
434 try:
435 self._node = bin(changeid)
435 self._node = bin(changeid)
436 self._rev = repo.changelog.rev(self._node)
436 self._rev = repo.changelog.rev(self._node)
437 return
437 return
438 except error.FilteredLookupError:
438 except error.FilteredLookupError:
439 raise
439 raise
440 except (TypeError, LookupError):
440 except (TypeError, LookupError):
441 pass
441 pass
442
442
443 # lookup bookmarks through the name interface
443 # lookup bookmarks through the name interface
444 try:
444 try:
445 self._node = repo.names.singlenode(repo, changeid)
445 self._node = repo.names.singlenode(repo, changeid)
446 self._rev = repo.changelog.rev(self._node)
446 self._rev = repo.changelog.rev(self._node)
447 return
447 return
448 except KeyError:
448 except KeyError:
449 pass
449 pass
450 except error.FilteredRepoLookupError:
450 except error.FilteredRepoLookupError:
451 raise
451 raise
452 except error.RepoLookupError:
452 except error.RepoLookupError:
453 pass
453 pass
454
454
455 self._node = repo.unfiltered().changelog._partialmatch(changeid)
455 self._node = repo.unfiltered().changelog._partialmatch(changeid)
456 if self._node is not None:
456 if self._node is not None:
457 self._rev = repo.changelog.rev(self._node)
457 self._rev = repo.changelog.rev(self._node)
458 return
458 return
459
459
460 # lookup failed
460 # lookup failed
461 # check if it might have come from damaged dirstate
461 # check if it might have come from damaged dirstate
462 #
462 #
463 # XXX we could avoid the unfiltered if we had a recognizable
463 # XXX we could avoid the unfiltered if we had a recognizable
464 # exception for filtered changeset access
464 # exception for filtered changeset access
465 if changeid in repo.unfiltered().dirstate.parents():
465 if changeid in repo.unfiltered().dirstate.parents():
466 msg = _("working directory has unknown parent '%s'!")
466 msg = _("working directory has unknown parent '%s'!")
467 raise error.Abort(msg % short(changeid))
467 raise error.Abort(msg % short(changeid))
468 try:
468 try:
469 if len(changeid) == 20:
469 if len(changeid) == 20:
470 changeid = hex(changeid)
470 changeid = hex(changeid)
471 except TypeError:
471 except TypeError:
472 pass
472 pass
473 except (error.FilteredIndexError, error.FilteredLookupError,
473 except (error.FilteredIndexError, error.FilteredLookupError,
474 error.FilteredRepoLookupError):
474 error.FilteredRepoLookupError):
475 if repo.filtername.startswith('visible'):
475 if repo.filtername.startswith('visible'):
476 msg = _("hidden revision '%s'") % changeid
476 msg = _("hidden revision '%s'") % changeid
477 hint = _('use --hidden to access hidden revisions')
477 hint = _('use --hidden to access hidden revisions')
478 raise error.FilteredRepoLookupError(msg, hint=hint)
478 raise error.FilteredRepoLookupError(msg, hint=hint)
479 msg = _("filtered revision '%s' (not in '%s' subset)")
479 msg = _("filtered revision '%s' (not in '%s' subset)")
480 msg %= (changeid, repo.filtername)
480 msg %= (changeid, repo.filtername)
481 raise error.FilteredRepoLookupError(msg)
481 raise error.FilteredRepoLookupError(msg)
482 except IndexError:
482 except IndexError:
483 pass
483 pass
484 raise error.RepoLookupError(
484 raise error.RepoLookupError(
485 _("unknown revision '%s'") % changeid)
485 _("unknown revision '%s'") % changeid)
486
486
487 def __hash__(self):
487 def __hash__(self):
488 try:
488 try:
489 return hash(self._rev)
489 return hash(self._rev)
490 except AttributeError:
490 except AttributeError:
491 return id(self)
491 return id(self)
492
492
493 def __nonzero__(self):
493 def __nonzero__(self):
494 return self._rev != nullrev
494 return self._rev != nullrev
495
495
496 @propertycache
496 @propertycache
497 def _changeset(self):
497 def _changeset(self):
498 return self._repo.changelog.read(self.rev())
498 return self._repo.changelog.read(self.rev())
499
499
500 @propertycache
500 @propertycache
501 def _manifest(self):
501 def _manifest(self):
502 return self._repo.manifest.read(self._changeset[0])
502 return self._repo.manifest.read(self._changeset[0])
503
503
504 @propertycache
504 @propertycache
505 def _manifestdelta(self):
505 def _manifestdelta(self):
506 return self._repo.manifest.readdelta(self._changeset[0])
506 return self._repo.manifest.readdelta(self._changeset[0])
507
507
508 @propertycache
508 @propertycache
509 def _parents(self):
509 def _parents(self):
510 p = self._repo.changelog.parentrevs(self._rev)
510 p = self._repo.changelog.parentrevs(self._rev)
511 if p[1] == nullrev:
511 if p[1] == nullrev:
512 p = p[:-1]
512 p = p[:-1]
513 return [changectx(self._repo, x) for x in p]
513 return [changectx(self._repo, x) for x in p]
514
514
515 def changeset(self):
515 def changeset(self):
516 return self._changeset
516 return self._changeset
517 def manifestnode(self):
517 def manifestnode(self):
518 return self._changeset[0]
518 return self._changeset[0]
519
519
520 def user(self):
520 def user(self):
521 return self._changeset[1]
521 return self._changeset[1]
522 def date(self):
522 def date(self):
523 return self._changeset[2]
523 return self._changeset[2]
524 def files(self):
524 def files(self):
525 return self._changeset[3]
525 return self._changeset[3]
526 def description(self):
526 def description(self):
527 return self._changeset[4]
527 return self._changeset[4]
528 def branch(self):
528 def branch(self):
529 return encoding.tolocal(self._changeset[5].get("branch"))
529 return encoding.tolocal(self._changeset[5].get("branch"))
530 def closesbranch(self):
530 def closesbranch(self):
531 return 'close' in self._changeset[5]
531 return 'close' in self._changeset[5]
532 def extra(self):
532 def extra(self):
533 return self._changeset[5]
533 return self._changeset[5]
534 def tags(self):
534 def tags(self):
535 return self._repo.nodetags(self._node)
535 return self._repo.nodetags(self._node)
536 def bookmarks(self):
536 def bookmarks(self):
537 return self._repo.nodebookmarks(self._node)
537 return self._repo.nodebookmarks(self._node)
538 def phase(self):
538 def phase(self):
539 return self._repo._phasecache.phase(self._repo, self._rev)
539 return self._repo._phasecache.phase(self._repo, self._rev)
540 def hidden(self):
540 def hidden(self):
541 return self._rev in repoview.filterrevs(self._repo, 'visible')
541 return self._rev in repoview.filterrevs(self._repo, 'visible')
542
542
543 def children(self):
543 def children(self):
544 """return contexts for each child changeset"""
544 """return contexts for each child changeset"""
545 c = self._repo.changelog.children(self._node)
545 c = self._repo.changelog.children(self._node)
546 return [changectx(self._repo, x) for x in c]
546 return [changectx(self._repo, x) for x in c]
547
547
548 def ancestors(self):
548 def ancestors(self):
549 for a in self._repo.changelog.ancestors([self._rev]):
549 for a in self._repo.changelog.ancestors([self._rev]):
550 yield changectx(self._repo, a)
550 yield changectx(self._repo, a)
551
551
552 def descendants(self):
552 def descendants(self):
553 for d in self._repo.changelog.descendants([self._rev]):
553 for d in self._repo.changelog.descendants([self._rev]):
554 yield changectx(self._repo, d)
554 yield changectx(self._repo, d)
555
555
556 def filectx(self, path, fileid=None, filelog=None):
556 def filectx(self, path, fileid=None, filelog=None):
557 """get a file context from this changeset"""
557 """get a file context from this changeset"""
558 if fileid is None:
558 if fileid is None:
559 fileid = self.filenode(path)
559 fileid = self.filenode(path)
560 return filectx(self._repo, path, fileid=fileid,
560 return filectx(self._repo, path, fileid=fileid,
561 changectx=self, filelog=filelog)
561 changectx=self, filelog=filelog)
562
562
563 def ancestor(self, c2, warn=False):
563 def ancestor(self, c2, warn=False):
564 """return the "best" ancestor context of self and c2
564 """return the "best" ancestor context of self and c2
565
565
566 If there are multiple candidates, it will show a message and check
566 If there are multiple candidates, it will show a message and check
567 merge.preferancestor configuration before falling back to the
567 merge.preferancestor configuration before falling back to the
568 revlog ancestor."""
568 revlog ancestor."""
569 # deal with workingctxs
569 # deal with workingctxs
570 n2 = c2._node
570 n2 = c2._node
571 if n2 is None:
571 if n2 is None:
572 n2 = c2._parents[0]._node
572 n2 = c2._parents[0]._node
573 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
573 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
574 if not cahs:
574 if not cahs:
575 anc = nullid
575 anc = nullid
576 elif len(cahs) == 1:
576 elif len(cahs) == 1:
577 anc = cahs[0]
577 anc = cahs[0]
578 else:
578 else:
579 # experimental config: merge.preferancestor
579 # experimental config: merge.preferancestor
580 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
580 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
581 try:
581 try:
582 ctx = changectx(self._repo, r)
582 ctx = changectx(self._repo, r)
583 except error.RepoLookupError:
583 except error.RepoLookupError:
584 continue
584 continue
585 anc = ctx.node()
585 anc = ctx.node()
586 if anc in cahs:
586 if anc in cahs:
587 break
587 break
588 else:
588 else:
589 anc = self._repo.changelog.ancestor(self._node, n2)
589 anc = self._repo.changelog.ancestor(self._node, n2)
590 if warn:
590 if warn:
591 self._repo.ui.status(
591 self._repo.ui.status(
592 (_("note: using %s as ancestor of %s and %s\n") %
592 (_("note: using %s as ancestor of %s and %s\n") %
593 (short(anc), short(self._node), short(n2))) +
593 (short(anc), short(self._node), short(n2))) +
594 ''.join(_(" alternatively, use --config "
594 ''.join(_(" alternatively, use --config "
595 "merge.preferancestor=%s\n") %
595 "merge.preferancestor=%s\n") %
596 short(n) for n in sorted(cahs) if n != anc))
596 short(n) for n in sorted(cahs) if n != anc))
597 return changectx(self._repo, anc)
597 return changectx(self._repo, anc)
598
598
599 def descendant(self, other):
599 def descendant(self, other):
600 """True if other is descendant of this changeset"""
600 """True if other is descendant of this changeset"""
601 return self._repo.changelog.descendant(self._rev, other._rev)
601 return self._repo.changelog.descendant(self._rev, other._rev)
602
602
603 def walk(self, match):
603 def walk(self, match):
604 '''Generates matching file names.'''
604 '''Generates matching file names.'''
605
605
606 # Wrap match.bad method to have message with nodeid
606 # Wrap match.bad method to have message with nodeid
607 def bad(fn, msg):
607 def bad(fn, msg):
608 # The manifest doesn't know about subrepos, so don't complain about
608 # The manifest doesn't know about subrepos, so don't complain about
609 # paths into valid subrepos.
609 # paths into valid subrepos.
610 if any(fn == s or fn.startswith(s + '/')
610 if any(fn == s or fn.startswith(s + '/')
611 for s in self.substate):
611 for s in self.substate):
612 return
612 return
613 match.bad(fn, _('no such file in rev %s') % self)
613 match.bad(fn, _('no such file in rev %s') % self)
614
614
615 m = matchmod.badmatch(match, bad)
615 m = matchmod.badmatch(match, bad)
616 return self._manifest.walk(m)
616 return self._manifest.walk(m)
617
617
618 def matches(self, match):
618 def matches(self, match):
619 return self.walk(match)
619 return self.walk(match)
620
620
621 class basefilectx(object):
621 class basefilectx(object):
622 """A filecontext object represents the common logic for its children:
622 """A filecontext object represents the common logic for its children:
623 filectx: read-only access to a filerevision that is already present
623 filectx: read-only access to a filerevision that is already present
624 in the repo,
624 in the repo,
625 workingfilectx: a filecontext that represents files from the working
625 workingfilectx: a filecontext that represents files from the working
626 directory,
626 directory,
627 memfilectx: a filecontext that represents files in-memory."""
627 memfilectx: a filecontext that represents files in-memory."""
628 def __new__(cls, repo, path, *args, **kwargs):
628 def __new__(cls, repo, path, *args, **kwargs):
629 return super(basefilectx, cls).__new__(cls)
629 return super(basefilectx, cls).__new__(cls)
630
630
631 @propertycache
631 @propertycache
632 def _filelog(self):
632 def _filelog(self):
633 return self._repo.file(self._path)
633 return self._repo.file(self._path)
634
634
635 @propertycache
635 @propertycache
636 def _changeid(self):
636 def _changeid(self):
637 if '_changeid' in self.__dict__:
637 if '_changeid' in self.__dict__:
638 return self._changeid
638 return self._changeid
639 elif '_changectx' in self.__dict__:
639 elif '_changectx' in self.__dict__:
640 return self._changectx.rev()
640 return self._changectx.rev()
641 elif '_descendantrev' in self.__dict__:
641 elif '_descendantrev' in self.__dict__:
642 # this file context was created from a revision with a known
642 # this file context was created from a revision with a known
643 # descendant, we can (lazily) correct for linkrev aliases
643 # descendant, we can (lazily) correct for linkrev aliases
644 return self._adjustlinkrev(self._path, self._filelog,
644 return self._adjustlinkrev(self._path, self._filelog,
645 self._filenode, self._descendantrev)
645 self._filenode, self._descendantrev)
646 else:
646 else:
647 return self._filelog.linkrev(self._filerev)
647 return self._filelog.linkrev(self._filerev)
648
648
649 @propertycache
649 @propertycache
650 def _filenode(self):
650 def _filenode(self):
651 if '_fileid' in self.__dict__:
651 if '_fileid' in self.__dict__:
652 return self._filelog.lookup(self._fileid)
652 return self._filelog.lookup(self._fileid)
653 else:
653 else:
654 return self._changectx.filenode(self._path)
654 return self._changectx.filenode(self._path)
655
655
656 @propertycache
656 @propertycache
657 def _filerev(self):
657 def _filerev(self):
658 return self._filelog.rev(self._filenode)
658 return self._filelog.rev(self._filenode)
659
659
660 @propertycache
660 @propertycache
661 def _repopath(self):
661 def _repopath(self):
662 return self._path
662 return self._path
663
663
664 def __nonzero__(self):
664 def __nonzero__(self):
665 try:
665 try:
666 self._filenode
666 self._filenode
667 return True
667 return True
668 except error.LookupError:
668 except error.LookupError:
669 # file is missing
669 # file is missing
670 return False
670 return False
671
671
672 def __str__(self):
672 def __str__(self):
673 return "%s@%s" % (self.path(), self._changectx)
673 return "%s@%s" % (self.path(), self._changectx)
674
674
675 def __repr__(self):
675 def __repr__(self):
676 return "<%s %s>" % (type(self).__name__, str(self))
676 return "<%s %s>" % (type(self).__name__, str(self))
677
677
678 def __hash__(self):
678 def __hash__(self):
679 try:
679 try:
680 return hash((self._path, self._filenode))
680 return hash((self._path, self._filenode))
681 except AttributeError:
681 except AttributeError:
682 return id(self)
682 return id(self)
683
683
684 def __eq__(self, other):
684 def __eq__(self, other):
685 try:
685 try:
686 return (type(self) == type(other) and self._path == other._path
686 return (type(self) == type(other) and self._path == other._path
687 and self._filenode == other._filenode)
687 and self._filenode == other._filenode)
688 except AttributeError:
688 except AttributeError:
689 return False
689 return False
690
690
691 def __ne__(self, other):
691 def __ne__(self, other):
692 return not (self == other)
692 return not (self == other)
693
693
694 def filerev(self):
694 def filerev(self):
695 return self._filerev
695 return self._filerev
696 def filenode(self):
696 def filenode(self):
697 return self._filenode
697 return self._filenode
698 def flags(self):
698 def flags(self):
699 return self._changectx.flags(self._path)
699 return self._changectx.flags(self._path)
700 def filelog(self):
700 def filelog(self):
701 return self._filelog
701 return self._filelog
702 def rev(self):
702 def rev(self):
703 return self._changeid
703 return self._changeid
704 def linkrev(self):
704 def linkrev(self):
705 return self._filelog.linkrev(self._filerev)
705 return self._filelog.linkrev(self._filerev)
706 def node(self):
706 def node(self):
707 return self._changectx.node()
707 return self._changectx.node()
708 def hex(self):
708 def hex(self):
709 return self._changectx.hex()
709 return self._changectx.hex()
710 def user(self):
710 def user(self):
711 return self._changectx.user()
711 return self._changectx.user()
712 def date(self):
712 def date(self):
713 return self._changectx.date()
713 return self._changectx.date()
714 def files(self):
714 def files(self):
715 return self._changectx.files()
715 return self._changectx.files()
716 def description(self):
716 def description(self):
717 return self._changectx.description()
717 return self._changectx.description()
718 def branch(self):
718 def branch(self):
719 return self._changectx.branch()
719 return self._changectx.branch()
720 def extra(self):
720 def extra(self):
721 return self._changectx.extra()
721 return self._changectx.extra()
722 def phase(self):
722 def phase(self):
723 return self._changectx.phase()
723 return self._changectx.phase()
724 def phasestr(self):
724 def phasestr(self):
725 return self._changectx.phasestr()
725 return self._changectx.phasestr()
726 def manifest(self):
726 def manifest(self):
727 return self._changectx.manifest()
727 return self._changectx.manifest()
728 def changectx(self):
728 def changectx(self):
729 return self._changectx
729 return self._changectx
730 def repo(self):
730 def repo(self):
731 return self._repo
731 return self._repo
732
732
733 def path(self):
733 def path(self):
734 return self._path
734 return self._path
735
735
736 def isbinary(self):
736 def isbinary(self):
737 try:
737 try:
738 return util.binary(self.data())
738 return util.binary(self.data())
739 except IOError:
739 except IOError:
740 return False
740 return False
741 def isexec(self):
741 def isexec(self):
742 return 'x' in self.flags()
742 return 'x' in self.flags()
743 def islink(self):
743 def islink(self):
744 return 'l' in self.flags()
744 return 'l' in self.flags()
745
745
746 def cmp(self, fctx):
746 def cmp(self, fctx):
747 """compare with other file context
747 """compare with other file context
748
748
749 returns True if different than fctx.
749 returns True if different than fctx.
750 """
750 """
751 if (fctx._filerev is None
751 if (fctx._filerev is None
752 and (self._repo._encodefilterpats
752 and (self._repo._encodefilterpats
753 # if file data starts with '\1\n', empty metadata block is
753 # if file data starts with '\1\n', empty metadata block is
754 # prepended, which adds 4 bytes to filelog.size().
754 # prepended, which adds 4 bytes to filelog.size().
755 or self.size() - 4 == fctx.size())
755 or self.size() - 4 == fctx.size())
756 or self.size() == fctx.size()):
756 or self.size() == fctx.size()):
757 return self._filelog.cmp(self._filenode, fctx.data())
757 return self._filelog.cmp(self._filenode, fctx.data())
758
758
759 return True
759 return True
760
760
761 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
761 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
762 """return the first ancestor of <srcrev> introducing <fnode>
762 """return the first ancestor of <srcrev> introducing <fnode>
763
763
764 If the linkrev of the file revision does not point to an ancestor of
764 If the linkrev of the file revision does not point to an ancestor of
765 srcrev, we'll walk down the ancestors until we find one introducing
765 srcrev, we'll walk down the ancestors until we find one introducing
766 this file revision.
766 this file revision.
767
767
768 :repo: a localrepository object (used to access changelog and manifest)
768 :repo: a localrepository object (used to access changelog and manifest)
769 :path: the file path
769 :path: the file path
770 :fnode: the nodeid of the file revision
770 :fnode: the nodeid of the file revision
771 :filelog: the filelog of this path
771 :filelog: the filelog of this path
772 :srcrev: the changeset revision we search ancestors from
772 :srcrev: the changeset revision we search ancestors from
773 :inclusive: if true, the src revision will also be checked
773 :inclusive: if true, the src revision will also be checked
774 """
774 """
775 repo = self._repo
775 repo = self._repo
776 cl = repo.unfiltered().changelog
776 cl = repo.unfiltered().changelog
777 ma = repo.manifest
777 ma = repo.manifest
778 # fetch the linkrev
778 # fetch the linkrev
779 fr = filelog.rev(fnode)
779 fr = filelog.rev(fnode)
780 lkr = filelog.linkrev(fr)
780 lkr = filelog.linkrev(fr)
781 # hack to reuse ancestor computation when searching for renames
781 # hack to reuse ancestor computation when searching for renames
782 memberanc = getattr(self, '_ancestrycontext', None)
782 memberanc = getattr(self, '_ancestrycontext', None)
783 iteranc = None
783 iteranc = None
784 if srcrev is None:
784 if srcrev is None:
785 # wctx case, used by workingfilectx during mergecopy
785 # wctx case, used by workingfilectx during mergecopy
786 revs = [p.rev() for p in self._repo[None].parents()]
786 revs = [p.rev() for p in self._repo[None].parents()]
787 inclusive = True # we skipped the real (revless) source
787 inclusive = True # we skipped the real (revless) source
788 else:
788 else:
789 revs = [srcrev]
789 revs = [srcrev]
790 if memberanc is None:
790 if memberanc is None:
791 memberanc = iteranc = cl.ancestors(revs, lkr,
791 memberanc = iteranc = cl.ancestors(revs, lkr,
792 inclusive=inclusive)
792 inclusive=inclusive)
793 # check if this linkrev is an ancestor of srcrev
793 # check if this linkrev is an ancestor of srcrev
794 if lkr not in memberanc:
794 if lkr not in memberanc:
795 if iteranc is None:
795 if iteranc is None:
796 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
796 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
797 for a in iteranc:
797 for a in iteranc:
798 ac = cl.read(a) # get changeset data (we avoid object creation)
798 ac = cl.read(a) # get changeset data (we avoid object creation)
799 if path in ac[3]: # checking the 'files' field.
799 if path in ac[3]: # checking the 'files' field.
800 # The file has been touched, check if the content is
800 # The file has been touched, check if the content is
801 # similar to the one we search for.
801 # similar to the one we search for.
802 if fnode == ma.readfast(ac[0]).get(path):
802 if fnode == ma.readfast(ac[0]).get(path):
803 return a
803 return a
804 # In theory, we should never get out of that loop without a result.
804 # In theory, we should never get out of that loop without a result.
805 # But if manifest uses a buggy file revision (not children of the
805 # But if manifest uses a buggy file revision (not children of the
806 # one it replaces) we could. Such a buggy situation will likely
806 # one it replaces) we could. Such a buggy situation will likely
807 # result is crash somewhere else at to some point.
807 # result is crash somewhere else at to some point.
808 return lkr
808 return lkr
809
809
810 def introrev(self):
810 def introrev(self):
811 """return the rev of the changeset which introduced this file revision
811 """return the rev of the changeset which introduced this file revision
812
812
813 This method is different from linkrev because it take into account the
813 This method is different from linkrev because it take into account the
814 changeset the filectx was created from. It ensures the returned
814 changeset the filectx was created from. It ensures the returned
815 revision is one of its ancestors. This prevents bugs from
815 revision is one of its ancestors. This prevents bugs from
816 'linkrev-shadowing' when a file revision is used by multiple
816 'linkrev-shadowing' when a file revision is used by multiple
817 changesets.
817 changesets.
818 """
818 """
819 lkr = self.linkrev()
819 lkr = self.linkrev()
820 attrs = vars(self)
820 attrs = vars(self)
821 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
821 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
822 if noctx or self.rev() == lkr:
822 if noctx or self.rev() == lkr:
823 return self.linkrev()
823 return self.linkrev()
824 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
824 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
825 self.rev(), inclusive=True)
825 self.rev(), inclusive=True)
826
826
827 def _parentfilectx(self, path, fileid, filelog):
827 def _parentfilectx(self, path, fileid, filelog):
828 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
828 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
829 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
829 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
830 if '_changeid' in vars(self) or '_changectx' in vars(self):
830 if '_changeid' in vars(self) or '_changectx' in vars(self):
831 # If self is associated with a changeset (probably explicitly
831 # If self is associated with a changeset (probably explicitly
832 # fed), ensure the created filectx is associated with a
832 # fed), ensure the created filectx is associated with a
833 # changeset that is an ancestor of self.changectx.
833 # changeset that is an ancestor of self.changectx.
834 # This lets us later use _adjustlinkrev to get a correct link.
834 # This lets us later use _adjustlinkrev to get a correct link.
835 fctx._descendantrev = self.rev()
835 fctx._descendantrev = self.rev()
836 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
836 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
837 elif '_descendantrev' in vars(self):
837 elif '_descendantrev' in vars(self):
838 # Otherwise propagate _descendantrev if we have one associated.
838 # Otherwise propagate _descendantrev if we have one associated.
839 fctx._descendantrev = self._descendantrev
839 fctx._descendantrev = self._descendantrev
840 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
840 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
841 return fctx
841 return fctx
842
842
843 def parents(self):
843 def parents(self):
844 _path = self._path
844 _path = self._path
845 fl = self._filelog
845 fl = self._filelog
846 parents = self._filelog.parents(self._filenode)
846 parents = self._filelog.parents(self._filenode)
847 pl = [(_path, node, fl) for node in parents if node != nullid]
847 pl = [(_path, node, fl) for node in parents if node != nullid]
848
848
849 r = fl.renamed(self._filenode)
849 r = fl.renamed(self._filenode)
850 if r:
850 if r:
851 # - In the simple rename case, both parent are nullid, pl is empty.
851 # - In the simple rename case, both parent are nullid, pl is empty.
852 # - In case of merge, only one of the parent is null id and should
852 # - In case of merge, only one of the parent is null id and should
853 # be replaced with the rename information. This parent is -always-
853 # be replaced with the rename information. This parent is -always-
854 # the first one.
854 # the first one.
855 #
855 #
856 # As null id have always been filtered out in the previous list
856 # As null id have always been filtered out in the previous list
857 # comprehension, inserting to 0 will always result in "replacing
857 # comprehension, inserting to 0 will always result in "replacing
858 # first nullid parent with rename information.
858 # first nullid parent with rename information.
859 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
859 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
860
860
861 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
861 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
862
862
863 def p1(self):
863 def p1(self):
864 return self.parents()[0]
864 return self.parents()[0]
865
865
866 def p2(self):
866 def p2(self):
867 p = self.parents()
867 p = self.parents()
868 if len(p) == 2:
868 if len(p) == 2:
869 return p[1]
869 return p[1]
870 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
870 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
871
871
872 def annotate(self, follow=False, linenumber=None, diffopts=None):
872 def annotate(self, follow=False, linenumber=None, diffopts=None):
873 '''returns a list of tuples of (ctx, line) for each line
873 '''returns a list of tuples of (ctx, line) for each line
874 in the file, where ctx is the filectx of the node where
874 in the file, where ctx is the filectx of the node where
875 that line was last changed.
875 that line was last changed.
876 This returns tuples of ((ctx, linenumber), line) for each line,
876 This returns tuples of ((ctx, linenumber), line) for each line,
877 if "linenumber" parameter is NOT "None".
877 if "linenumber" parameter is NOT "None".
878 In such tuples, linenumber means one at the first appearance
878 In such tuples, linenumber means one at the first appearance
879 in the managed file.
879 in the managed file.
880 To reduce annotation cost,
880 To reduce annotation cost,
881 this returns fixed value(False is used) as linenumber,
881 this returns fixed value(False is used) as linenumber,
882 if "linenumber" parameter is "False".'''
882 if "linenumber" parameter is "False".'''
883
883
884 if linenumber is None:
884 if linenumber is None:
885 def decorate(text, rev):
885 def decorate(text, rev):
886 return ([rev] * len(text.splitlines()), text)
886 return ([rev] * len(text.splitlines()), text)
887 elif linenumber:
887 elif linenumber:
888 def decorate(text, rev):
888 def decorate(text, rev):
889 size = len(text.splitlines())
889 size = len(text.splitlines())
890 return ([(rev, i) for i in xrange(1, size + 1)], text)
890 return ([(rev, i) for i in xrange(1, size + 1)], text)
891 else:
891 else:
892 def decorate(text, rev):
892 def decorate(text, rev):
893 return ([(rev, False)] * len(text.splitlines()), text)
893 return ([(rev, False)] * len(text.splitlines()), text)
894
894
895 def pair(parent, child):
895 def pair(parent, child):
896 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
896 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
897 refine=True)
897 refine=True)
898 for (a1, a2, b1, b2), t in blocks:
898 for (a1, a2, b1, b2), t in blocks:
899 # Changed blocks ('!') or blocks made only of blank lines ('~')
899 # Changed blocks ('!') or blocks made only of blank lines ('~')
900 # belong to the child.
900 # belong to the child.
901 if t == '=':
901 if t == '=':
902 child[0][b1:b2] = parent[0][a1:a2]
902 child[0][b1:b2] = parent[0][a1:a2]
903 return child
903 return child
904
904
905 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
905 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
906
906
907 def parents(f):
907 def parents(f):
908 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
908 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
909 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
909 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
910 # from the topmost introrev (= srcrev) down to p.linkrev() if it
910 # from the topmost introrev (= srcrev) down to p.linkrev() if it
911 # isn't an ancestor of the srcrev.
911 # isn't an ancestor of the srcrev.
912 f._changeid
912 f._changeid
913 pl = f.parents()
913 pl = f.parents()
914
914
915 # Don't return renamed parents if we aren't following.
915 # Don't return renamed parents if we aren't following.
916 if not follow:
916 if not follow:
917 pl = [p for p in pl if p.path() == f.path()]
917 pl = [p for p in pl if p.path() == f.path()]
918
918
919 # renamed filectx won't have a filelog yet, so set it
919 # renamed filectx won't have a filelog yet, so set it
920 # from the cache to save time
920 # from the cache to save time
921 for p in pl:
921 for p in pl:
922 if not '_filelog' in p.__dict__:
922 if not '_filelog' in p.__dict__:
923 p._filelog = getlog(p.path())
923 p._filelog = getlog(p.path())
924
924
925 return pl
925 return pl
926
926
927 # use linkrev to find the first changeset where self appeared
927 # use linkrev to find the first changeset where self appeared
928 base = self
928 base = self
929 introrev = self.introrev()
929 introrev = self.introrev()
930 if self.rev() != introrev:
930 if self.rev() != introrev:
931 base = self.filectx(self.filenode(), changeid=introrev)
931 base = self.filectx(self.filenode(), changeid=introrev)
932 if getattr(base, '_ancestrycontext', None) is None:
932 if getattr(base, '_ancestrycontext', None) is None:
933 cl = self._repo.changelog
933 cl = self._repo.changelog
934 if introrev is None:
934 if introrev is None:
935 # wctx is not inclusive, but works because _ancestrycontext
935 # wctx is not inclusive, but works because _ancestrycontext
936 # is used to test filelog revisions
936 # is used to test filelog revisions
937 ac = cl.ancestors([p.rev() for p in base.parents()],
937 ac = cl.ancestors([p.rev() for p in base.parents()],
938 inclusive=True)
938 inclusive=True)
939 else:
939 else:
940 ac = cl.ancestors([introrev], inclusive=True)
940 ac = cl.ancestors([introrev], inclusive=True)
941 base._ancestrycontext = ac
941 base._ancestrycontext = ac
942
942
943 # This algorithm would prefer to be recursive, but Python is a
943 # This algorithm would prefer to be recursive, but Python is a
944 # bit recursion-hostile. Instead we do an iterative
944 # bit recursion-hostile. Instead we do an iterative
945 # depth-first search.
945 # depth-first search.
946
946
947 visit = [base]
947 visit = [base]
948 hist = {}
948 hist = {}
949 pcache = {}
949 pcache = {}
950 needed = {base: 1}
950 needed = {base: 1}
951 while visit:
951 while visit:
952 f = visit[-1]
952 f = visit[-1]
953 pcached = f in pcache
953 pcached = f in pcache
954 if not pcached:
954 if not pcached:
955 pcache[f] = parents(f)
955 pcache[f] = parents(f)
956
956
957 ready = True
957 ready = True
958 pl = pcache[f]
958 pl = pcache[f]
959 for p in pl:
959 for p in pl:
960 if p not in hist:
960 if p not in hist:
961 ready = False
961 ready = False
962 visit.append(p)
962 visit.append(p)
963 if not pcached:
963 if not pcached:
964 needed[p] = needed.get(p, 0) + 1
964 needed[p] = needed.get(p, 0) + 1
965 if ready:
965 if ready:
966 visit.pop()
966 visit.pop()
967 reusable = f in hist
967 reusable = f in hist
968 if reusable:
968 if reusable:
969 curr = hist[f]
969 curr = hist[f]
970 else:
970 else:
971 curr = decorate(f.data(), f)
971 curr = decorate(f.data(), f)
972 for p in pl:
972 for p in pl:
973 if not reusable:
973 if not reusable:
974 curr = pair(hist[p], curr)
974 curr = pair(hist[p], curr)
975 if needed[p] == 1:
975 if needed[p] == 1:
976 del hist[p]
976 del hist[p]
977 del needed[p]
977 del needed[p]
978 else:
978 else:
979 needed[p] -= 1
979 needed[p] -= 1
980
980
981 hist[f] = curr
981 hist[f] = curr
982 pcache[f] = []
982 pcache[f] = []
983
983
984 return zip(hist[base][0], hist[base][1].splitlines(True))
984 return zip(hist[base][0], hist[base][1].splitlines(True))
985
985
986 def ancestors(self, followfirst=False):
986 def ancestors(self, followfirst=False):
987 visit = {}
987 visit = {}
988 c = self
988 c = self
989 if followfirst:
989 if followfirst:
990 cut = 1
990 cut = 1
991 else:
991 else:
992 cut = None
992 cut = None
993
993
994 while True:
994 while True:
995 for parent in c.parents()[:cut]:
995 for parent in c.parents()[:cut]:
996 visit[(parent.linkrev(), parent.filenode())] = parent
996 visit[(parent.linkrev(), parent.filenode())] = parent
997 if not visit:
997 if not visit:
998 break
998 break
999 c = visit.pop(max(visit))
999 c = visit.pop(max(visit))
1000 yield c
1000 yield c
1001
1001
1002 class filectx(basefilectx):
1002 class filectx(basefilectx):
1003 """A filecontext object makes access to data related to a particular
1003 """A filecontext object makes access to data related to a particular
1004 filerevision convenient."""
1004 filerevision convenient."""
1005 def __init__(self, repo, path, changeid=None, fileid=None,
1005 def __init__(self, repo, path, changeid=None, fileid=None,
1006 filelog=None, changectx=None):
1006 filelog=None, changectx=None):
1007 """changeid can be a changeset revision, node, or tag.
1007 """changeid can be a changeset revision, node, or tag.
1008 fileid can be a file revision or node."""
1008 fileid can be a file revision or node."""
1009 self._repo = repo
1009 self._repo = repo
1010 self._path = path
1010 self._path = path
1011
1011
1012 assert (changeid is not None
1012 assert (changeid is not None
1013 or fileid is not None
1013 or fileid is not None
1014 or changectx is not None), \
1014 or changectx is not None), \
1015 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1015 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1016 % (changeid, fileid, changectx))
1016 % (changeid, fileid, changectx))
1017
1017
1018 if filelog is not None:
1018 if filelog is not None:
1019 self._filelog = filelog
1019 self._filelog = filelog
1020
1020
1021 if changeid is not None:
1021 if changeid is not None:
1022 self._changeid = changeid
1022 self._changeid = changeid
1023 if changectx is not None:
1023 if changectx is not None:
1024 self._changectx = changectx
1024 self._changectx = changectx
1025 if fileid is not None:
1025 if fileid is not None:
1026 self._fileid = fileid
1026 self._fileid = fileid
1027
1027
1028 @propertycache
1028 @propertycache
1029 def _changectx(self):
1029 def _changectx(self):
1030 try:
1030 try:
1031 return changectx(self._repo, self._changeid)
1031 return changectx(self._repo, self._changeid)
1032 except error.FilteredRepoLookupError:
1032 except error.FilteredRepoLookupError:
1033 # Linkrev may point to any revision in the repository. When the
1033 # Linkrev may point to any revision in the repository. When the
1034 # repository is filtered this may lead to `filectx` trying to build
1034 # repository is filtered this may lead to `filectx` trying to build
1035 # `changectx` for filtered revision. In such case we fallback to
1035 # `changectx` for filtered revision. In such case we fallback to
1036 # creating `changectx` on the unfiltered version of the reposition.
1036 # creating `changectx` on the unfiltered version of the reposition.
1037 # This fallback should not be an issue because `changectx` from
1037 # This fallback should not be an issue because `changectx` from
1038 # `filectx` are not used in complex operations that care about
1038 # `filectx` are not used in complex operations that care about
1039 # filtering.
1039 # filtering.
1040 #
1040 #
1041 # This fallback is a cheap and dirty fix that prevent several
1041 # This fallback is a cheap and dirty fix that prevent several
1042 # crashes. It does not ensure the behavior is correct. However the
1042 # crashes. It does not ensure the behavior is correct. However the
1043 # behavior was not correct before filtering either and "incorrect
1043 # behavior was not correct before filtering either and "incorrect
1044 # behavior" is seen as better as "crash"
1044 # behavior" is seen as better as "crash"
1045 #
1045 #
1046 # Linkrevs have several serious troubles with filtering that are
1046 # Linkrevs have several serious troubles with filtering that are
1047 # complicated to solve. Proper handling of the issue here should be
1047 # complicated to solve. Proper handling of the issue here should be
1048 # considered when solving linkrev issue are on the table.
1048 # considered when solving linkrev issue are on the table.
1049 return changectx(self._repo.unfiltered(), self._changeid)
1049 return changectx(self._repo.unfiltered(), self._changeid)
1050
1050
1051 def filectx(self, fileid, changeid=None):
1051 def filectx(self, fileid, changeid=None):
1052 '''opens an arbitrary revision of the file without
1052 '''opens an arbitrary revision of the file without
1053 opening a new filelog'''
1053 opening a new filelog'''
1054 return filectx(self._repo, self._path, fileid=fileid,
1054 return filectx(self._repo, self._path, fileid=fileid,
1055 filelog=self._filelog, changeid=changeid)
1055 filelog=self._filelog, changeid=changeid)
1056
1056
1057 def data(self):
1057 def data(self):
1058 try:
1058 try:
1059 return self._filelog.read(self._filenode)
1059 return self._filelog.read(self._filenode)
1060 except error.CensoredNodeError:
1060 except error.CensoredNodeError:
1061 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1061 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1062 return ""
1062 return ""
1063 raise util.Abort(_("censored node: %s") % short(self._filenode),
1063 raise util.Abort(_("censored node: %s") % short(self._filenode),
1064 hint=_("set censor.policy to ignore errors"))
1064 hint=_("set censor.policy to ignore errors"))
1065
1065
1066 def size(self):
1066 def size(self):
1067 return self._filelog.size(self._filerev)
1067 return self._filelog.size(self._filerev)
1068
1068
1069 def renamed(self):
1069 def renamed(self):
1070 """check if file was actually renamed in this changeset revision
1070 """check if file was actually renamed in this changeset revision
1071
1071
1072 If rename logged in file revision, we report copy for changeset only
1072 If rename logged in file revision, we report copy for changeset only
1073 if file revisions linkrev points back to the changeset in question
1073 if file revisions linkrev points back to the changeset in question
1074 or both changeset parents contain different file revisions.
1074 or both changeset parents contain different file revisions.
1075 """
1075 """
1076
1076
1077 renamed = self._filelog.renamed(self._filenode)
1077 renamed = self._filelog.renamed(self._filenode)
1078 if not renamed:
1078 if not renamed:
1079 return renamed
1079 return renamed
1080
1080
1081 if self.rev() == self.linkrev():
1081 if self.rev() == self.linkrev():
1082 return renamed
1082 return renamed
1083
1083
1084 name = self.path()
1084 name = self.path()
1085 fnode = self._filenode
1085 fnode = self._filenode
1086 for p in self._changectx.parents():
1086 for p in self._changectx.parents():
1087 try:
1087 try:
1088 if fnode == p.filenode(name):
1088 if fnode == p.filenode(name):
1089 return None
1089 return None
1090 except error.LookupError:
1090 except error.LookupError:
1091 pass
1091 pass
1092 return renamed
1092 return renamed
1093
1093
1094 def children(self):
1094 def children(self):
1095 # hard for renames
1095 # hard for renames
1096 c = self._filelog.children(self._filenode)
1096 c = self._filelog.children(self._filenode)
1097 return [filectx(self._repo, self._path, fileid=x,
1097 return [filectx(self._repo, self._path, fileid=x,
1098 filelog=self._filelog) for x in c]
1098 filelog=self._filelog) for x in c]
1099
1099
1100 class committablectx(basectx):
1100 class committablectx(basectx):
1101 """A committablectx object provides common functionality for a context that
1101 """A committablectx object provides common functionality for a context that
1102 wants the ability to commit, e.g. workingctx or memctx."""
1102 wants the ability to commit, e.g. workingctx or memctx."""
1103 def __init__(self, repo, text="", user=None, date=None, extra=None,
1103 def __init__(self, repo, text="", user=None, date=None, extra=None,
1104 changes=None):
1104 changes=None):
1105 self._repo = repo
1105 self._repo = repo
1106 self._rev = None
1106 self._rev = None
1107 self._node = None
1107 self._node = None
1108 self._text = text
1108 self._text = text
1109 if date:
1109 if date:
1110 self._date = util.parsedate(date)
1110 self._date = util.parsedate(date)
1111 if user:
1111 if user:
1112 self._user = user
1112 self._user = user
1113 if changes:
1113 if changes:
1114 self._status = changes
1114 self._status = changes
1115
1115
1116 self._extra = {}
1116 self._extra = {}
1117 if extra:
1117 if extra:
1118 self._extra = extra.copy()
1118 self._extra = extra.copy()
1119 if 'branch' not in self._extra:
1119 if 'branch' not in self._extra:
1120 try:
1120 try:
1121 branch = encoding.fromlocal(self._repo.dirstate.branch())
1121 branch = encoding.fromlocal(self._repo.dirstate.branch())
1122 except UnicodeDecodeError:
1122 except UnicodeDecodeError:
1123 raise util.Abort(_('branch name not in UTF-8!'))
1123 raise util.Abort(_('branch name not in UTF-8!'))
1124 self._extra['branch'] = branch
1124 self._extra['branch'] = branch
1125 if self._extra['branch'] == '':
1125 if self._extra['branch'] == '':
1126 self._extra['branch'] = 'default'
1126 self._extra['branch'] = 'default'
1127
1127
1128 def __str__(self):
1128 def __str__(self):
1129 return str(self._parents[0]) + "+"
1129 return str(self._parents[0]) + "+"
1130
1130
1131 def __nonzero__(self):
1131 def __nonzero__(self):
1132 return True
1132 return True
1133
1133
1134 def _buildflagfunc(self):
1134 def _buildflagfunc(self):
1135 # Create a fallback function for getting file flags when the
1135 # Create a fallback function for getting file flags when the
1136 # filesystem doesn't support them
1136 # filesystem doesn't support them
1137
1137
1138 copiesget = self._repo.dirstate.copies().get
1138 copiesget = self._repo.dirstate.copies().get
1139
1139
1140 if len(self._parents) < 2:
1140 if len(self._parents) < 2:
1141 # when we have one parent, it's easy: copy from parent
1141 # when we have one parent, it's easy: copy from parent
1142 man = self._parents[0].manifest()
1142 man = self._parents[0].manifest()
1143 def func(f):
1143 def func(f):
1144 f = copiesget(f, f)
1144 f = copiesget(f, f)
1145 return man.flags(f)
1145 return man.flags(f)
1146 else:
1146 else:
1147 # merges are tricky: we try to reconstruct the unstored
1147 # merges are tricky: we try to reconstruct the unstored
1148 # result from the merge (issue1802)
1148 # result from the merge (issue1802)
1149 p1, p2 = self._parents
1149 p1, p2 = self._parents
1150 pa = p1.ancestor(p2)
1150 pa = p1.ancestor(p2)
1151 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1151 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1152
1152
1153 def func(f):
1153 def func(f):
1154 f = copiesget(f, f) # may be wrong for merges with copies
1154 f = copiesget(f, f) # may be wrong for merges with copies
1155 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1155 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1156 if fl1 == fl2:
1156 if fl1 == fl2:
1157 return fl1
1157 return fl1
1158 if fl1 == fla:
1158 if fl1 == fla:
1159 return fl2
1159 return fl2
1160 if fl2 == fla:
1160 if fl2 == fla:
1161 return fl1
1161 return fl1
1162 return '' # punt for conflicts
1162 return '' # punt for conflicts
1163
1163
1164 return func
1164 return func
1165
1165
1166 @propertycache
1166 @propertycache
1167 def _flagfunc(self):
1167 def _flagfunc(self):
1168 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1168 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1169
1169
1170 @propertycache
1170 @propertycache
1171 def _manifest(self):
1171 def _manifest(self):
1172 """generate a manifest corresponding to the values in self._status
1172 """generate a manifest corresponding to the values in self._status
1173
1173
1174 This reuse the file nodeid from parent, but we append an extra letter
1174 This reuse the file nodeid from parent, but we append an extra letter
1175 when modified. Modified files get an extra 'm' while added files get
1175 when modified. Modified files get an extra 'm' while added files get
1176 an extra 'a'. This is used by manifests merge to see that files
1176 an extra 'a'. This is used by manifests merge to see that files
1177 are different and by update logic to avoid deleting newly added files.
1177 are different and by update logic to avoid deleting newly added files.
1178 """
1178 """
1179
1179
1180 man1 = self._parents[0].manifest()
1180 man1 = self._parents[0].manifest()
1181 man = man1.copy()
1181 man = man1.copy()
1182 if len(self._parents) > 1:
1182 if len(self._parents) > 1:
1183 man2 = self.p2().manifest()
1183 man2 = self.p2().manifest()
1184 def getman(f):
1184 def getman(f):
1185 if f in man1:
1185 if f in man1:
1186 return man1
1186 return man1
1187 return man2
1187 return man2
1188 else:
1188 else:
1189 getman = lambda f: man1
1189 getman = lambda f: man1
1190
1190
1191 copied = self._repo.dirstate.copies()
1191 copied = self._repo.dirstate.copies()
1192 ff = self._flagfunc
1192 ff = self._flagfunc
1193 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1193 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1194 for f in l:
1194 for f in l:
1195 orig = copied.get(f, f)
1195 orig = copied.get(f, f)
1196 man[f] = getman(orig).get(orig, nullid) + i
1196 man[f] = getman(orig).get(orig, nullid) + i
1197 try:
1197 try:
1198 man.setflag(f, ff(f))
1198 man.setflag(f, ff(f))
1199 except OSError:
1199 except OSError:
1200 pass
1200 pass
1201
1201
1202 for f in self._status.deleted + self._status.removed:
1202 for f in self._status.deleted + self._status.removed:
1203 if f in man:
1203 if f in man:
1204 del man[f]
1204 del man[f]
1205
1205
1206 return man
1206 return man
1207
1207
1208 @propertycache
1208 @propertycache
1209 def _status(self):
1209 def _status(self):
1210 return self._repo.status()
1210 return self._repo.status()
1211
1211
1212 @propertycache
1212 @propertycache
1213 def _user(self):
1213 def _user(self):
1214 return self._repo.ui.username()
1214 return self._repo.ui.username()
1215
1215
1216 @propertycache
1216 @propertycache
1217 def _date(self):
1217 def _date(self):
1218 return util.makedate()
1218 return util.makedate()
1219
1219
1220 def subrev(self, subpath):
1220 def subrev(self, subpath):
1221 return None
1221 return None
1222
1222
1223 def manifestnode(self):
1223 def manifestnode(self):
1224 return None
1224 return None
1225 def user(self):
1225 def user(self):
1226 return self._user or self._repo.ui.username()
1226 return self._user or self._repo.ui.username()
1227 def date(self):
1227 def date(self):
1228 return self._date
1228 return self._date
1229 def description(self):
1229 def description(self):
1230 return self._text
1230 return self._text
1231 def files(self):
1231 def files(self):
1232 return sorted(self._status.modified + self._status.added +
1232 return sorted(self._status.modified + self._status.added +
1233 self._status.removed)
1233 self._status.removed)
1234
1234
1235 def modified(self):
1235 def modified(self):
1236 return self._status.modified
1236 return self._status.modified
1237 def added(self):
1237 def added(self):
1238 return self._status.added
1238 return self._status.added
1239 def removed(self):
1239 def removed(self):
1240 return self._status.removed
1240 return self._status.removed
1241 def deleted(self):
1241 def deleted(self):
1242 return self._status.deleted
1242 return self._status.deleted
1243 def branch(self):
1243 def branch(self):
1244 return encoding.tolocal(self._extra['branch'])
1244 return encoding.tolocal(self._extra['branch'])
1245 def closesbranch(self):
1245 def closesbranch(self):
1246 return 'close' in self._extra
1246 return 'close' in self._extra
1247 def extra(self):
1247 def extra(self):
1248 return self._extra
1248 return self._extra
1249
1249
1250 def tags(self):
1250 def tags(self):
1251 return []
1251 return []
1252
1252
1253 def bookmarks(self):
1253 def bookmarks(self):
1254 b = []
1254 b = []
1255 for p in self.parents():
1255 for p in self.parents():
1256 b.extend(p.bookmarks())
1256 b.extend(p.bookmarks())
1257 return b
1257 return b
1258
1258
1259 def phase(self):
1259 def phase(self):
1260 phase = phases.draft # default phase to draft
1260 phase = phases.draft # default phase to draft
1261 for p in self.parents():
1261 for p in self.parents():
1262 phase = max(phase, p.phase())
1262 phase = max(phase, p.phase())
1263 return phase
1263 return phase
1264
1264
1265 def hidden(self):
1265 def hidden(self):
1266 return False
1266 return False
1267
1267
1268 def children(self):
1268 def children(self):
1269 return []
1269 return []
1270
1270
1271 def flags(self, path):
1271 def flags(self, path):
1272 if '_manifest' in self.__dict__:
1272 if '_manifest' in self.__dict__:
1273 try:
1273 try:
1274 return self._manifest.flags(path)
1274 return self._manifest.flags(path)
1275 except KeyError:
1275 except KeyError:
1276 return ''
1276 return ''
1277
1277
1278 try:
1278 try:
1279 return self._flagfunc(path)
1279 return self._flagfunc(path)
1280 except OSError:
1280 except OSError:
1281 return ''
1281 return ''
1282
1282
1283 def ancestor(self, c2):
1283 def ancestor(self, c2):
1284 """return the "best" ancestor context of self and c2"""
1284 """return the "best" ancestor context of self and c2"""
1285 return self._parents[0].ancestor(c2) # punt on two parents for now
1285 return self._parents[0].ancestor(c2) # punt on two parents for now
1286
1286
1287 def walk(self, match):
1287 def walk(self, match):
1288 '''Generates matching file names.'''
1288 '''Generates matching file names.'''
1289 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1289 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1290 True, False))
1290 True, False))
1291
1291
1292 def matches(self, match):
1292 def matches(self, match):
1293 return sorted(self._repo.dirstate.matches(match))
1293 return sorted(self._repo.dirstate.matches(match))
1294
1294
1295 def ancestors(self):
1295 def ancestors(self):
1296 for p in self._parents:
1296 for p in self._parents:
1297 yield p
1297 yield p
1298 for a in self._repo.changelog.ancestors(
1298 for a in self._repo.changelog.ancestors(
1299 [p.rev() for p in self._parents]):
1299 [p.rev() for p in self._parents]):
1300 yield changectx(self._repo, a)
1300 yield changectx(self._repo, a)
1301
1301
1302 def markcommitted(self, node):
1302 def markcommitted(self, node):
1303 """Perform post-commit cleanup necessary after committing this ctx
1303 """Perform post-commit cleanup necessary after committing this ctx
1304
1304
1305 Specifically, this updates backing stores this working context
1305 Specifically, this updates backing stores this working context
1306 wraps to reflect the fact that the changes reflected by this
1306 wraps to reflect the fact that the changes reflected by this
1307 workingctx have been committed. For example, it marks
1307 workingctx have been committed. For example, it marks
1308 modified and added files as normal in the dirstate.
1308 modified and added files as normal in the dirstate.
1309
1309
1310 """
1310 """
1311
1311
1312 self._repo.dirstate.beginparentchange()
1312 self._repo.dirstate.beginparentchange()
1313 for f in self.modified() + self.added():
1313 for f in self.modified() + self.added():
1314 self._repo.dirstate.normal(f)
1314 self._repo.dirstate.normal(f)
1315 for f in self.removed():
1315 for f in self.removed():
1316 self._repo.dirstate.drop(f)
1316 self._repo.dirstate.drop(f)
1317 self._repo.dirstate.setparents(node)
1317 self._repo.dirstate.setparents(node)
1318 self._repo.dirstate.endparentchange()
1318 self._repo.dirstate.endparentchange()
1319
1319
1320 # write changes out explicitly, because nesting wlock at
1320 # write changes out explicitly, because nesting wlock at
1321 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1321 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1322 # from immediately doing so for subsequent changing files
1322 # from immediately doing so for subsequent changing files
1323 self._repo.dirstate.write()
1323 self._repo.dirstate.write()
1324
1324
1325 class workingctx(committablectx):
1325 class workingctx(committablectx):
1326 """A workingctx object makes access to data related to
1326 """A workingctx object makes access to data related to
1327 the current working directory convenient.
1327 the current working directory convenient.
1328 date - any valid date string or (unixtime, offset), or None.
1328 date - any valid date string or (unixtime, offset), or None.
1329 user - username string, or None.
1329 user - username string, or None.
1330 extra - a dictionary of extra values, or None.
1330 extra - a dictionary of extra values, or None.
1331 changes - a list of file lists as returned by localrepo.status()
1331 changes - a list of file lists as returned by localrepo.status()
1332 or None to use the repository status.
1332 or None to use the repository status.
1333 """
1333 """
1334 def __init__(self, repo, text="", user=None, date=None, extra=None,
1334 def __init__(self, repo, text="", user=None, date=None, extra=None,
1335 changes=None):
1335 changes=None):
1336 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1336 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1337
1337
1338 def __iter__(self):
1338 def __iter__(self):
1339 d = self._repo.dirstate
1339 d = self._repo.dirstate
1340 for f in d:
1340 for f in d:
1341 if d[f] != 'r':
1341 if d[f] != 'r':
1342 yield f
1342 yield f
1343
1343
1344 def __contains__(self, key):
1344 def __contains__(self, key):
1345 return self._repo.dirstate[key] not in "?r"
1345 return self._repo.dirstate[key] not in "?r"
1346
1346
1347 def hex(self):
1347 def hex(self):
1348 return hex(wdirid)
1348 return hex(wdirid)
1349
1349
1350 @propertycache
1350 @propertycache
1351 def _parents(self):
1351 def _parents(self):
1352 p = self._repo.dirstate.parents()
1352 p = self._repo.dirstate.parents()
1353 if p[1] == nullid:
1353 if p[1] == nullid:
1354 p = p[:-1]
1354 p = p[:-1]
1355 return [changectx(self._repo, x) for x in p]
1355 return [changectx(self._repo, x) for x in p]
1356
1356
1357 def filectx(self, path, filelog=None):
1357 def filectx(self, path, filelog=None):
1358 """get a file context from the working directory"""
1358 """get a file context from the working directory"""
1359 return workingfilectx(self._repo, path, workingctx=self,
1359 return workingfilectx(self._repo, path, workingctx=self,
1360 filelog=filelog)
1360 filelog=filelog)
1361
1361
1362 def dirty(self, missing=False, merge=True, branch=True):
1362 def dirty(self, missing=False, merge=True, branch=True):
1363 "check whether a working directory is modified"
1363 "check whether a working directory is modified"
1364 # check subrepos first
1364 # check subrepos first
1365 for s in sorted(self.substate):
1365 for s in sorted(self.substate):
1366 if self.sub(s).dirty():
1366 if self.sub(s).dirty():
1367 return True
1367 return True
1368 # check current working dir
1368 # check current working dir
1369 return ((merge and self.p2()) or
1369 return ((merge and self.p2()) or
1370 (branch and self.branch() != self.p1().branch()) or
1370 (branch and self.branch() != self.p1().branch()) or
1371 self.modified() or self.added() or self.removed() or
1371 self.modified() or self.added() or self.removed() or
1372 (missing and self.deleted()))
1372 (missing and self.deleted()))
1373
1373
1374 def add(self, list, prefix=""):
1374 def add(self, list, prefix=""):
1375 join = lambda f: os.path.join(prefix, f)
1375 join = lambda f: os.path.join(prefix, f)
1376 wlock = self._repo.wlock()
1376 wlock = self._repo.wlock()
1377 ui, ds = self._repo.ui, self._repo.dirstate
1377 ui, ds = self._repo.ui, self._repo.dirstate
1378 try:
1378 try:
1379 rejected = []
1379 rejected = []
1380 lstat = self._repo.wvfs.lstat
1380 lstat = self._repo.wvfs.lstat
1381 for f in list:
1381 for f in list:
1382 scmutil.checkportable(ui, join(f))
1382 scmutil.checkportable(ui, join(f))
1383 try:
1383 try:
1384 st = lstat(f)
1384 st = lstat(f)
1385 except OSError:
1385 except OSError:
1386 ui.warn(_("%s does not exist!\n") % join(f))
1386 ui.warn(_("%s does not exist!\n") % join(f))
1387 rejected.append(f)
1387 rejected.append(f)
1388 continue
1388 continue
1389 if st.st_size > 10000000:
1389 if st.st_size > 10000000:
1390 ui.warn(_("%s: up to %d MB of RAM may be required "
1390 ui.warn(_("%s: up to %d MB of RAM may be required "
1391 "to manage this file\n"
1391 "to manage this file\n"
1392 "(use 'hg revert %s' to cancel the "
1392 "(use 'hg revert %s' to cancel the "
1393 "pending addition)\n")
1393 "pending addition)\n")
1394 % (f, 3 * st.st_size // 1000000, join(f)))
1394 % (f, 3 * st.st_size // 1000000, join(f)))
1395 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1395 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1396 ui.warn(_("%s not added: only files and symlinks "
1396 ui.warn(_("%s not added: only files and symlinks "
1397 "supported currently\n") % join(f))
1397 "supported currently\n") % join(f))
1398 rejected.append(f)
1398 rejected.append(f)
1399 elif ds[f] in 'amn':
1399 elif ds[f] in 'amn':
1400 ui.warn(_("%s already tracked!\n") % join(f))
1400 ui.warn(_("%s already tracked!\n") % join(f))
1401 elif ds[f] == 'r':
1401 elif ds[f] == 'r':
1402 ds.normallookup(f)
1402 ds.normallookup(f)
1403 else:
1403 else:
1404 ds.add(f)
1404 ds.add(f)
1405 return rejected
1405 return rejected
1406 finally:
1406 finally:
1407 wlock.release()
1407 wlock.release()
1408
1408
1409 def forget(self, files, prefix=""):
1409 def forget(self, files, prefix=""):
1410 join = lambda f: os.path.join(prefix, f)
1410 join = lambda f: os.path.join(prefix, f)
1411 wlock = self._repo.wlock()
1411 wlock = self._repo.wlock()
1412 try:
1412 try:
1413 rejected = []
1413 rejected = []
1414 for f in files:
1414 for f in files:
1415 if f not in self._repo.dirstate:
1415 if f not in self._repo.dirstate:
1416 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1416 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1417 rejected.append(f)
1417 rejected.append(f)
1418 elif self._repo.dirstate[f] != 'a':
1418 elif self._repo.dirstate[f] != 'a':
1419 self._repo.dirstate.remove(f)
1419 self._repo.dirstate.remove(f)
1420 else:
1420 else:
1421 self._repo.dirstate.drop(f)
1421 self._repo.dirstate.drop(f)
1422 return rejected
1422 return rejected
1423 finally:
1423 finally:
1424 wlock.release()
1424 wlock.release()
1425
1425
1426 def undelete(self, list):
1426 def undelete(self, list):
1427 pctxs = self.parents()
1427 pctxs = self.parents()
1428 wlock = self._repo.wlock()
1428 wlock = self._repo.wlock()
1429 try:
1429 try:
1430 for f in list:
1430 for f in list:
1431 if self._repo.dirstate[f] != 'r':
1431 if self._repo.dirstate[f] != 'r':
1432 self._repo.ui.warn(_("%s not removed!\n") % f)
1432 self._repo.ui.warn(_("%s not removed!\n") % f)
1433 else:
1433 else:
1434 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1434 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1435 t = fctx.data()
1435 t = fctx.data()
1436 self._repo.wwrite(f, t, fctx.flags())
1436 self._repo.wwrite(f, t, fctx.flags())
1437 self._repo.dirstate.normal(f)
1437 self._repo.dirstate.normal(f)
1438 finally:
1438 finally:
1439 wlock.release()
1439 wlock.release()
1440
1440
1441 def copy(self, source, dest):
1441 def copy(self, source, dest):
1442 try:
1442 try:
1443 st = self._repo.wvfs.lstat(dest)
1443 st = self._repo.wvfs.lstat(dest)
1444 except OSError as err:
1444 except OSError as err:
1445 if err.errno != errno.ENOENT:
1445 if err.errno != errno.ENOENT:
1446 raise
1446 raise
1447 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1447 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1448 return
1448 return
1449 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1449 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1450 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1450 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1451 "symbolic link\n") % dest)
1451 "symbolic link\n") % dest)
1452 else:
1452 else:
1453 wlock = self._repo.wlock()
1453 wlock = self._repo.wlock()
1454 try:
1454 try:
1455 if self._repo.dirstate[dest] in '?':
1455 if self._repo.dirstate[dest] in '?':
1456 self._repo.dirstate.add(dest)
1456 self._repo.dirstate.add(dest)
1457 elif self._repo.dirstate[dest] in 'r':
1457 elif self._repo.dirstate[dest] in 'r':
1458 self._repo.dirstate.normallookup(dest)
1458 self._repo.dirstate.normallookup(dest)
1459 self._repo.dirstate.copy(source, dest)
1459 self._repo.dirstate.copy(source, dest)
1460 finally:
1460 finally:
1461 wlock.release()
1461 wlock.release()
1462
1462
1463 def match(self, pats=[], include=None, exclude=None, default='glob',
1463 def match(self, pats=[], include=None, exclude=None, default='glob',
1464 listsubrepos=False, badfn=None):
1464 listsubrepos=False, badfn=None):
1465 r = self._repo
1465 r = self._repo
1466
1466
1467 # Only a case insensitive filesystem needs magic to translate user input
1467 # Only a case insensitive filesystem needs magic to translate user input
1468 # to actual case in the filesystem.
1468 # to actual case in the filesystem.
1469 if not util.checkcase(r.root):
1469 if not util.checkcase(r.root):
1470 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1470 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1471 exclude, default, r.auditor, self,
1471 exclude, default, r.auditor, self,
1472 listsubrepos=listsubrepos,
1472 listsubrepos=listsubrepos,
1473 badfn=badfn)
1473 badfn=badfn)
1474 return matchmod.match(r.root, r.getcwd(), pats,
1474 return matchmod.match(r.root, r.getcwd(), pats,
1475 include, exclude, default,
1475 include, exclude, default,
1476 auditor=r.auditor, ctx=self,
1476 auditor=r.auditor, ctx=self,
1477 listsubrepos=listsubrepos, badfn=badfn)
1477 listsubrepos=listsubrepos, badfn=badfn)
1478
1478
1479 def _filtersuspectsymlink(self, files):
1479 def _filtersuspectsymlink(self, files):
1480 if not files or self._repo.dirstate._checklink:
1480 if not files or self._repo.dirstate._checklink:
1481 return files
1481 return files
1482
1482
1483 # Symlink placeholders may get non-symlink-like contents
1483 # Symlink placeholders may get non-symlink-like contents
1484 # via user error or dereferencing by NFS or Samba servers,
1484 # via user error or dereferencing by NFS or Samba servers,
1485 # so we filter out any placeholders that don't look like a
1485 # so we filter out any placeholders that don't look like a
1486 # symlink
1486 # symlink
1487 sane = []
1487 sane = []
1488 for f in files:
1488 for f in files:
1489 if self.flags(f) == 'l':
1489 if self.flags(f) == 'l':
1490 d = self[f].data()
1490 d = self[f].data()
1491 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1491 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1492 self._repo.ui.debug('ignoring suspect symlink placeholder'
1492 self._repo.ui.debug('ignoring suspect symlink placeholder'
1493 ' "%s"\n' % f)
1493 ' "%s"\n' % f)
1494 continue
1494 continue
1495 sane.append(f)
1495 sane.append(f)
1496 return sane
1496 return sane
1497
1497
1498 def _checklookup(self, files):
1498 def _checklookup(self, files):
1499 # check for any possibly clean files
1499 # check for any possibly clean files
1500 if not files:
1500 if not files:
1501 return [], []
1501 return [], []
1502
1502
1503 modified = []
1503 modified = []
1504 fixup = []
1504 fixup = []
1505 pctx = self._parents[0]
1505 pctx = self._parents[0]
1506 # do a full compare of any files that might have changed
1506 # do a full compare of any files that might have changed
1507 for f in sorted(files):
1507 for f in sorted(files):
1508 if (f not in pctx or self.flags(f) != pctx.flags(f)
1508 if (f not in pctx or self.flags(f) != pctx.flags(f)
1509 or pctx[f].cmp(self[f])):
1509 or pctx[f].cmp(self[f])):
1510 modified.append(f)
1510 modified.append(f)
1511 else:
1511 else:
1512 fixup.append(f)
1512 fixup.append(f)
1513
1513
1514 # update dirstate for files that are actually clean
1514 # update dirstate for files that are actually clean
1515 if fixup:
1515 if fixup:
1516 try:
1516 try:
1517 # updating the dirstate is optional
1517 # updating the dirstate is optional
1518 # so we don't wait on the lock
1518 # so we don't wait on the lock
1519 # wlock can invalidate the dirstate, so cache normal _after_
1519 # wlock can invalidate the dirstate, so cache normal _after_
1520 # taking the lock
1520 # taking the lock
1521 wlock = self._repo.wlock(False)
1521 wlock = self._repo.wlock(False)
1522 normal = self._repo.dirstate.normal
1522 normal = self._repo.dirstate.normal
1523 try:
1523 try:
1524 for f in fixup:
1524 for f in fixup:
1525 normal(f)
1525 normal(f)
1526 # write changes out explicitly, because nesting
1526 # write changes out explicitly, because nesting
1527 # wlock at runtime may prevent 'wlock.release()'
1527 # wlock at runtime may prevent 'wlock.release()'
1528 # below from doing so for subsequent changing files
1528 # below from doing so for subsequent changing files
1529 self._repo.dirstate.write()
1529 self._repo.dirstate.write()
1530 finally:
1530 finally:
1531 wlock.release()
1531 wlock.release()
1532 except error.LockError:
1532 except error.LockError:
1533 pass
1533 pass
1534 return modified, fixup
1534 return modified, fixup
1535
1535
1536 def _manifestmatches(self, match, s):
1536 def _manifestmatches(self, match, s):
1537 """Slow path for workingctx
1537 """Slow path for workingctx
1538
1538
1539 The fast path is when we compare the working directory to its parent
1539 The fast path is when we compare the working directory to its parent
1540 which means this function is comparing with a non-parent; therefore we
1540 which means this function is comparing with a non-parent; therefore we
1541 need to build a manifest and return what matches.
1541 need to build a manifest and return what matches.
1542 """
1542 """
1543 mf = self._repo['.']._manifestmatches(match, s)
1543 mf = self._repo['.']._manifestmatches(match, s)
1544 for f in s.modified + s.added:
1544 for f in s.modified + s.added:
1545 mf[f] = _newnode
1545 mf[f] = _newnode
1546 mf.setflag(f, self.flags(f))
1546 mf.setflag(f, self.flags(f))
1547 for f in s.removed:
1547 for f in s.removed:
1548 if f in mf:
1548 if f in mf:
1549 del mf[f]
1549 del mf[f]
1550 return mf
1550 return mf
1551
1551
1552 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1552 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1553 unknown=False):
1553 unknown=False):
1554 '''Gets the status from the dirstate -- internal use only.'''
1554 '''Gets the status from the dirstate -- internal use only.'''
1555 listignored, listclean, listunknown = ignored, clean, unknown
1555 listignored, listclean, listunknown = ignored, clean, unknown
1556 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1556 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1557 subrepos = []
1557 subrepos = []
1558 if '.hgsub' in self:
1558 if '.hgsub' in self:
1559 subrepos = sorted(self.substate)
1559 subrepos = sorted(self.substate)
1560 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1560 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1561 listclean, listunknown)
1561 listclean, listunknown)
1562
1562
1563 # check for any possibly clean files
1563 # check for any possibly clean files
1564 if cmp:
1564 if cmp:
1565 modified2, fixup = self._checklookup(cmp)
1565 modified2, fixup = self._checklookup(cmp)
1566 s.modified.extend(modified2)
1566 s.modified.extend(modified2)
1567
1567
1568 # update dirstate for files that are actually clean
1568 # update dirstate for files that are actually clean
1569 if fixup and listclean:
1569 if fixup and listclean:
1570 s.clean.extend(fixup)
1570 s.clean.extend(fixup)
1571
1571
1572 if match.always():
1572 if match.always():
1573 # cache for performance
1573 # cache for performance
1574 if s.unknown or s.ignored or s.clean:
1574 if s.unknown or s.ignored or s.clean:
1575 # "_status" is cached with list*=False in the normal route
1575 # "_status" is cached with list*=False in the normal route
1576 self._status = scmutil.status(s.modified, s.added, s.removed,
1576 self._status = scmutil.status(s.modified, s.added, s.removed,
1577 s.deleted, [], [], [])
1577 s.deleted, [], [], [])
1578 else:
1578 else:
1579 self._status = s
1579 self._status = s
1580
1580
1581 return s
1581 return s
1582
1582
1583 def _buildstatus(self, other, s, match, listignored, listclean,
1583 def _buildstatus(self, other, s, match, listignored, listclean,
1584 listunknown):
1584 listunknown):
1585 """build a status with respect to another context
1585 """build a status with respect to another context
1586
1586
1587 This includes logic for maintaining the fast path of status when
1587 This includes logic for maintaining the fast path of status when
1588 comparing the working directory against its parent, which is to skip
1588 comparing the working directory against its parent, which is to skip
1589 building a new manifest if self (working directory) is not comparing
1589 building a new manifest if self (working directory) is not comparing
1590 against its parent (repo['.']).
1590 against its parent (repo['.']).
1591 """
1591 """
1592 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1592 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1593 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1593 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1594 # might have accidentally ended up with the entire contents of the file
1594 # might have accidentally ended up with the entire contents of the file
1595 # they are supposed to be linking to.
1595 # they are supposed to be linking to.
1596 s.modified[:] = self._filtersuspectsymlink(s.modified)
1596 s.modified[:] = self._filtersuspectsymlink(s.modified)
1597 if other != self._repo['.']:
1597 if other != self._repo['.']:
1598 s = super(workingctx, self)._buildstatus(other, s, match,
1598 s = super(workingctx, self)._buildstatus(other, s, match,
1599 listignored, listclean,
1599 listignored, listclean,
1600 listunknown)
1600 listunknown)
1601 return s
1601 return s
1602
1602
1603 def _matchstatus(self, other, match):
1603 def _matchstatus(self, other, match):
1604 """override the match method with a filter for directory patterns
1604 """override the match method with a filter for directory patterns
1605
1605
1606 We use inheritance to customize the match.bad method only in cases of
1606 We use inheritance to customize the match.bad method only in cases of
1607 workingctx since it belongs only to the working directory when
1607 workingctx since it belongs only to the working directory when
1608 comparing against the parent changeset.
1608 comparing against the parent changeset.
1609
1609
1610 If we aren't comparing against the working directory's parent, then we
1610 If we aren't comparing against the working directory's parent, then we
1611 just use the default match object sent to us.
1611 just use the default match object sent to us.
1612 """
1612 """
1613 superself = super(workingctx, self)
1613 superself = super(workingctx, self)
1614 match = superself._matchstatus(other, match)
1614 match = superself._matchstatus(other, match)
1615 if other != self._repo['.']:
1615 if other != self._repo['.']:
1616 def bad(f, msg):
1616 def bad(f, msg):
1617 # 'f' may be a directory pattern from 'match.files()',
1617 # 'f' may be a directory pattern from 'match.files()',
1618 # so 'f not in ctx1' is not enough
1618 # so 'f not in ctx1' is not enough
1619 if f not in other and not other.hasdir(f):
1619 if f not in other and not other.hasdir(f):
1620 self._repo.ui.warn('%s: %s\n' %
1620 self._repo.ui.warn('%s: %s\n' %
1621 (self._repo.dirstate.pathto(f), msg))
1621 (self._repo.dirstate.pathto(f), msg))
1622 match.bad = bad
1622 match.bad = bad
1623 return match
1623 return match
1624
1624
1625 class committablefilectx(basefilectx):
1625 class committablefilectx(basefilectx):
1626 """A committablefilectx provides common functionality for a file context
1626 """A committablefilectx provides common functionality for a file context
1627 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1627 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1628 def __init__(self, repo, path, filelog=None, ctx=None):
1628 def __init__(self, repo, path, filelog=None, ctx=None):
1629 self._repo = repo
1629 self._repo = repo
1630 self._path = path
1630 self._path = path
1631 self._changeid = None
1631 self._changeid = None
1632 self._filerev = self._filenode = None
1632 self._filerev = self._filenode = None
1633
1633
1634 if filelog is not None:
1634 if filelog is not None:
1635 self._filelog = filelog
1635 self._filelog = filelog
1636 if ctx:
1636 if ctx:
1637 self._changectx = ctx
1637 self._changectx = ctx
1638
1638
1639 def __nonzero__(self):
1639 def __nonzero__(self):
1640 return True
1640 return True
1641
1641
1642 def linkrev(self):
1642 def linkrev(self):
1643 # linked to self._changectx no matter if file is modified or not
1643 # linked to self._changectx no matter if file is modified or not
1644 return self.rev()
1644 return self.rev()
1645
1645
1646 def parents(self):
1646 def parents(self):
1647 '''return parent filectxs, following copies if necessary'''
1647 '''return parent filectxs, following copies if necessary'''
1648 def filenode(ctx, path):
1648 def filenode(ctx, path):
1649 return ctx._manifest.get(path, nullid)
1649 return ctx._manifest.get(path, nullid)
1650
1650
1651 path = self._path
1651 path = self._path
1652 fl = self._filelog
1652 fl = self._filelog
1653 pcl = self._changectx._parents
1653 pcl = self._changectx._parents
1654 renamed = self.renamed()
1654 renamed = self.renamed()
1655
1655
1656 if renamed:
1656 if renamed:
1657 pl = [renamed + (None,)]
1657 pl = [renamed + (None,)]
1658 else:
1658 else:
1659 pl = [(path, filenode(pcl[0], path), fl)]
1659 pl = [(path, filenode(pcl[0], path), fl)]
1660
1660
1661 for pc in pcl[1:]:
1661 for pc in pcl[1:]:
1662 pl.append((path, filenode(pc, path), fl))
1662 pl.append((path, filenode(pc, path), fl))
1663
1663
1664 return [self._parentfilectx(p, fileid=n, filelog=l)
1664 return [self._parentfilectx(p, fileid=n, filelog=l)
1665 for p, n, l in pl if n != nullid]
1665 for p, n, l in pl if n != nullid]
1666
1666
1667 def children(self):
1667 def children(self):
1668 return []
1668 return []
1669
1669
1670 class workingfilectx(committablefilectx):
1670 class workingfilectx(committablefilectx):
1671 """A workingfilectx object makes access to data related to a particular
1671 """A workingfilectx object makes access to data related to a particular
1672 file in the working directory convenient."""
1672 file in the working directory convenient."""
1673 def __init__(self, repo, path, filelog=None, workingctx=None):
1673 def __init__(self, repo, path, filelog=None, workingctx=None):
1674 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1674 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1675
1675
1676 @propertycache
1676 @propertycache
1677 def _changectx(self):
1677 def _changectx(self):
1678 return workingctx(self._repo)
1678 return workingctx(self._repo)
1679
1679
1680 def data(self):
1680 def data(self):
1681 return self._repo.wread(self._path)
1681 return self._repo.wread(self._path)
1682 def renamed(self):
1682 def renamed(self):
1683 rp = self._repo.dirstate.copied(self._path)
1683 rp = self._repo.dirstate.copied(self._path)
1684 if not rp:
1684 if not rp:
1685 return None
1685 return None
1686 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1686 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1687
1687
1688 def size(self):
1688 def size(self):
1689 return self._repo.wvfs.lstat(self._path).st_size
1689 return self._repo.wvfs.lstat(self._path).st_size
1690 def date(self):
1690 def date(self):
1691 t, tz = self._changectx.date()
1691 t, tz = self._changectx.date()
1692 try:
1692 try:
1693 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1693 return (util.statmtimesec(self._repo.wvfs.lstat(self._path)), tz)
1694 except OSError as err:
1694 except OSError as err:
1695 if err.errno != errno.ENOENT:
1695 if err.errno != errno.ENOENT:
1696 raise
1696 raise
1697 return (t, tz)
1697 return (t, tz)
1698
1698
1699 def cmp(self, fctx):
1699 def cmp(self, fctx):
1700 """compare with other file context
1700 """compare with other file context
1701
1701
1702 returns True if different than fctx.
1702 returns True if different than fctx.
1703 """
1703 """
1704 # fctx should be a filectx (not a workingfilectx)
1704 # fctx should be a filectx (not a workingfilectx)
1705 # invert comparison to reuse the same code path
1705 # invert comparison to reuse the same code path
1706 return fctx.cmp(self)
1706 return fctx.cmp(self)
1707
1707
1708 def remove(self, ignoremissing=False):
1708 def remove(self, ignoremissing=False):
1709 """wraps unlink for a repo's working directory"""
1709 """wraps unlink for a repo's working directory"""
1710 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1710 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1711
1711
1712 def write(self, data, flags):
1712 def write(self, data, flags):
1713 """wraps repo.wwrite"""
1713 """wraps repo.wwrite"""
1714 self._repo.wwrite(self._path, data, flags)
1714 self._repo.wwrite(self._path, data, flags)
1715
1715
1716 class workingcommitctx(workingctx):
1716 class workingcommitctx(workingctx):
1717 """A workingcommitctx object makes access to data related to
1717 """A workingcommitctx object makes access to data related to
1718 the revision being committed convenient.
1718 the revision being committed convenient.
1719
1719
1720 This hides changes in the working directory, if they aren't
1720 This hides changes in the working directory, if they aren't
1721 committed in this context.
1721 committed in this context.
1722 """
1722 """
1723 def __init__(self, repo, changes,
1723 def __init__(self, repo, changes,
1724 text="", user=None, date=None, extra=None):
1724 text="", user=None, date=None, extra=None):
1725 super(workingctx, self).__init__(repo, text, user, date, extra,
1725 super(workingctx, self).__init__(repo, text, user, date, extra,
1726 changes)
1726 changes)
1727
1727
1728 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1728 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1729 unknown=False):
1729 unknown=False):
1730 """Return matched files only in ``self._status``
1730 """Return matched files only in ``self._status``
1731
1731
1732 Uncommitted files appear "clean" via this context, even if
1732 Uncommitted files appear "clean" via this context, even if
1733 they aren't actually so in the working directory.
1733 they aren't actually so in the working directory.
1734 """
1734 """
1735 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1735 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1736 if clean:
1736 if clean:
1737 clean = [f for f in self._manifest if f not in self._changedset]
1737 clean = [f for f in self._manifest if f not in self._changedset]
1738 else:
1738 else:
1739 clean = []
1739 clean = []
1740 return scmutil.status([f for f in self._status.modified if match(f)],
1740 return scmutil.status([f for f in self._status.modified if match(f)],
1741 [f for f in self._status.added if match(f)],
1741 [f for f in self._status.added if match(f)],
1742 [f for f in self._status.removed if match(f)],
1742 [f for f in self._status.removed if match(f)],
1743 [], [], [], clean)
1743 [], [], [], clean)
1744
1744
1745 @propertycache
1745 @propertycache
1746 def _changedset(self):
1746 def _changedset(self):
1747 """Return the set of files changed in this context
1747 """Return the set of files changed in this context
1748 """
1748 """
1749 changed = set(self._status.modified)
1749 changed = set(self._status.modified)
1750 changed.update(self._status.added)
1750 changed.update(self._status.added)
1751 changed.update(self._status.removed)
1751 changed.update(self._status.removed)
1752 return changed
1752 return changed
1753
1753
1754 class memctx(committablectx):
1754 class memctx(committablectx):
1755 """Use memctx to perform in-memory commits via localrepo.commitctx().
1755 """Use memctx to perform in-memory commits via localrepo.commitctx().
1756
1756
1757 Revision information is supplied at initialization time while
1757 Revision information is supplied at initialization time while
1758 related files data and is made available through a callback
1758 related files data and is made available through a callback
1759 mechanism. 'repo' is the current localrepo, 'parents' is a
1759 mechanism. 'repo' is the current localrepo, 'parents' is a
1760 sequence of two parent revisions identifiers (pass None for every
1760 sequence of two parent revisions identifiers (pass None for every
1761 missing parent), 'text' is the commit message and 'files' lists
1761 missing parent), 'text' is the commit message and 'files' lists
1762 names of files touched by the revision (normalized and relative to
1762 names of files touched by the revision (normalized and relative to
1763 repository root).
1763 repository root).
1764
1764
1765 filectxfn(repo, memctx, path) is a callable receiving the
1765 filectxfn(repo, memctx, path) is a callable receiving the
1766 repository, the current memctx object and the normalized path of
1766 repository, the current memctx object and the normalized path of
1767 requested file, relative to repository root. It is fired by the
1767 requested file, relative to repository root. It is fired by the
1768 commit function for every file in 'files', but calls order is
1768 commit function for every file in 'files', but calls order is
1769 undefined. If the file is available in the revision being
1769 undefined. If the file is available in the revision being
1770 committed (updated or added), filectxfn returns a memfilectx
1770 committed (updated or added), filectxfn returns a memfilectx
1771 object. If the file was removed, filectxfn raises an
1771 object. If the file was removed, filectxfn raises an
1772 IOError. Moved files are represented by marking the source file
1772 IOError. Moved files are represented by marking the source file
1773 removed and the new file added with copy information (see
1773 removed and the new file added with copy information (see
1774 memfilectx).
1774 memfilectx).
1775
1775
1776 user receives the committer name and defaults to current
1776 user receives the committer name and defaults to current
1777 repository username, date is the commit date in any format
1777 repository username, date is the commit date in any format
1778 supported by util.parsedate() and defaults to current date, extra
1778 supported by util.parsedate() and defaults to current date, extra
1779 is a dictionary of metadata or is left empty.
1779 is a dictionary of metadata or is left empty.
1780 """
1780 """
1781
1781
1782 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1782 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1783 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1783 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1784 # this field to determine what to do in filectxfn.
1784 # this field to determine what to do in filectxfn.
1785 _returnnoneformissingfiles = True
1785 _returnnoneformissingfiles = True
1786
1786
1787 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1787 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1788 date=None, extra=None, editor=False):
1788 date=None, extra=None, editor=False):
1789 super(memctx, self).__init__(repo, text, user, date, extra)
1789 super(memctx, self).__init__(repo, text, user, date, extra)
1790 self._rev = None
1790 self._rev = None
1791 self._node = None
1791 self._node = None
1792 parents = [(p or nullid) for p in parents]
1792 parents = [(p or nullid) for p in parents]
1793 p1, p2 = parents
1793 p1, p2 = parents
1794 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1794 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1795 files = sorted(set(files))
1795 files = sorted(set(files))
1796 self._files = files
1796 self._files = files
1797 self.substate = {}
1797 self.substate = {}
1798
1798
1799 # if store is not callable, wrap it in a function
1799 # if store is not callable, wrap it in a function
1800 if not callable(filectxfn):
1800 if not callable(filectxfn):
1801 def getfilectx(repo, memctx, path):
1801 def getfilectx(repo, memctx, path):
1802 fctx = filectxfn[path]
1802 fctx = filectxfn[path]
1803 # this is weird but apparently we only keep track of one parent
1803 # this is weird but apparently we only keep track of one parent
1804 # (why not only store that instead of a tuple?)
1804 # (why not only store that instead of a tuple?)
1805 copied = fctx.renamed()
1805 copied = fctx.renamed()
1806 if copied:
1806 if copied:
1807 copied = copied[0]
1807 copied = copied[0]
1808 return memfilectx(repo, path, fctx.data(),
1808 return memfilectx(repo, path, fctx.data(),
1809 islink=fctx.islink(), isexec=fctx.isexec(),
1809 islink=fctx.islink(), isexec=fctx.isexec(),
1810 copied=copied, memctx=memctx)
1810 copied=copied, memctx=memctx)
1811 self._filectxfn = getfilectx
1811 self._filectxfn = getfilectx
1812 else:
1812 else:
1813 # "util.cachefunc" reduces invocation of possibly expensive
1813 # "util.cachefunc" reduces invocation of possibly expensive
1814 # "filectxfn" for performance (e.g. converting from another VCS)
1814 # "filectxfn" for performance (e.g. converting from another VCS)
1815 self._filectxfn = util.cachefunc(filectxfn)
1815 self._filectxfn = util.cachefunc(filectxfn)
1816
1816
1817 if extra:
1817 if extra:
1818 self._extra = extra.copy()
1818 self._extra = extra.copy()
1819 else:
1819 else:
1820 self._extra = {}
1820 self._extra = {}
1821
1821
1822 if self._extra.get('branch', '') == '':
1822 if self._extra.get('branch', '') == '':
1823 self._extra['branch'] = 'default'
1823 self._extra['branch'] = 'default'
1824
1824
1825 if editor:
1825 if editor:
1826 self._text = editor(self._repo, self, [])
1826 self._text = editor(self._repo, self, [])
1827 self._repo.savecommitmessage(self._text)
1827 self._repo.savecommitmessage(self._text)
1828
1828
1829 def filectx(self, path, filelog=None):
1829 def filectx(self, path, filelog=None):
1830 """get a file context from the working directory
1830 """get a file context from the working directory
1831
1831
1832 Returns None if file doesn't exist and should be removed."""
1832 Returns None if file doesn't exist and should be removed."""
1833 return self._filectxfn(self._repo, self, path)
1833 return self._filectxfn(self._repo, self, path)
1834
1834
1835 def commit(self):
1835 def commit(self):
1836 """commit context to the repo"""
1836 """commit context to the repo"""
1837 return self._repo.commitctx(self)
1837 return self._repo.commitctx(self)
1838
1838
1839 @propertycache
1839 @propertycache
1840 def _manifest(self):
1840 def _manifest(self):
1841 """generate a manifest based on the return values of filectxfn"""
1841 """generate a manifest based on the return values of filectxfn"""
1842
1842
1843 # keep this simple for now; just worry about p1
1843 # keep this simple for now; just worry about p1
1844 pctx = self._parents[0]
1844 pctx = self._parents[0]
1845 man = pctx.manifest().copy()
1845 man = pctx.manifest().copy()
1846
1846
1847 for f in self._status.modified:
1847 for f in self._status.modified:
1848 p1node = nullid
1848 p1node = nullid
1849 p2node = nullid
1849 p2node = nullid
1850 p = pctx[f].parents() # if file isn't in pctx, check p2?
1850 p = pctx[f].parents() # if file isn't in pctx, check p2?
1851 if len(p) > 0:
1851 if len(p) > 0:
1852 p1node = p[0].node()
1852 p1node = p[0].node()
1853 if len(p) > 1:
1853 if len(p) > 1:
1854 p2node = p[1].node()
1854 p2node = p[1].node()
1855 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1855 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1856
1856
1857 for f in self._status.added:
1857 for f in self._status.added:
1858 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1858 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1859
1859
1860 for f in self._status.removed:
1860 for f in self._status.removed:
1861 if f in man:
1861 if f in man:
1862 del man[f]
1862 del man[f]
1863
1863
1864 return man
1864 return man
1865
1865
1866 @propertycache
1866 @propertycache
1867 def _status(self):
1867 def _status(self):
1868 """Calculate exact status from ``files`` specified at construction
1868 """Calculate exact status from ``files`` specified at construction
1869 """
1869 """
1870 man1 = self.p1().manifest()
1870 man1 = self.p1().manifest()
1871 p2 = self._parents[1]
1871 p2 = self._parents[1]
1872 # "1 < len(self._parents)" can't be used for checking
1872 # "1 < len(self._parents)" can't be used for checking
1873 # existence of the 2nd parent, because "memctx._parents" is
1873 # existence of the 2nd parent, because "memctx._parents" is
1874 # explicitly initialized by the list, of which length is 2.
1874 # explicitly initialized by the list, of which length is 2.
1875 if p2.node() != nullid:
1875 if p2.node() != nullid:
1876 man2 = p2.manifest()
1876 man2 = p2.manifest()
1877 managing = lambda f: f in man1 or f in man2
1877 managing = lambda f: f in man1 or f in man2
1878 else:
1878 else:
1879 managing = lambda f: f in man1
1879 managing = lambda f: f in man1
1880
1880
1881 modified, added, removed = [], [], []
1881 modified, added, removed = [], [], []
1882 for f in self._files:
1882 for f in self._files:
1883 if not managing(f):
1883 if not managing(f):
1884 added.append(f)
1884 added.append(f)
1885 elif self[f]:
1885 elif self[f]:
1886 modified.append(f)
1886 modified.append(f)
1887 else:
1887 else:
1888 removed.append(f)
1888 removed.append(f)
1889
1889
1890 return scmutil.status(modified, added, removed, [], [], [], [])
1890 return scmutil.status(modified, added, removed, [], [], [], [])
1891
1891
1892 class memfilectx(committablefilectx):
1892 class memfilectx(committablefilectx):
1893 """memfilectx represents an in-memory file to commit.
1893 """memfilectx represents an in-memory file to commit.
1894
1894
1895 See memctx and committablefilectx for more details.
1895 See memctx and committablefilectx for more details.
1896 """
1896 """
1897 def __init__(self, repo, path, data, islink=False,
1897 def __init__(self, repo, path, data, islink=False,
1898 isexec=False, copied=None, memctx=None):
1898 isexec=False, copied=None, memctx=None):
1899 """
1899 """
1900 path is the normalized file path relative to repository root.
1900 path is the normalized file path relative to repository root.
1901 data is the file content as a string.
1901 data is the file content as a string.
1902 islink is True if the file is a symbolic link.
1902 islink is True if the file is a symbolic link.
1903 isexec is True if the file is executable.
1903 isexec is True if the file is executable.
1904 copied is the source file path if current file was copied in the
1904 copied is the source file path if current file was copied in the
1905 revision being committed, or None."""
1905 revision being committed, or None."""
1906 super(memfilectx, self).__init__(repo, path, None, memctx)
1906 super(memfilectx, self).__init__(repo, path, None, memctx)
1907 self._data = data
1907 self._data = data
1908 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1908 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1909 self._copied = None
1909 self._copied = None
1910 if copied:
1910 if copied:
1911 self._copied = (copied, nullid)
1911 self._copied = (copied, nullid)
1912
1912
1913 def data(self):
1913 def data(self):
1914 return self._data
1914 return self._data
1915 def size(self):
1915 def size(self):
1916 return len(self.data())
1916 return len(self.data())
1917 def flags(self):
1917 def flags(self):
1918 return self._flags
1918 return self._flags
1919 def renamed(self):
1919 def renamed(self):
1920 return self._copied
1920 return self._copied
1921
1921
1922 def remove(self, ignoremissing=False):
1922 def remove(self, ignoremissing=False):
1923 """wraps unlink for a repo's working directory"""
1923 """wraps unlink for a repo's working directory"""
1924 # need to figure out what to do here
1924 # need to figure out what to do here
1925 del self._changectx[self._path]
1925 del self._changectx[self._path]
1926
1926
1927 def write(self, data, flags):
1927 def write(self, data, flags):
1928 """wraps repo.wwrite"""
1928 """wraps repo.wwrite"""
1929 self._data = data
1929 self._data = data
@@ -1,1044 +1,1044 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import scmutil, util, osutil, parsers, encoding, pathutil
10 import scmutil, util, osutil, parsers, encoding, pathutil
11 import os, stat, errno
11 import os, stat, errno
12 import match as matchmod
12 import match as matchmod
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15 filecache = scmutil.filecache
15 filecache = scmutil.filecache
16 _rangemask = 0x7fffffff
16 _rangemask = 0x7fffffff
17
17
18 dirstatetuple = parsers.dirstatetuple
18 dirstatetuple = parsers.dirstatetuple
19
19
20 class repocache(filecache):
20 class repocache(filecache):
21 """filecache for files in .hg/"""
21 """filecache for files in .hg/"""
22 def join(self, obj, fname):
22 def join(self, obj, fname):
23 return obj._opener.join(fname)
23 return obj._opener.join(fname)
24
24
25 class rootcache(filecache):
25 class rootcache(filecache):
26 """filecache for files in the repository root"""
26 """filecache for files in the repository root"""
27 def join(self, obj, fname):
27 def join(self, obj, fname):
28 return obj._join(fname)
28 return obj._join(fname)
29
29
30 class dirstate(object):
30 class dirstate(object):
31
31
32 def __init__(self, opener, ui, root, validate):
32 def __init__(self, opener, ui, root, validate):
33 '''Create a new dirstate object.
33 '''Create a new dirstate object.
34
34
35 opener is an open()-like callable that can be used to open the
35 opener is an open()-like callable that can be used to open the
36 dirstate file; root is the root of the directory tracked by
36 dirstate file; root is the root of the directory tracked by
37 the dirstate.
37 the dirstate.
38 '''
38 '''
39 self._opener = opener
39 self._opener = opener
40 self._validate = validate
40 self._validate = validate
41 self._root = root
41 self._root = root
42 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
42 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
43 # UNC path pointing to root share (issue4557)
43 # UNC path pointing to root share (issue4557)
44 self._rootdir = pathutil.normasprefix(root)
44 self._rootdir = pathutil.normasprefix(root)
45 # internal config: ui.forcecwd
45 # internal config: ui.forcecwd
46 forcecwd = ui.config('ui', 'forcecwd')
46 forcecwd = ui.config('ui', 'forcecwd')
47 if forcecwd:
47 if forcecwd:
48 self._cwd = forcecwd
48 self._cwd = forcecwd
49 self._dirty = False
49 self._dirty = False
50 self._dirtypl = False
50 self._dirtypl = False
51 self._lastnormaltime = 0
51 self._lastnormaltime = 0
52 self._ui = ui
52 self._ui = ui
53 self._filecache = {}
53 self._filecache = {}
54 self._parentwriters = 0
54 self._parentwriters = 0
55 self._filename = 'dirstate'
55 self._filename = 'dirstate'
56
56
57 def beginparentchange(self):
57 def beginparentchange(self):
58 '''Marks the beginning of a set of changes that involve changing
58 '''Marks the beginning of a set of changes that involve changing
59 the dirstate parents. If there is an exception during this time,
59 the dirstate parents. If there is an exception during this time,
60 the dirstate will not be written when the wlock is released. This
60 the dirstate will not be written when the wlock is released. This
61 prevents writing an incoherent dirstate where the parent doesn't
61 prevents writing an incoherent dirstate where the parent doesn't
62 match the contents.
62 match the contents.
63 '''
63 '''
64 self._parentwriters += 1
64 self._parentwriters += 1
65
65
66 def endparentchange(self):
66 def endparentchange(self):
67 '''Marks the end of a set of changes that involve changing the
67 '''Marks the end of a set of changes that involve changing the
68 dirstate parents. Once all parent changes have been marked done,
68 dirstate parents. Once all parent changes have been marked done,
69 the wlock will be free to write the dirstate on release.
69 the wlock will be free to write the dirstate on release.
70 '''
70 '''
71 if self._parentwriters > 0:
71 if self._parentwriters > 0:
72 self._parentwriters -= 1
72 self._parentwriters -= 1
73
73
74 def pendingparentchange(self):
74 def pendingparentchange(self):
75 '''Returns true if the dirstate is in the middle of a set of changes
75 '''Returns true if the dirstate is in the middle of a set of changes
76 that modify the dirstate parent.
76 that modify the dirstate parent.
77 '''
77 '''
78 return self._parentwriters > 0
78 return self._parentwriters > 0
79
79
80 @propertycache
80 @propertycache
81 def _map(self):
81 def _map(self):
82 '''Return the dirstate contents as a map from filename to
82 '''Return the dirstate contents as a map from filename to
83 (state, mode, size, time).'''
83 (state, mode, size, time).'''
84 self._read()
84 self._read()
85 return self._map
85 return self._map
86
86
87 @propertycache
87 @propertycache
88 def _copymap(self):
88 def _copymap(self):
89 self._read()
89 self._read()
90 return self._copymap
90 return self._copymap
91
91
92 @propertycache
92 @propertycache
93 def _filefoldmap(self):
93 def _filefoldmap(self):
94 try:
94 try:
95 makefilefoldmap = parsers.make_file_foldmap
95 makefilefoldmap = parsers.make_file_foldmap
96 except AttributeError:
96 except AttributeError:
97 pass
97 pass
98 else:
98 else:
99 return makefilefoldmap(self._map, util.normcasespec,
99 return makefilefoldmap(self._map, util.normcasespec,
100 util.normcasefallback)
100 util.normcasefallback)
101
101
102 f = {}
102 f = {}
103 normcase = util.normcase
103 normcase = util.normcase
104 for name, s in self._map.iteritems():
104 for name, s in self._map.iteritems():
105 if s[0] != 'r':
105 if s[0] != 'r':
106 f[normcase(name)] = name
106 f[normcase(name)] = name
107 f['.'] = '.' # prevents useless util.fspath() invocation
107 f['.'] = '.' # prevents useless util.fspath() invocation
108 return f
108 return f
109
109
110 @propertycache
110 @propertycache
111 def _dirfoldmap(self):
111 def _dirfoldmap(self):
112 f = {}
112 f = {}
113 normcase = util.normcase
113 normcase = util.normcase
114 for name in self._dirs:
114 for name in self._dirs:
115 f[normcase(name)] = name
115 f[normcase(name)] = name
116 return f
116 return f
117
117
118 @repocache('branch')
118 @repocache('branch')
119 def _branch(self):
119 def _branch(self):
120 try:
120 try:
121 return self._opener.read("branch").strip() or "default"
121 return self._opener.read("branch").strip() or "default"
122 except IOError as inst:
122 except IOError as inst:
123 if inst.errno != errno.ENOENT:
123 if inst.errno != errno.ENOENT:
124 raise
124 raise
125 return "default"
125 return "default"
126
126
127 @propertycache
127 @propertycache
128 def _pl(self):
128 def _pl(self):
129 try:
129 try:
130 fp = self._opener(self._filename)
130 fp = self._opener(self._filename)
131 st = fp.read(40)
131 st = fp.read(40)
132 fp.close()
132 fp.close()
133 l = len(st)
133 l = len(st)
134 if l == 40:
134 if l == 40:
135 return st[:20], st[20:40]
135 return st[:20], st[20:40]
136 elif l > 0 and l < 40:
136 elif l > 0 and l < 40:
137 raise util.Abort(_('working directory state appears damaged!'))
137 raise util.Abort(_('working directory state appears damaged!'))
138 except IOError as err:
138 except IOError as err:
139 if err.errno != errno.ENOENT:
139 if err.errno != errno.ENOENT:
140 raise
140 raise
141 return [nullid, nullid]
141 return [nullid, nullid]
142
142
143 @propertycache
143 @propertycache
144 def _dirs(self):
144 def _dirs(self):
145 return util.dirs(self._map, 'r')
145 return util.dirs(self._map, 'r')
146
146
147 def dirs(self):
147 def dirs(self):
148 return self._dirs
148 return self._dirs
149
149
150 @rootcache('.hgignore')
150 @rootcache('.hgignore')
151 def _ignore(self):
151 def _ignore(self):
152 files = []
152 files = []
153 if os.path.exists(self._join('.hgignore')):
153 if os.path.exists(self._join('.hgignore')):
154 files.append(self._join('.hgignore'))
154 files.append(self._join('.hgignore'))
155 for name, path in self._ui.configitems("ui"):
155 for name, path in self._ui.configitems("ui"):
156 if name == 'ignore' or name.startswith('ignore.'):
156 if name == 'ignore' or name.startswith('ignore.'):
157 # we need to use os.path.join here rather than self._join
157 # we need to use os.path.join here rather than self._join
158 # because path is arbitrary and user-specified
158 # because path is arbitrary and user-specified
159 files.append(os.path.join(self._rootdir, util.expandpath(path)))
159 files.append(os.path.join(self._rootdir, util.expandpath(path)))
160
160
161 if not files:
161 if not files:
162 return util.never
162 return util.never
163
163
164 pats = ['include:%s' % f for f in files]
164 pats = ['include:%s' % f for f in files]
165 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
165 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
166
166
167 @propertycache
167 @propertycache
168 def _slash(self):
168 def _slash(self):
169 return self._ui.configbool('ui', 'slash') and os.sep != '/'
169 return self._ui.configbool('ui', 'slash') and os.sep != '/'
170
170
171 @propertycache
171 @propertycache
172 def _checklink(self):
172 def _checklink(self):
173 return util.checklink(self._root)
173 return util.checklink(self._root)
174
174
175 @propertycache
175 @propertycache
176 def _checkexec(self):
176 def _checkexec(self):
177 return util.checkexec(self._root)
177 return util.checkexec(self._root)
178
178
179 @propertycache
179 @propertycache
180 def _checkcase(self):
180 def _checkcase(self):
181 return not util.checkcase(self._join('.hg'))
181 return not util.checkcase(self._join('.hg'))
182
182
183 def _join(self, f):
183 def _join(self, f):
184 # much faster than os.path.join()
184 # much faster than os.path.join()
185 # it's safe because f is always a relative path
185 # it's safe because f is always a relative path
186 return self._rootdir + f
186 return self._rootdir + f
187
187
188 def flagfunc(self, buildfallback):
188 def flagfunc(self, buildfallback):
189 if self._checklink and self._checkexec:
189 if self._checklink and self._checkexec:
190 def f(x):
190 def f(x):
191 try:
191 try:
192 st = os.lstat(self._join(x))
192 st = os.lstat(self._join(x))
193 if util.statislink(st):
193 if util.statislink(st):
194 return 'l'
194 return 'l'
195 if util.statisexec(st):
195 if util.statisexec(st):
196 return 'x'
196 return 'x'
197 except OSError:
197 except OSError:
198 pass
198 pass
199 return ''
199 return ''
200 return f
200 return f
201
201
202 fallback = buildfallback()
202 fallback = buildfallback()
203 if self._checklink:
203 if self._checklink:
204 def f(x):
204 def f(x):
205 if os.path.islink(self._join(x)):
205 if os.path.islink(self._join(x)):
206 return 'l'
206 return 'l'
207 if 'x' in fallback(x):
207 if 'x' in fallback(x):
208 return 'x'
208 return 'x'
209 return ''
209 return ''
210 return f
210 return f
211 if self._checkexec:
211 if self._checkexec:
212 def f(x):
212 def f(x):
213 if 'l' in fallback(x):
213 if 'l' in fallback(x):
214 return 'l'
214 return 'l'
215 if util.isexec(self._join(x)):
215 if util.isexec(self._join(x)):
216 return 'x'
216 return 'x'
217 return ''
217 return ''
218 return f
218 return f
219 else:
219 else:
220 return fallback
220 return fallback
221
221
222 @propertycache
222 @propertycache
223 def _cwd(self):
223 def _cwd(self):
224 return os.getcwd()
224 return os.getcwd()
225
225
226 def getcwd(self):
226 def getcwd(self):
227 '''Return the path from which a canonical path is calculated.
227 '''Return the path from which a canonical path is calculated.
228
228
229 This path should be used to resolve file patterns or to convert
229 This path should be used to resolve file patterns or to convert
230 canonical paths back to file paths for display. It shouldn't be
230 canonical paths back to file paths for display. It shouldn't be
231 used to get real file paths. Use vfs functions instead.
231 used to get real file paths. Use vfs functions instead.
232 '''
232 '''
233 cwd = self._cwd
233 cwd = self._cwd
234 if cwd == self._root:
234 if cwd == self._root:
235 return ''
235 return ''
236 # self._root ends with a path separator if self._root is '/' or 'C:\'
236 # self._root ends with a path separator if self._root is '/' or 'C:\'
237 rootsep = self._root
237 rootsep = self._root
238 if not util.endswithsep(rootsep):
238 if not util.endswithsep(rootsep):
239 rootsep += os.sep
239 rootsep += os.sep
240 if cwd.startswith(rootsep):
240 if cwd.startswith(rootsep):
241 return cwd[len(rootsep):]
241 return cwd[len(rootsep):]
242 else:
242 else:
243 # we're outside the repo. return an absolute path.
243 # we're outside the repo. return an absolute path.
244 return cwd
244 return cwd
245
245
246 def pathto(self, f, cwd=None):
246 def pathto(self, f, cwd=None):
247 if cwd is None:
247 if cwd is None:
248 cwd = self.getcwd()
248 cwd = self.getcwd()
249 path = util.pathto(self._root, cwd, f)
249 path = util.pathto(self._root, cwd, f)
250 if self._slash:
250 if self._slash:
251 return util.pconvert(path)
251 return util.pconvert(path)
252 return path
252 return path
253
253
254 def __getitem__(self, key):
254 def __getitem__(self, key):
255 '''Return the current state of key (a filename) in the dirstate.
255 '''Return the current state of key (a filename) in the dirstate.
256
256
257 States are:
257 States are:
258 n normal
258 n normal
259 m needs merging
259 m needs merging
260 r marked for removal
260 r marked for removal
261 a marked for addition
261 a marked for addition
262 ? not tracked
262 ? not tracked
263 '''
263 '''
264 return self._map.get(key, ("?",))[0]
264 return self._map.get(key, ("?",))[0]
265
265
266 def __contains__(self, key):
266 def __contains__(self, key):
267 return key in self._map
267 return key in self._map
268
268
269 def __iter__(self):
269 def __iter__(self):
270 for x in sorted(self._map):
270 for x in sorted(self._map):
271 yield x
271 yield x
272
272
273 def iteritems(self):
273 def iteritems(self):
274 return self._map.iteritems()
274 return self._map.iteritems()
275
275
276 def parents(self):
276 def parents(self):
277 return [self._validate(p) for p in self._pl]
277 return [self._validate(p) for p in self._pl]
278
278
279 def p1(self):
279 def p1(self):
280 return self._validate(self._pl[0])
280 return self._validate(self._pl[0])
281
281
282 def p2(self):
282 def p2(self):
283 return self._validate(self._pl[1])
283 return self._validate(self._pl[1])
284
284
285 def branch(self):
285 def branch(self):
286 return encoding.tolocal(self._branch)
286 return encoding.tolocal(self._branch)
287
287
288 def setparents(self, p1, p2=nullid):
288 def setparents(self, p1, p2=nullid):
289 """Set dirstate parents to p1 and p2.
289 """Set dirstate parents to p1 and p2.
290
290
291 When moving from two parents to one, 'm' merged entries a
291 When moving from two parents to one, 'm' merged entries a
292 adjusted to normal and previous copy records discarded and
292 adjusted to normal and previous copy records discarded and
293 returned by the call.
293 returned by the call.
294
294
295 See localrepo.setparents()
295 See localrepo.setparents()
296 """
296 """
297 if self._parentwriters == 0:
297 if self._parentwriters == 0:
298 raise ValueError("cannot set dirstate parent without "
298 raise ValueError("cannot set dirstate parent without "
299 "calling dirstate.beginparentchange")
299 "calling dirstate.beginparentchange")
300
300
301 self._dirty = self._dirtypl = True
301 self._dirty = self._dirtypl = True
302 oldp2 = self._pl[1]
302 oldp2 = self._pl[1]
303 self._pl = p1, p2
303 self._pl = p1, p2
304 copies = {}
304 copies = {}
305 if oldp2 != nullid and p2 == nullid:
305 if oldp2 != nullid and p2 == nullid:
306 for f, s in self._map.iteritems():
306 for f, s in self._map.iteritems():
307 # Discard 'm' markers when moving away from a merge state
307 # Discard 'm' markers when moving away from a merge state
308 if s[0] == 'm':
308 if s[0] == 'm':
309 if f in self._copymap:
309 if f in self._copymap:
310 copies[f] = self._copymap[f]
310 copies[f] = self._copymap[f]
311 self.normallookup(f)
311 self.normallookup(f)
312 # Also fix up otherparent markers
312 # Also fix up otherparent markers
313 elif s[0] == 'n' and s[2] == -2:
313 elif s[0] == 'n' and s[2] == -2:
314 if f in self._copymap:
314 if f in self._copymap:
315 copies[f] = self._copymap[f]
315 copies[f] = self._copymap[f]
316 self.add(f)
316 self.add(f)
317 return copies
317 return copies
318
318
319 def setbranch(self, branch):
319 def setbranch(self, branch):
320 self._branch = encoding.fromlocal(branch)
320 self._branch = encoding.fromlocal(branch)
321 f = self._opener('branch', 'w', atomictemp=True)
321 f = self._opener('branch', 'w', atomictemp=True)
322 try:
322 try:
323 f.write(self._branch + '\n')
323 f.write(self._branch + '\n')
324 f.close()
324 f.close()
325
325
326 # make sure filecache has the correct stat info for _branch after
326 # make sure filecache has the correct stat info for _branch after
327 # replacing the underlying file
327 # replacing the underlying file
328 ce = self._filecache['_branch']
328 ce = self._filecache['_branch']
329 if ce:
329 if ce:
330 ce.refresh()
330 ce.refresh()
331 except: # re-raises
331 except: # re-raises
332 f.discard()
332 f.discard()
333 raise
333 raise
334
334
335 def _read(self):
335 def _read(self):
336 self._map = {}
336 self._map = {}
337 self._copymap = {}
337 self._copymap = {}
338 try:
338 try:
339 fp = self._opener.open(self._filename)
339 fp = self._opener.open(self._filename)
340 try:
340 try:
341 st = fp.read()
341 st = fp.read()
342 finally:
342 finally:
343 fp.close()
343 fp.close()
344 except IOError as err:
344 except IOError as err:
345 if err.errno != errno.ENOENT:
345 if err.errno != errno.ENOENT:
346 raise
346 raise
347 return
347 return
348 if not st:
348 if not st:
349 return
349 return
350
350
351 if util.safehasattr(parsers, 'dict_new_presized'):
351 if util.safehasattr(parsers, 'dict_new_presized'):
352 # Make an estimate of the number of files in the dirstate based on
352 # Make an estimate of the number of files in the dirstate based on
353 # its size. From a linear regression on a set of real-world repos,
353 # its size. From a linear regression on a set of real-world repos,
354 # all over 10,000 files, the size of a dirstate entry is 85
354 # all over 10,000 files, the size of a dirstate entry is 85
355 # bytes. The cost of resizing is significantly higher than the cost
355 # bytes. The cost of resizing is significantly higher than the cost
356 # of filling in a larger presized dict, so subtract 20% from the
356 # of filling in a larger presized dict, so subtract 20% from the
357 # size.
357 # size.
358 #
358 #
359 # This heuristic is imperfect in many ways, so in a future dirstate
359 # This heuristic is imperfect in many ways, so in a future dirstate
360 # format update it makes sense to just record the number of entries
360 # format update it makes sense to just record the number of entries
361 # on write.
361 # on write.
362 self._map = parsers.dict_new_presized(len(st) / 71)
362 self._map = parsers.dict_new_presized(len(st) / 71)
363
363
364 # Python's garbage collector triggers a GC each time a certain number
364 # Python's garbage collector triggers a GC each time a certain number
365 # of container objects (the number being defined by
365 # of container objects (the number being defined by
366 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
366 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
367 # for each file in the dirstate. The C version then immediately marks
367 # for each file in the dirstate. The C version then immediately marks
368 # them as not to be tracked by the collector. However, this has no
368 # them as not to be tracked by the collector. However, this has no
369 # effect on when GCs are triggered, only on what objects the GC looks
369 # effect on when GCs are triggered, only on what objects the GC looks
370 # into. This means that O(number of files) GCs are unavoidable.
370 # into. This means that O(number of files) GCs are unavoidable.
371 # Depending on when in the process's lifetime the dirstate is parsed,
371 # Depending on when in the process's lifetime the dirstate is parsed,
372 # this can get very expensive. As a workaround, disable GC while
372 # this can get very expensive. As a workaround, disable GC while
373 # parsing the dirstate.
373 # parsing the dirstate.
374 #
374 #
375 # (we cannot decorate the function directly since it is in a C module)
375 # (we cannot decorate the function directly since it is in a C module)
376 parse_dirstate = util.nogc(parsers.parse_dirstate)
376 parse_dirstate = util.nogc(parsers.parse_dirstate)
377 p = parse_dirstate(self._map, self._copymap, st)
377 p = parse_dirstate(self._map, self._copymap, st)
378 if not self._dirtypl:
378 if not self._dirtypl:
379 self._pl = p
379 self._pl = p
380
380
381 def invalidate(self):
381 def invalidate(self):
382 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
382 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
383 "_pl", "_dirs", "_ignore"):
383 "_pl", "_dirs", "_ignore"):
384 if a in self.__dict__:
384 if a in self.__dict__:
385 delattr(self, a)
385 delattr(self, a)
386 self._lastnormaltime = 0
386 self._lastnormaltime = 0
387 self._dirty = False
387 self._dirty = False
388 self._parentwriters = 0
388 self._parentwriters = 0
389
389
390 def copy(self, source, dest):
390 def copy(self, source, dest):
391 """Mark dest as a copy of source. Unmark dest if source is None."""
391 """Mark dest as a copy of source. Unmark dest if source is None."""
392 if source == dest:
392 if source == dest:
393 return
393 return
394 self._dirty = True
394 self._dirty = True
395 if source is not None:
395 if source is not None:
396 self._copymap[dest] = source
396 self._copymap[dest] = source
397 elif dest in self._copymap:
397 elif dest in self._copymap:
398 del self._copymap[dest]
398 del self._copymap[dest]
399
399
400 def copied(self, file):
400 def copied(self, file):
401 return self._copymap.get(file, None)
401 return self._copymap.get(file, None)
402
402
403 def copies(self):
403 def copies(self):
404 return self._copymap
404 return self._copymap
405
405
406 def _droppath(self, f):
406 def _droppath(self, f):
407 if self[f] not in "?r" and "_dirs" in self.__dict__:
407 if self[f] not in "?r" and "_dirs" in self.__dict__:
408 self._dirs.delpath(f)
408 self._dirs.delpath(f)
409
409
410 def _addpath(self, f, state, mode, size, mtime):
410 def _addpath(self, f, state, mode, size, mtime):
411 oldstate = self[f]
411 oldstate = self[f]
412 if state == 'a' or oldstate == 'r':
412 if state == 'a' or oldstate == 'r':
413 scmutil.checkfilename(f)
413 scmutil.checkfilename(f)
414 if f in self._dirs:
414 if f in self._dirs:
415 raise util.Abort(_('directory %r already in dirstate') % f)
415 raise util.Abort(_('directory %r already in dirstate') % f)
416 # shadows
416 # shadows
417 for d in util.finddirs(f):
417 for d in util.finddirs(f):
418 if d in self._dirs:
418 if d in self._dirs:
419 break
419 break
420 if d in self._map and self[d] != 'r':
420 if d in self._map and self[d] != 'r':
421 raise util.Abort(
421 raise util.Abort(
422 _('file %r in dirstate clashes with %r') % (d, f))
422 _('file %r in dirstate clashes with %r') % (d, f))
423 if oldstate in "?r" and "_dirs" in self.__dict__:
423 if oldstate in "?r" and "_dirs" in self.__dict__:
424 self._dirs.addpath(f)
424 self._dirs.addpath(f)
425 self._dirty = True
425 self._dirty = True
426 self._map[f] = dirstatetuple(state, mode, size, mtime)
426 self._map[f] = dirstatetuple(state, mode, size, mtime)
427
427
428 def normal(self, f):
428 def normal(self, f):
429 '''Mark a file normal and clean.'''
429 '''Mark a file normal and clean.'''
430 s = os.lstat(self._join(f))
430 s = os.lstat(self._join(f))
431 mtime = int(s.st_mtime)
431 mtime = util.statmtimesec(s)
432 self._addpath(f, 'n', s.st_mode,
432 self._addpath(f, 'n', s.st_mode,
433 s.st_size & _rangemask, mtime & _rangemask)
433 s.st_size & _rangemask, mtime & _rangemask)
434 if f in self._copymap:
434 if f in self._copymap:
435 del self._copymap[f]
435 del self._copymap[f]
436 if mtime > self._lastnormaltime:
436 if mtime > self._lastnormaltime:
437 # Remember the most recent modification timeslot for status(),
437 # Remember the most recent modification timeslot for status(),
438 # to make sure we won't miss future size-preserving file content
438 # to make sure we won't miss future size-preserving file content
439 # modifications that happen within the same timeslot.
439 # modifications that happen within the same timeslot.
440 self._lastnormaltime = mtime
440 self._lastnormaltime = mtime
441
441
442 def normallookup(self, f):
442 def normallookup(self, f):
443 '''Mark a file normal, but possibly dirty.'''
443 '''Mark a file normal, but possibly dirty.'''
444 if self._pl[1] != nullid and f in self._map:
444 if self._pl[1] != nullid and f in self._map:
445 # if there is a merge going on and the file was either
445 # if there is a merge going on and the file was either
446 # in state 'm' (-1) or coming from other parent (-2) before
446 # in state 'm' (-1) or coming from other parent (-2) before
447 # being removed, restore that state.
447 # being removed, restore that state.
448 entry = self._map[f]
448 entry = self._map[f]
449 if entry[0] == 'r' and entry[2] in (-1, -2):
449 if entry[0] == 'r' and entry[2] in (-1, -2):
450 source = self._copymap.get(f)
450 source = self._copymap.get(f)
451 if entry[2] == -1:
451 if entry[2] == -1:
452 self.merge(f)
452 self.merge(f)
453 elif entry[2] == -2:
453 elif entry[2] == -2:
454 self.otherparent(f)
454 self.otherparent(f)
455 if source:
455 if source:
456 self.copy(source, f)
456 self.copy(source, f)
457 return
457 return
458 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
458 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
459 return
459 return
460 self._addpath(f, 'n', 0, -1, -1)
460 self._addpath(f, 'n', 0, -1, -1)
461 if f in self._copymap:
461 if f in self._copymap:
462 del self._copymap[f]
462 del self._copymap[f]
463
463
464 def otherparent(self, f):
464 def otherparent(self, f):
465 '''Mark as coming from the other parent, always dirty.'''
465 '''Mark as coming from the other parent, always dirty.'''
466 if self._pl[1] == nullid:
466 if self._pl[1] == nullid:
467 raise util.Abort(_("setting %r to other parent "
467 raise util.Abort(_("setting %r to other parent "
468 "only allowed in merges") % f)
468 "only allowed in merges") % f)
469 if f in self and self[f] == 'n':
469 if f in self and self[f] == 'n':
470 # merge-like
470 # merge-like
471 self._addpath(f, 'm', 0, -2, -1)
471 self._addpath(f, 'm', 0, -2, -1)
472 else:
472 else:
473 # add-like
473 # add-like
474 self._addpath(f, 'n', 0, -2, -1)
474 self._addpath(f, 'n', 0, -2, -1)
475
475
476 if f in self._copymap:
476 if f in self._copymap:
477 del self._copymap[f]
477 del self._copymap[f]
478
478
479 def add(self, f):
479 def add(self, f):
480 '''Mark a file added.'''
480 '''Mark a file added.'''
481 self._addpath(f, 'a', 0, -1, -1)
481 self._addpath(f, 'a', 0, -1, -1)
482 if f in self._copymap:
482 if f in self._copymap:
483 del self._copymap[f]
483 del self._copymap[f]
484
484
485 def remove(self, f):
485 def remove(self, f):
486 '''Mark a file removed.'''
486 '''Mark a file removed.'''
487 self._dirty = True
487 self._dirty = True
488 self._droppath(f)
488 self._droppath(f)
489 size = 0
489 size = 0
490 if self._pl[1] != nullid and f in self._map:
490 if self._pl[1] != nullid and f in self._map:
491 # backup the previous state
491 # backup the previous state
492 entry = self._map[f]
492 entry = self._map[f]
493 if entry[0] == 'm': # merge
493 if entry[0] == 'm': # merge
494 size = -1
494 size = -1
495 elif entry[0] == 'n' and entry[2] == -2: # other parent
495 elif entry[0] == 'n' and entry[2] == -2: # other parent
496 size = -2
496 size = -2
497 self._map[f] = dirstatetuple('r', 0, size, 0)
497 self._map[f] = dirstatetuple('r', 0, size, 0)
498 if size == 0 and f in self._copymap:
498 if size == 0 and f in self._copymap:
499 del self._copymap[f]
499 del self._copymap[f]
500
500
501 def merge(self, f):
501 def merge(self, f):
502 '''Mark a file merged.'''
502 '''Mark a file merged.'''
503 if self._pl[1] == nullid:
503 if self._pl[1] == nullid:
504 return self.normallookup(f)
504 return self.normallookup(f)
505 return self.otherparent(f)
505 return self.otherparent(f)
506
506
507 def drop(self, f):
507 def drop(self, f):
508 '''Drop a file from the dirstate'''
508 '''Drop a file from the dirstate'''
509 if f in self._map:
509 if f in self._map:
510 self._dirty = True
510 self._dirty = True
511 self._droppath(f)
511 self._droppath(f)
512 del self._map[f]
512 del self._map[f]
513
513
514 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
514 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
515 if exists is None:
515 if exists is None:
516 exists = os.path.lexists(os.path.join(self._root, path))
516 exists = os.path.lexists(os.path.join(self._root, path))
517 if not exists:
517 if not exists:
518 # Maybe a path component exists
518 # Maybe a path component exists
519 if not ignoremissing and '/' in path:
519 if not ignoremissing and '/' in path:
520 d, f = path.rsplit('/', 1)
520 d, f = path.rsplit('/', 1)
521 d = self._normalize(d, False, ignoremissing, None)
521 d = self._normalize(d, False, ignoremissing, None)
522 folded = d + "/" + f
522 folded = d + "/" + f
523 else:
523 else:
524 # No path components, preserve original case
524 # No path components, preserve original case
525 folded = path
525 folded = path
526 else:
526 else:
527 # recursively normalize leading directory components
527 # recursively normalize leading directory components
528 # against dirstate
528 # against dirstate
529 if '/' in normed:
529 if '/' in normed:
530 d, f = normed.rsplit('/', 1)
530 d, f = normed.rsplit('/', 1)
531 d = self._normalize(d, False, ignoremissing, True)
531 d = self._normalize(d, False, ignoremissing, True)
532 r = self._root + "/" + d
532 r = self._root + "/" + d
533 folded = d + "/" + util.fspath(f, r)
533 folded = d + "/" + util.fspath(f, r)
534 else:
534 else:
535 folded = util.fspath(normed, self._root)
535 folded = util.fspath(normed, self._root)
536 storemap[normed] = folded
536 storemap[normed] = folded
537
537
538 return folded
538 return folded
539
539
540 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
540 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
541 normed = util.normcase(path)
541 normed = util.normcase(path)
542 folded = self._filefoldmap.get(normed, None)
542 folded = self._filefoldmap.get(normed, None)
543 if folded is None:
543 if folded is None:
544 if isknown:
544 if isknown:
545 folded = path
545 folded = path
546 else:
546 else:
547 folded = self._discoverpath(path, normed, ignoremissing, exists,
547 folded = self._discoverpath(path, normed, ignoremissing, exists,
548 self._filefoldmap)
548 self._filefoldmap)
549 return folded
549 return folded
550
550
551 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
551 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
552 normed = util.normcase(path)
552 normed = util.normcase(path)
553 folded = self._filefoldmap.get(normed, None)
553 folded = self._filefoldmap.get(normed, None)
554 if folded is None:
554 if folded is None:
555 folded = self._dirfoldmap.get(normed, None)
555 folded = self._dirfoldmap.get(normed, None)
556 if folded is None:
556 if folded is None:
557 if isknown:
557 if isknown:
558 folded = path
558 folded = path
559 else:
559 else:
560 # store discovered result in dirfoldmap so that future
560 # store discovered result in dirfoldmap so that future
561 # normalizefile calls don't start matching directories
561 # normalizefile calls don't start matching directories
562 folded = self._discoverpath(path, normed, ignoremissing, exists,
562 folded = self._discoverpath(path, normed, ignoremissing, exists,
563 self._dirfoldmap)
563 self._dirfoldmap)
564 return folded
564 return folded
565
565
566 def normalize(self, path, isknown=False, ignoremissing=False):
566 def normalize(self, path, isknown=False, ignoremissing=False):
567 '''
567 '''
568 normalize the case of a pathname when on a casefolding filesystem
568 normalize the case of a pathname when on a casefolding filesystem
569
569
570 isknown specifies whether the filename came from walking the
570 isknown specifies whether the filename came from walking the
571 disk, to avoid extra filesystem access.
571 disk, to avoid extra filesystem access.
572
572
573 If ignoremissing is True, missing path are returned
573 If ignoremissing is True, missing path are returned
574 unchanged. Otherwise, we try harder to normalize possibly
574 unchanged. Otherwise, we try harder to normalize possibly
575 existing path components.
575 existing path components.
576
576
577 The normalized case is determined based on the following precedence:
577 The normalized case is determined based on the following precedence:
578
578
579 - version of name already stored in the dirstate
579 - version of name already stored in the dirstate
580 - version of name stored on disk
580 - version of name stored on disk
581 - version provided via command arguments
581 - version provided via command arguments
582 '''
582 '''
583
583
584 if self._checkcase:
584 if self._checkcase:
585 return self._normalize(path, isknown, ignoremissing)
585 return self._normalize(path, isknown, ignoremissing)
586 return path
586 return path
587
587
588 def clear(self):
588 def clear(self):
589 self._map = {}
589 self._map = {}
590 if "_dirs" in self.__dict__:
590 if "_dirs" in self.__dict__:
591 delattr(self, "_dirs")
591 delattr(self, "_dirs")
592 self._copymap = {}
592 self._copymap = {}
593 self._pl = [nullid, nullid]
593 self._pl = [nullid, nullid]
594 self._lastnormaltime = 0
594 self._lastnormaltime = 0
595 self._dirty = True
595 self._dirty = True
596
596
597 def rebuild(self, parent, allfiles, changedfiles=None):
597 def rebuild(self, parent, allfiles, changedfiles=None):
598 if changedfiles is None:
598 if changedfiles is None:
599 changedfiles = allfiles
599 changedfiles = allfiles
600 oldmap = self._map
600 oldmap = self._map
601 self.clear()
601 self.clear()
602 for f in allfiles:
602 for f in allfiles:
603 if f not in changedfiles:
603 if f not in changedfiles:
604 self._map[f] = oldmap[f]
604 self._map[f] = oldmap[f]
605 else:
605 else:
606 if 'x' in allfiles.flags(f):
606 if 'x' in allfiles.flags(f):
607 self._map[f] = dirstatetuple('n', 0o777, -1, 0)
607 self._map[f] = dirstatetuple('n', 0o777, -1, 0)
608 else:
608 else:
609 self._map[f] = dirstatetuple('n', 0o666, -1, 0)
609 self._map[f] = dirstatetuple('n', 0o666, -1, 0)
610 self._pl = (parent, nullid)
610 self._pl = (parent, nullid)
611 self._dirty = True
611 self._dirty = True
612
612
613 def write(self):
613 def write(self):
614 if not self._dirty:
614 if not self._dirty:
615 return
615 return
616
616
617 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
617 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
618 # timestamp of each entries in dirstate, because of 'now > mtime'
618 # timestamp of each entries in dirstate, because of 'now > mtime'
619 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
619 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
620 if delaywrite > 0:
620 if delaywrite > 0:
621 import time # to avoid useless import
621 import time # to avoid useless import
622 time.sleep(delaywrite)
622 time.sleep(delaywrite)
623
623
624 st = self._opener(self._filename, "w", atomictemp=True)
624 st = self._opener(self._filename, "w", atomictemp=True)
625 # use the modification time of the newly created temporary file as the
625 # use the modification time of the newly created temporary file as the
626 # filesystem's notion of 'now'
626 # filesystem's notion of 'now'
627 now = util.fstat(st).st_mtime
627 now = util.fstat(st).st_mtime
628 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
628 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
629 st.close()
629 st.close()
630 self._lastnormaltime = 0
630 self._lastnormaltime = 0
631 self._dirty = self._dirtypl = False
631 self._dirty = self._dirtypl = False
632
632
633 def _dirignore(self, f):
633 def _dirignore(self, f):
634 if f == '.':
634 if f == '.':
635 return False
635 return False
636 if self._ignore(f):
636 if self._ignore(f):
637 return True
637 return True
638 for p in util.finddirs(f):
638 for p in util.finddirs(f):
639 if self._ignore(p):
639 if self._ignore(p):
640 return True
640 return True
641 return False
641 return False
642
642
643 def _walkexplicit(self, match, subrepos):
643 def _walkexplicit(self, match, subrepos):
644 '''Get stat data about the files explicitly specified by match.
644 '''Get stat data about the files explicitly specified by match.
645
645
646 Return a triple (results, dirsfound, dirsnotfound).
646 Return a triple (results, dirsfound, dirsnotfound).
647 - results is a mapping from filename to stat result. It also contains
647 - results is a mapping from filename to stat result. It also contains
648 listings mapping subrepos and .hg to None.
648 listings mapping subrepos and .hg to None.
649 - dirsfound is a list of files found to be directories.
649 - dirsfound is a list of files found to be directories.
650 - dirsnotfound is a list of files that the dirstate thinks are
650 - dirsnotfound is a list of files that the dirstate thinks are
651 directories and that were not found.'''
651 directories and that were not found.'''
652
652
653 def badtype(mode):
653 def badtype(mode):
654 kind = _('unknown')
654 kind = _('unknown')
655 if stat.S_ISCHR(mode):
655 if stat.S_ISCHR(mode):
656 kind = _('character device')
656 kind = _('character device')
657 elif stat.S_ISBLK(mode):
657 elif stat.S_ISBLK(mode):
658 kind = _('block device')
658 kind = _('block device')
659 elif stat.S_ISFIFO(mode):
659 elif stat.S_ISFIFO(mode):
660 kind = _('fifo')
660 kind = _('fifo')
661 elif stat.S_ISSOCK(mode):
661 elif stat.S_ISSOCK(mode):
662 kind = _('socket')
662 kind = _('socket')
663 elif stat.S_ISDIR(mode):
663 elif stat.S_ISDIR(mode):
664 kind = _('directory')
664 kind = _('directory')
665 return _('unsupported file type (type is %s)') % kind
665 return _('unsupported file type (type is %s)') % kind
666
666
667 matchedir = match.explicitdir
667 matchedir = match.explicitdir
668 badfn = match.bad
668 badfn = match.bad
669 dmap = self._map
669 dmap = self._map
670 lstat = os.lstat
670 lstat = os.lstat
671 getkind = stat.S_IFMT
671 getkind = stat.S_IFMT
672 dirkind = stat.S_IFDIR
672 dirkind = stat.S_IFDIR
673 regkind = stat.S_IFREG
673 regkind = stat.S_IFREG
674 lnkkind = stat.S_IFLNK
674 lnkkind = stat.S_IFLNK
675 join = self._join
675 join = self._join
676 dirsfound = []
676 dirsfound = []
677 foundadd = dirsfound.append
677 foundadd = dirsfound.append
678 dirsnotfound = []
678 dirsnotfound = []
679 notfoundadd = dirsnotfound.append
679 notfoundadd = dirsnotfound.append
680
680
681 if not match.isexact() and self._checkcase:
681 if not match.isexact() and self._checkcase:
682 normalize = self._normalize
682 normalize = self._normalize
683 else:
683 else:
684 normalize = None
684 normalize = None
685
685
686 files = sorted(match.files())
686 files = sorted(match.files())
687 subrepos.sort()
687 subrepos.sort()
688 i, j = 0, 0
688 i, j = 0, 0
689 while i < len(files) and j < len(subrepos):
689 while i < len(files) and j < len(subrepos):
690 subpath = subrepos[j] + "/"
690 subpath = subrepos[j] + "/"
691 if files[i] < subpath:
691 if files[i] < subpath:
692 i += 1
692 i += 1
693 continue
693 continue
694 while i < len(files) and files[i].startswith(subpath):
694 while i < len(files) and files[i].startswith(subpath):
695 del files[i]
695 del files[i]
696 j += 1
696 j += 1
697
697
698 if not files or '.' in files:
698 if not files or '.' in files:
699 files = ['.']
699 files = ['.']
700 results = dict.fromkeys(subrepos)
700 results = dict.fromkeys(subrepos)
701 results['.hg'] = None
701 results['.hg'] = None
702
702
703 alldirs = None
703 alldirs = None
704 for ff in files:
704 for ff in files:
705 # constructing the foldmap is expensive, so don't do it for the
705 # constructing the foldmap is expensive, so don't do it for the
706 # common case where files is ['.']
706 # common case where files is ['.']
707 if normalize and ff != '.':
707 if normalize and ff != '.':
708 nf = normalize(ff, False, True)
708 nf = normalize(ff, False, True)
709 else:
709 else:
710 nf = ff
710 nf = ff
711 if nf in results:
711 if nf in results:
712 continue
712 continue
713
713
714 try:
714 try:
715 st = lstat(join(nf))
715 st = lstat(join(nf))
716 kind = getkind(st.st_mode)
716 kind = getkind(st.st_mode)
717 if kind == dirkind:
717 if kind == dirkind:
718 if nf in dmap:
718 if nf in dmap:
719 # file replaced by dir on disk but still in dirstate
719 # file replaced by dir on disk but still in dirstate
720 results[nf] = None
720 results[nf] = None
721 if matchedir:
721 if matchedir:
722 matchedir(nf)
722 matchedir(nf)
723 foundadd((nf, ff))
723 foundadd((nf, ff))
724 elif kind == regkind or kind == lnkkind:
724 elif kind == regkind or kind == lnkkind:
725 results[nf] = st
725 results[nf] = st
726 else:
726 else:
727 badfn(ff, badtype(kind))
727 badfn(ff, badtype(kind))
728 if nf in dmap:
728 if nf in dmap:
729 results[nf] = None
729 results[nf] = None
730 except OSError as inst: # nf not found on disk - it is dirstate only
730 except OSError as inst: # nf not found on disk - it is dirstate only
731 if nf in dmap: # does it exactly match a missing file?
731 if nf in dmap: # does it exactly match a missing file?
732 results[nf] = None
732 results[nf] = None
733 else: # does it match a missing directory?
733 else: # does it match a missing directory?
734 if alldirs is None:
734 if alldirs is None:
735 alldirs = util.dirs(dmap)
735 alldirs = util.dirs(dmap)
736 if nf in alldirs:
736 if nf in alldirs:
737 if matchedir:
737 if matchedir:
738 matchedir(nf)
738 matchedir(nf)
739 notfoundadd(nf)
739 notfoundadd(nf)
740 else:
740 else:
741 badfn(ff, inst.strerror)
741 badfn(ff, inst.strerror)
742
742
743 # Case insensitive filesystems cannot rely on lstat() failing to detect
743 # Case insensitive filesystems cannot rely on lstat() failing to detect
744 # a case-only rename. Prune the stat object for any file that does not
744 # a case-only rename. Prune the stat object for any file that does not
745 # match the case in the filesystem, if there are multiple files that
745 # match the case in the filesystem, if there are multiple files that
746 # normalize to the same path.
746 # normalize to the same path.
747 if match.isexact() and self._checkcase:
747 if match.isexact() and self._checkcase:
748 normed = {}
748 normed = {}
749
749
750 for f, st in results.iteritems():
750 for f, st in results.iteritems():
751 if st is None:
751 if st is None:
752 continue
752 continue
753
753
754 nc = util.normcase(f)
754 nc = util.normcase(f)
755 paths = normed.get(nc)
755 paths = normed.get(nc)
756
756
757 if paths is None:
757 if paths is None:
758 paths = set()
758 paths = set()
759 normed[nc] = paths
759 normed[nc] = paths
760
760
761 paths.add(f)
761 paths.add(f)
762
762
763 for norm, paths in normed.iteritems():
763 for norm, paths in normed.iteritems():
764 if len(paths) > 1:
764 if len(paths) > 1:
765 for path in paths:
765 for path in paths:
766 folded = self._discoverpath(path, norm, True, None,
766 folded = self._discoverpath(path, norm, True, None,
767 self._dirfoldmap)
767 self._dirfoldmap)
768 if path != folded:
768 if path != folded:
769 results[path] = None
769 results[path] = None
770
770
771 return results, dirsfound, dirsnotfound
771 return results, dirsfound, dirsnotfound
772
772
773 def walk(self, match, subrepos, unknown, ignored, full=True):
773 def walk(self, match, subrepos, unknown, ignored, full=True):
774 '''
774 '''
775 Walk recursively through the directory tree, finding all files
775 Walk recursively through the directory tree, finding all files
776 matched by match.
776 matched by match.
777
777
778 If full is False, maybe skip some known-clean files.
778 If full is False, maybe skip some known-clean files.
779
779
780 Return a dict mapping filename to stat-like object (either
780 Return a dict mapping filename to stat-like object (either
781 mercurial.osutil.stat instance or return value of os.stat()).
781 mercurial.osutil.stat instance or return value of os.stat()).
782
782
783 '''
783 '''
784 # full is a flag that extensions that hook into walk can use -- this
784 # full is a flag that extensions that hook into walk can use -- this
785 # implementation doesn't use it at all. This satisfies the contract
785 # implementation doesn't use it at all. This satisfies the contract
786 # because we only guarantee a "maybe".
786 # because we only guarantee a "maybe".
787
787
788 if ignored:
788 if ignored:
789 ignore = util.never
789 ignore = util.never
790 dirignore = util.never
790 dirignore = util.never
791 elif unknown:
791 elif unknown:
792 ignore = self._ignore
792 ignore = self._ignore
793 dirignore = self._dirignore
793 dirignore = self._dirignore
794 else:
794 else:
795 # if not unknown and not ignored, drop dir recursion and step 2
795 # if not unknown and not ignored, drop dir recursion and step 2
796 ignore = util.always
796 ignore = util.always
797 dirignore = util.always
797 dirignore = util.always
798
798
799 matchfn = match.matchfn
799 matchfn = match.matchfn
800 matchalways = match.always()
800 matchalways = match.always()
801 matchtdir = match.traversedir
801 matchtdir = match.traversedir
802 dmap = self._map
802 dmap = self._map
803 listdir = osutil.listdir
803 listdir = osutil.listdir
804 lstat = os.lstat
804 lstat = os.lstat
805 dirkind = stat.S_IFDIR
805 dirkind = stat.S_IFDIR
806 regkind = stat.S_IFREG
806 regkind = stat.S_IFREG
807 lnkkind = stat.S_IFLNK
807 lnkkind = stat.S_IFLNK
808 join = self._join
808 join = self._join
809
809
810 exact = skipstep3 = False
810 exact = skipstep3 = False
811 if match.isexact(): # match.exact
811 if match.isexact(): # match.exact
812 exact = True
812 exact = True
813 dirignore = util.always # skip step 2
813 dirignore = util.always # skip step 2
814 elif match.prefix(): # match.match, no patterns
814 elif match.prefix(): # match.match, no patterns
815 skipstep3 = True
815 skipstep3 = True
816
816
817 if not exact and self._checkcase:
817 if not exact and self._checkcase:
818 normalize = self._normalize
818 normalize = self._normalize
819 normalizefile = self._normalizefile
819 normalizefile = self._normalizefile
820 skipstep3 = False
820 skipstep3 = False
821 else:
821 else:
822 normalize = self._normalize
822 normalize = self._normalize
823 normalizefile = None
823 normalizefile = None
824
824
825 # step 1: find all explicit files
825 # step 1: find all explicit files
826 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
826 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
827
827
828 skipstep3 = skipstep3 and not (work or dirsnotfound)
828 skipstep3 = skipstep3 and not (work or dirsnotfound)
829 work = [d for d in work if not dirignore(d[0])]
829 work = [d for d in work if not dirignore(d[0])]
830
830
831 # step 2: visit subdirectories
831 # step 2: visit subdirectories
832 def traverse(work, alreadynormed):
832 def traverse(work, alreadynormed):
833 wadd = work.append
833 wadd = work.append
834 while work:
834 while work:
835 nd = work.pop()
835 nd = work.pop()
836 skip = None
836 skip = None
837 if nd == '.':
837 if nd == '.':
838 nd = ''
838 nd = ''
839 else:
839 else:
840 skip = '.hg'
840 skip = '.hg'
841 try:
841 try:
842 entries = listdir(join(nd), stat=True, skip=skip)
842 entries = listdir(join(nd), stat=True, skip=skip)
843 except OSError as inst:
843 except OSError as inst:
844 if inst.errno in (errno.EACCES, errno.ENOENT):
844 if inst.errno in (errno.EACCES, errno.ENOENT):
845 match.bad(self.pathto(nd), inst.strerror)
845 match.bad(self.pathto(nd), inst.strerror)
846 continue
846 continue
847 raise
847 raise
848 for f, kind, st in entries:
848 for f, kind, st in entries:
849 if normalizefile:
849 if normalizefile:
850 # even though f might be a directory, we're only
850 # even though f might be a directory, we're only
851 # interested in comparing it to files currently in the
851 # interested in comparing it to files currently in the
852 # dmap -- therefore normalizefile is enough
852 # dmap -- therefore normalizefile is enough
853 nf = normalizefile(nd and (nd + "/" + f) or f, True,
853 nf = normalizefile(nd and (nd + "/" + f) or f, True,
854 True)
854 True)
855 else:
855 else:
856 nf = nd and (nd + "/" + f) or f
856 nf = nd and (nd + "/" + f) or f
857 if nf not in results:
857 if nf not in results:
858 if kind == dirkind:
858 if kind == dirkind:
859 if not ignore(nf):
859 if not ignore(nf):
860 if matchtdir:
860 if matchtdir:
861 matchtdir(nf)
861 matchtdir(nf)
862 wadd(nf)
862 wadd(nf)
863 if nf in dmap and (matchalways or matchfn(nf)):
863 if nf in dmap and (matchalways or matchfn(nf)):
864 results[nf] = None
864 results[nf] = None
865 elif kind == regkind or kind == lnkkind:
865 elif kind == regkind or kind == lnkkind:
866 if nf in dmap:
866 if nf in dmap:
867 if matchalways or matchfn(nf):
867 if matchalways or matchfn(nf):
868 results[nf] = st
868 results[nf] = st
869 elif ((matchalways or matchfn(nf))
869 elif ((matchalways or matchfn(nf))
870 and not ignore(nf)):
870 and not ignore(nf)):
871 # unknown file -- normalize if necessary
871 # unknown file -- normalize if necessary
872 if not alreadynormed:
872 if not alreadynormed:
873 nf = normalize(nf, False, True)
873 nf = normalize(nf, False, True)
874 results[nf] = st
874 results[nf] = st
875 elif nf in dmap and (matchalways or matchfn(nf)):
875 elif nf in dmap and (matchalways or matchfn(nf)):
876 results[nf] = None
876 results[nf] = None
877
877
878 for nd, d in work:
878 for nd, d in work:
879 # alreadynormed means that processwork doesn't have to do any
879 # alreadynormed means that processwork doesn't have to do any
880 # expensive directory normalization
880 # expensive directory normalization
881 alreadynormed = not normalize or nd == d
881 alreadynormed = not normalize or nd == d
882 traverse([d], alreadynormed)
882 traverse([d], alreadynormed)
883
883
884 for s in subrepos:
884 for s in subrepos:
885 del results[s]
885 del results[s]
886 del results['.hg']
886 del results['.hg']
887
887
888 # step 3: visit remaining files from dmap
888 # step 3: visit remaining files from dmap
889 if not skipstep3 and not exact:
889 if not skipstep3 and not exact:
890 # If a dmap file is not in results yet, it was either
890 # If a dmap file is not in results yet, it was either
891 # a) not matching matchfn b) ignored, c) missing, or d) under a
891 # a) not matching matchfn b) ignored, c) missing, or d) under a
892 # symlink directory.
892 # symlink directory.
893 if not results and matchalways:
893 if not results and matchalways:
894 visit = dmap.keys()
894 visit = dmap.keys()
895 else:
895 else:
896 visit = [f for f in dmap if f not in results and matchfn(f)]
896 visit = [f for f in dmap if f not in results and matchfn(f)]
897 visit.sort()
897 visit.sort()
898
898
899 if unknown:
899 if unknown:
900 # unknown == True means we walked all dirs under the roots
900 # unknown == True means we walked all dirs under the roots
901 # that wasn't ignored, and everything that matched was stat'ed
901 # that wasn't ignored, and everything that matched was stat'ed
902 # and is already in results.
902 # and is already in results.
903 # The rest must thus be ignored or under a symlink.
903 # The rest must thus be ignored or under a symlink.
904 audit_path = pathutil.pathauditor(self._root)
904 audit_path = pathutil.pathauditor(self._root)
905
905
906 for nf in iter(visit):
906 for nf in iter(visit):
907 # If a stat for the same file was already added with a
907 # If a stat for the same file was already added with a
908 # different case, don't add one for this, since that would
908 # different case, don't add one for this, since that would
909 # make it appear as if the file exists under both names
909 # make it appear as if the file exists under both names
910 # on disk.
910 # on disk.
911 if (normalizefile and
911 if (normalizefile and
912 normalizefile(nf, True, True) in results):
912 normalizefile(nf, True, True) in results):
913 results[nf] = None
913 results[nf] = None
914 # Report ignored items in the dmap as long as they are not
914 # Report ignored items in the dmap as long as they are not
915 # under a symlink directory.
915 # under a symlink directory.
916 elif audit_path.check(nf):
916 elif audit_path.check(nf):
917 try:
917 try:
918 results[nf] = lstat(join(nf))
918 results[nf] = lstat(join(nf))
919 # file was just ignored, no links, and exists
919 # file was just ignored, no links, and exists
920 except OSError:
920 except OSError:
921 # file doesn't exist
921 # file doesn't exist
922 results[nf] = None
922 results[nf] = None
923 else:
923 else:
924 # It's either missing or under a symlink directory
924 # It's either missing or under a symlink directory
925 # which we in this case report as missing
925 # which we in this case report as missing
926 results[nf] = None
926 results[nf] = None
927 else:
927 else:
928 # We may not have walked the full directory tree above,
928 # We may not have walked the full directory tree above,
929 # so stat and check everything we missed.
929 # so stat and check everything we missed.
930 nf = iter(visit).next
930 nf = iter(visit).next
931 for st in util.statfiles([join(i) for i in visit]):
931 for st in util.statfiles([join(i) for i in visit]):
932 results[nf()] = st
932 results[nf()] = st
933 return results
933 return results
934
934
935 def status(self, match, subrepos, ignored, clean, unknown):
935 def status(self, match, subrepos, ignored, clean, unknown):
936 '''Determine the status of the working copy relative to the
936 '''Determine the status of the working copy relative to the
937 dirstate and return a pair of (unsure, status), where status is of type
937 dirstate and return a pair of (unsure, status), where status is of type
938 scmutil.status and:
938 scmutil.status and:
939
939
940 unsure:
940 unsure:
941 files that might have been modified since the dirstate was
941 files that might have been modified since the dirstate was
942 written, but need to be read to be sure (size is the same
942 written, but need to be read to be sure (size is the same
943 but mtime differs)
943 but mtime differs)
944 status.modified:
944 status.modified:
945 files that have definitely been modified since the dirstate
945 files that have definitely been modified since the dirstate
946 was written (different size or mode)
946 was written (different size or mode)
947 status.clean:
947 status.clean:
948 files that have definitely not been modified since the
948 files that have definitely not been modified since the
949 dirstate was written
949 dirstate was written
950 '''
950 '''
951 listignored, listclean, listunknown = ignored, clean, unknown
951 listignored, listclean, listunknown = ignored, clean, unknown
952 lookup, modified, added, unknown, ignored = [], [], [], [], []
952 lookup, modified, added, unknown, ignored = [], [], [], [], []
953 removed, deleted, clean = [], [], []
953 removed, deleted, clean = [], [], []
954
954
955 dmap = self._map
955 dmap = self._map
956 ladd = lookup.append # aka "unsure"
956 ladd = lookup.append # aka "unsure"
957 madd = modified.append
957 madd = modified.append
958 aadd = added.append
958 aadd = added.append
959 uadd = unknown.append
959 uadd = unknown.append
960 iadd = ignored.append
960 iadd = ignored.append
961 radd = removed.append
961 radd = removed.append
962 dadd = deleted.append
962 dadd = deleted.append
963 cadd = clean.append
963 cadd = clean.append
964 mexact = match.exact
964 mexact = match.exact
965 dirignore = self._dirignore
965 dirignore = self._dirignore
966 checkexec = self._checkexec
966 checkexec = self._checkexec
967 copymap = self._copymap
967 copymap = self._copymap
968 lastnormaltime = self._lastnormaltime
968 lastnormaltime = self._lastnormaltime
969
969
970 # We need to do full walks when either
970 # We need to do full walks when either
971 # - we're listing all clean files, or
971 # - we're listing all clean files, or
972 # - match.traversedir does something, because match.traversedir should
972 # - match.traversedir does something, because match.traversedir should
973 # be called for every dir in the working dir
973 # be called for every dir in the working dir
974 full = listclean or match.traversedir is not None
974 full = listclean or match.traversedir is not None
975 for fn, st in self.walk(match, subrepos, listunknown, listignored,
975 for fn, st in self.walk(match, subrepos, listunknown, listignored,
976 full=full).iteritems():
976 full=full).iteritems():
977 if fn not in dmap:
977 if fn not in dmap:
978 if (listignored or mexact(fn)) and dirignore(fn):
978 if (listignored or mexact(fn)) and dirignore(fn):
979 if listignored:
979 if listignored:
980 iadd(fn)
980 iadd(fn)
981 else:
981 else:
982 uadd(fn)
982 uadd(fn)
983 continue
983 continue
984
984
985 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
985 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
986 # written like that for performance reasons. dmap[fn] is not a
986 # written like that for performance reasons. dmap[fn] is not a
987 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
987 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
988 # opcode has fast paths when the value to be unpacked is a tuple or
988 # opcode has fast paths when the value to be unpacked is a tuple or
989 # a list, but falls back to creating a full-fledged iterator in
989 # a list, but falls back to creating a full-fledged iterator in
990 # general. That is much slower than simply accessing and storing the
990 # general. That is much slower than simply accessing and storing the
991 # tuple members one by one.
991 # tuple members one by one.
992 t = dmap[fn]
992 t = dmap[fn]
993 state = t[0]
993 state = t[0]
994 mode = t[1]
994 mode = t[1]
995 size = t[2]
995 size = t[2]
996 time = t[3]
996 time = t[3]
997
997
998 if not st and state in "nma":
998 if not st and state in "nma":
999 dadd(fn)
999 dadd(fn)
1000 elif state == 'n':
1000 elif state == 'n':
1001 mtime = int(st.st_mtime)
1001 mtime = util.statmtimesec(st)
1002 if (size >= 0 and
1002 if (size >= 0 and
1003 ((size != st.st_size and size != st.st_size & _rangemask)
1003 ((size != st.st_size and size != st.st_size & _rangemask)
1004 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1004 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1005 or size == -2 # other parent
1005 or size == -2 # other parent
1006 or fn in copymap):
1006 or fn in copymap):
1007 madd(fn)
1007 madd(fn)
1008 elif time != mtime and time != mtime & _rangemask:
1008 elif time != mtime and time != mtime & _rangemask:
1009 ladd(fn)
1009 ladd(fn)
1010 elif mtime == lastnormaltime:
1010 elif mtime == lastnormaltime:
1011 # fn may have just been marked as normal and it may have
1011 # fn may have just been marked as normal and it may have
1012 # changed in the same second without changing its size.
1012 # changed in the same second without changing its size.
1013 # This can happen if we quickly do multiple commits.
1013 # This can happen if we quickly do multiple commits.
1014 # Force lookup, so we don't miss such a racy file change.
1014 # Force lookup, so we don't miss such a racy file change.
1015 ladd(fn)
1015 ladd(fn)
1016 elif listclean:
1016 elif listclean:
1017 cadd(fn)
1017 cadd(fn)
1018 elif state == 'm':
1018 elif state == 'm':
1019 madd(fn)
1019 madd(fn)
1020 elif state == 'a':
1020 elif state == 'a':
1021 aadd(fn)
1021 aadd(fn)
1022 elif state == 'r':
1022 elif state == 'r':
1023 radd(fn)
1023 radd(fn)
1024
1024
1025 return (lookup, scmutil.status(modified, added, removed, deleted,
1025 return (lookup, scmutil.status(modified, added, removed, deleted,
1026 unknown, ignored, clean))
1026 unknown, ignored, clean))
1027
1027
1028 def matches(self, match):
1028 def matches(self, match):
1029 '''
1029 '''
1030 return files in the dirstate (in whatever state) filtered by match
1030 return files in the dirstate (in whatever state) filtered by match
1031 '''
1031 '''
1032 dmap = self._map
1032 dmap = self._map
1033 if match.always():
1033 if match.always():
1034 return dmap.keys()
1034 return dmap.keys()
1035 files = match.files()
1035 files = match.files()
1036 if match.isexact():
1036 if match.isexact():
1037 # fast path -- filter the other way around, since typically files is
1037 # fast path -- filter the other way around, since typically files is
1038 # much smaller than dmap
1038 # much smaller than dmap
1039 return [f for f in files if f in dmap]
1039 return [f for f in files if f in dmap]
1040 if match.prefix() and all(fn in dmap for fn in files):
1040 if match.prefix() and all(fn in dmap for fn in files):
1041 # fast path -- all the values are known to be files, so just return
1041 # fast path -- all the values are known to be files, so just return
1042 # that
1042 # that
1043 return list(files)
1043 return list(files)
1044 return [f for f in dmap if match(f)]
1044 return [f for f in dmap if match(f)]
@@ -1,2462 +1,2465 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 import i18n
16 import i18n
17 _ = i18n._
17 _ = i18n._
18 import error, osutil, encoding, parsers
18 import error, osutil, encoding, parsers
19 import errno, shutil, sys, tempfile, traceback
19 import errno, shutil, sys, tempfile, traceback
20 import re as remod
20 import re as remod
21 import os, time, datetime, calendar, textwrap, signal, collections
21 import os, time, datetime, calendar, textwrap, signal, collections
22 import imp, socket, urllib
22 import imp, socket, urllib
23 import gc
23 import gc
24 import bz2
24 import bz2
25 import zlib
25 import zlib
26
26
27 if os.name == 'nt':
27 if os.name == 'nt':
28 import windows as platform
28 import windows as platform
29 else:
29 else:
30 import posix as platform
30 import posix as platform
31
31
32 cachestat = platform.cachestat
32 cachestat = platform.cachestat
33 checkexec = platform.checkexec
33 checkexec = platform.checkexec
34 checklink = platform.checklink
34 checklink = platform.checklink
35 copymode = platform.copymode
35 copymode = platform.copymode
36 executablepath = platform.executablepath
36 executablepath = platform.executablepath
37 expandglobs = platform.expandglobs
37 expandglobs = platform.expandglobs
38 explainexit = platform.explainexit
38 explainexit = platform.explainexit
39 findexe = platform.findexe
39 findexe = platform.findexe
40 gethgcmd = platform.gethgcmd
40 gethgcmd = platform.gethgcmd
41 getuser = platform.getuser
41 getuser = platform.getuser
42 groupmembers = platform.groupmembers
42 groupmembers = platform.groupmembers
43 groupname = platform.groupname
43 groupname = platform.groupname
44 hidewindow = platform.hidewindow
44 hidewindow = platform.hidewindow
45 isexec = platform.isexec
45 isexec = platform.isexec
46 isowner = platform.isowner
46 isowner = platform.isowner
47 localpath = platform.localpath
47 localpath = platform.localpath
48 lookupreg = platform.lookupreg
48 lookupreg = platform.lookupreg
49 makedir = platform.makedir
49 makedir = platform.makedir
50 nlinks = platform.nlinks
50 nlinks = platform.nlinks
51 normpath = platform.normpath
51 normpath = platform.normpath
52 normcase = platform.normcase
52 normcase = platform.normcase
53 normcasespec = platform.normcasespec
53 normcasespec = platform.normcasespec
54 normcasefallback = platform.normcasefallback
54 normcasefallback = platform.normcasefallback
55 openhardlinks = platform.openhardlinks
55 openhardlinks = platform.openhardlinks
56 oslink = platform.oslink
56 oslink = platform.oslink
57 parsepatchoutput = platform.parsepatchoutput
57 parsepatchoutput = platform.parsepatchoutput
58 pconvert = platform.pconvert
58 pconvert = platform.pconvert
59 poll = platform.poll
59 poll = platform.poll
60 popen = platform.popen
60 popen = platform.popen
61 posixfile = platform.posixfile
61 posixfile = platform.posixfile
62 quotecommand = platform.quotecommand
62 quotecommand = platform.quotecommand
63 readpipe = platform.readpipe
63 readpipe = platform.readpipe
64 rename = platform.rename
64 rename = platform.rename
65 removedirs = platform.removedirs
65 removedirs = platform.removedirs
66 samedevice = platform.samedevice
66 samedevice = platform.samedevice
67 samefile = platform.samefile
67 samefile = platform.samefile
68 samestat = platform.samestat
68 samestat = platform.samestat
69 setbinary = platform.setbinary
69 setbinary = platform.setbinary
70 setflags = platform.setflags
70 setflags = platform.setflags
71 setsignalhandler = platform.setsignalhandler
71 setsignalhandler = platform.setsignalhandler
72 shellquote = platform.shellquote
72 shellquote = platform.shellquote
73 spawndetached = platform.spawndetached
73 spawndetached = platform.spawndetached
74 split = platform.split
74 split = platform.split
75 sshargs = platform.sshargs
75 sshargs = platform.sshargs
76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
77 statisexec = platform.statisexec
77 statisexec = platform.statisexec
78 statislink = platform.statislink
78 statislink = platform.statislink
79 termwidth = platform.termwidth
79 termwidth = platform.termwidth
80 testpid = platform.testpid
80 testpid = platform.testpid
81 umask = platform.umask
81 umask = platform.umask
82 unlink = platform.unlink
82 unlink = platform.unlink
83 unlinkpath = platform.unlinkpath
83 unlinkpath = platform.unlinkpath
84 username = platform.username
84 username = platform.username
85
85
86 # Python compatibility
86 # Python compatibility
87
87
88 _notset = object()
88 _notset = object()
89
89
90 def safehasattr(thing, attr):
90 def safehasattr(thing, attr):
91 return getattr(thing, attr, _notset) is not _notset
91 return getattr(thing, attr, _notset) is not _notset
92
92
93 def sha1(s=''):
93 def sha1(s=''):
94 '''
94 '''
95 Low-overhead wrapper around Python's SHA support
95 Low-overhead wrapper around Python's SHA support
96
96
97 >>> f = _fastsha1
97 >>> f = _fastsha1
98 >>> a = sha1()
98 >>> a = sha1()
99 >>> a = f()
99 >>> a = f()
100 >>> a.hexdigest()
100 >>> a.hexdigest()
101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
102 '''
102 '''
103
103
104 return _fastsha1(s)
104 return _fastsha1(s)
105
105
106 def _fastsha1(s=''):
106 def _fastsha1(s=''):
107 # This function will import sha1 from hashlib or sha (whichever is
107 # This function will import sha1 from hashlib or sha (whichever is
108 # available) and overwrite itself with it on the first call.
108 # available) and overwrite itself with it on the first call.
109 # Subsequent calls will go directly to the imported function.
109 # Subsequent calls will go directly to the imported function.
110 if sys.version_info >= (2, 5):
110 if sys.version_info >= (2, 5):
111 from hashlib import sha1 as _sha1
111 from hashlib import sha1 as _sha1
112 else:
112 else:
113 from sha import sha as _sha1
113 from sha import sha as _sha1
114 global _fastsha1, sha1
114 global _fastsha1, sha1
115 _fastsha1 = sha1 = _sha1
115 _fastsha1 = sha1 = _sha1
116 return _sha1(s)
116 return _sha1(s)
117
117
118 def md5(s=''):
118 def md5(s=''):
119 try:
119 try:
120 from hashlib import md5 as _md5
120 from hashlib import md5 as _md5
121 except ImportError:
121 except ImportError:
122 from md5 import md5 as _md5
122 from md5 import md5 as _md5
123 global md5
123 global md5
124 md5 = _md5
124 md5 = _md5
125 return _md5(s)
125 return _md5(s)
126
126
127 DIGESTS = {
127 DIGESTS = {
128 'md5': md5,
128 'md5': md5,
129 'sha1': sha1,
129 'sha1': sha1,
130 }
130 }
131 # List of digest types from strongest to weakest
131 # List of digest types from strongest to weakest
132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
133
133
134 try:
134 try:
135 import hashlib
135 import hashlib
136 DIGESTS.update({
136 DIGESTS.update({
137 'sha512': hashlib.sha512,
137 'sha512': hashlib.sha512,
138 })
138 })
139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
140 except ImportError:
140 except ImportError:
141 pass
141 pass
142
142
143 for k in DIGESTS_BY_STRENGTH:
143 for k in DIGESTS_BY_STRENGTH:
144 assert k in DIGESTS
144 assert k in DIGESTS
145
145
146 class digester(object):
146 class digester(object):
147 """helper to compute digests.
147 """helper to compute digests.
148
148
149 This helper can be used to compute one or more digests given their name.
149 This helper can be used to compute one or more digests given their name.
150
150
151 >>> d = digester(['md5', 'sha1'])
151 >>> d = digester(['md5', 'sha1'])
152 >>> d.update('foo')
152 >>> d.update('foo')
153 >>> [k for k in sorted(d)]
153 >>> [k for k in sorted(d)]
154 ['md5', 'sha1']
154 ['md5', 'sha1']
155 >>> d['md5']
155 >>> d['md5']
156 'acbd18db4cc2f85cedef654fccc4a4d8'
156 'acbd18db4cc2f85cedef654fccc4a4d8'
157 >>> d['sha1']
157 >>> d['sha1']
158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
159 >>> digester.preferred(['md5', 'sha1'])
159 >>> digester.preferred(['md5', 'sha1'])
160 'sha1'
160 'sha1'
161 """
161 """
162
162
163 def __init__(self, digests, s=''):
163 def __init__(self, digests, s=''):
164 self._hashes = {}
164 self._hashes = {}
165 for k in digests:
165 for k in digests:
166 if k not in DIGESTS:
166 if k not in DIGESTS:
167 raise Abort(_('unknown digest type: %s') % k)
167 raise Abort(_('unknown digest type: %s') % k)
168 self._hashes[k] = DIGESTS[k]()
168 self._hashes[k] = DIGESTS[k]()
169 if s:
169 if s:
170 self.update(s)
170 self.update(s)
171
171
172 def update(self, data):
172 def update(self, data):
173 for h in self._hashes.values():
173 for h in self._hashes.values():
174 h.update(data)
174 h.update(data)
175
175
176 def __getitem__(self, key):
176 def __getitem__(self, key):
177 if key not in DIGESTS:
177 if key not in DIGESTS:
178 raise Abort(_('unknown digest type: %s') % k)
178 raise Abort(_('unknown digest type: %s') % k)
179 return self._hashes[key].hexdigest()
179 return self._hashes[key].hexdigest()
180
180
181 def __iter__(self):
181 def __iter__(self):
182 return iter(self._hashes)
182 return iter(self._hashes)
183
183
184 @staticmethod
184 @staticmethod
185 def preferred(supported):
185 def preferred(supported):
186 """returns the strongest digest type in both supported and DIGESTS."""
186 """returns the strongest digest type in both supported and DIGESTS."""
187
187
188 for k in DIGESTS_BY_STRENGTH:
188 for k in DIGESTS_BY_STRENGTH:
189 if k in supported:
189 if k in supported:
190 return k
190 return k
191 return None
191 return None
192
192
193 class digestchecker(object):
193 class digestchecker(object):
194 """file handle wrapper that additionally checks content against a given
194 """file handle wrapper that additionally checks content against a given
195 size and digests.
195 size and digests.
196
196
197 d = digestchecker(fh, size, {'md5': '...'})
197 d = digestchecker(fh, size, {'md5': '...'})
198
198
199 When multiple digests are given, all of them are validated.
199 When multiple digests are given, all of them are validated.
200 """
200 """
201
201
202 def __init__(self, fh, size, digests):
202 def __init__(self, fh, size, digests):
203 self._fh = fh
203 self._fh = fh
204 self._size = size
204 self._size = size
205 self._got = 0
205 self._got = 0
206 self._digests = dict(digests)
206 self._digests = dict(digests)
207 self._digester = digester(self._digests.keys())
207 self._digester = digester(self._digests.keys())
208
208
209 def read(self, length=-1):
209 def read(self, length=-1):
210 content = self._fh.read(length)
210 content = self._fh.read(length)
211 self._digester.update(content)
211 self._digester.update(content)
212 self._got += len(content)
212 self._got += len(content)
213 return content
213 return content
214
214
215 def validate(self):
215 def validate(self):
216 if self._size != self._got:
216 if self._size != self._got:
217 raise Abort(_('size mismatch: expected %d, got %d') %
217 raise Abort(_('size mismatch: expected %d, got %d') %
218 (self._size, self._got))
218 (self._size, self._got))
219 for k, v in self._digests.items():
219 for k, v in self._digests.items():
220 if v != self._digester[k]:
220 if v != self._digester[k]:
221 # i18n: first parameter is a digest name
221 # i18n: first parameter is a digest name
222 raise Abort(_('%s mismatch: expected %s, got %s') %
222 raise Abort(_('%s mismatch: expected %s, got %s') %
223 (k, v, self._digester[k]))
223 (k, v, self._digester[k]))
224
224
225 try:
225 try:
226 buffer = buffer
226 buffer = buffer
227 except NameError:
227 except NameError:
228 if sys.version_info[0] < 3:
228 if sys.version_info[0] < 3:
229 def buffer(sliceable, offset=0):
229 def buffer(sliceable, offset=0):
230 return sliceable[offset:]
230 return sliceable[offset:]
231 else:
231 else:
232 def buffer(sliceable, offset=0):
232 def buffer(sliceable, offset=0):
233 return memoryview(sliceable)[offset:]
233 return memoryview(sliceable)[offset:]
234
234
235 import subprocess
235 import subprocess
236 closefds = os.name == 'posix'
236 closefds = os.name == 'posix'
237
237
238 _chunksize = 4096
238 _chunksize = 4096
239
239
240 class bufferedinputpipe(object):
240 class bufferedinputpipe(object):
241 """a manually buffered input pipe
241 """a manually buffered input pipe
242
242
243 Python will not let us use buffered IO and lazy reading with 'polling' at
243 Python will not let us use buffered IO and lazy reading with 'polling' at
244 the same time. We cannot probe the buffer state and select will not detect
244 the same time. We cannot probe the buffer state and select will not detect
245 that data are ready to read if they are already buffered.
245 that data are ready to read if they are already buffered.
246
246
247 This class let us work around that by implementing its own buffering
247 This class let us work around that by implementing its own buffering
248 (allowing efficient readline) while offering a way to know if the buffer is
248 (allowing efficient readline) while offering a way to know if the buffer is
249 empty from the output (allowing collaboration of the buffer with polling).
249 empty from the output (allowing collaboration of the buffer with polling).
250
250
251 This class lives in the 'util' module because it makes use of the 'os'
251 This class lives in the 'util' module because it makes use of the 'os'
252 module from the python stdlib.
252 module from the python stdlib.
253 """
253 """
254
254
255 def __init__(self, input):
255 def __init__(self, input):
256 self._input = input
256 self._input = input
257 self._buffer = []
257 self._buffer = []
258 self._eof = False
258 self._eof = False
259 self._lenbuf = 0
259 self._lenbuf = 0
260
260
261 @property
261 @property
262 def hasbuffer(self):
262 def hasbuffer(self):
263 """True is any data is currently buffered
263 """True is any data is currently buffered
264
264
265 This will be used externally a pre-step for polling IO. If there is
265 This will be used externally a pre-step for polling IO. If there is
266 already data then no polling should be set in place."""
266 already data then no polling should be set in place."""
267 return bool(self._buffer)
267 return bool(self._buffer)
268
268
269 @property
269 @property
270 def closed(self):
270 def closed(self):
271 return self._input.closed
271 return self._input.closed
272
272
273 def fileno(self):
273 def fileno(self):
274 return self._input.fileno()
274 return self._input.fileno()
275
275
276 def close(self):
276 def close(self):
277 return self._input.close()
277 return self._input.close()
278
278
279 def read(self, size):
279 def read(self, size):
280 while (not self._eof) and (self._lenbuf < size):
280 while (not self._eof) and (self._lenbuf < size):
281 self._fillbuffer()
281 self._fillbuffer()
282 return self._frombuffer(size)
282 return self._frombuffer(size)
283
283
284 def readline(self, *args, **kwargs):
284 def readline(self, *args, **kwargs):
285 if 1 < len(self._buffer):
285 if 1 < len(self._buffer):
286 # this should not happen because both read and readline end with a
286 # this should not happen because both read and readline end with a
287 # _frombuffer call that collapse it.
287 # _frombuffer call that collapse it.
288 self._buffer = [''.join(self._buffer)]
288 self._buffer = [''.join(self._buffer)]
289 self._lenbuf = len(self._buffer[0])
289 self._lenbuf = len(self._buffer[0])
290 lfi = -1
290 lfi = -1
291 if self._buffer:
291 if self._buffer:
292 lfi = self._buffer[-1].find('\n')
292 lfi = self._buffer[-1].find('\n')
293 while (not self._eof) and lfi < 0:
293 while (not self._eof) and lfi < 0:
294 self._fillbuffer()
294 self._fillbuffer()
295 if self._buffer:
295 if self._buffer:
296 lfi = self._buffer[-1].find('\n')
296 lfi = self._buffer[-1].find('\n')
297 size = lfi + 1
297 size = lfi + 1
298 if lfi < 0: # end of file
298 if lfi < 0: # end of file
299 size = self._lenbuf
299 size = self._lenbuf
300 elif 1 < len(self._buffer):
300 elif 1 < len(self._buffer):
301 # we need to take previous chunks into account
301 # we need to take previous chunks into account
302 size += self._lenbuf - len(self._buffer[-1])
302 size += self._lenbuf - len(self._buffer[-1])
303 return self._frombuffer(size)
303 return self._frombuffer(size)
304
304
305 def _frombuffer(self, size):
305 def _frombuffer(self, size):
306 """return at most 'size' data from the buffer
306 """return at most 'size' data from the buffer
307
307
308 The data are removed from the buffer."""
308 The data are removed from the buffer."""
309 if size == 0 or not self._buffer:
309 if size == 0 or not self._buffer:
310 return ''
310 return ''
311 buf = self._buffer[0]
311 buf = self._buffer[0]
312 if 1 < len(self._buffer):
312 if 1 < len(self._buffer):
313 buf = ''.join(self._buffer)
313 buf = ''.join(self._buffer)
314
314
315 data = buf[:size]
315 data = buf[:size]
316 buf = buf[len(data):]
316 buf = buf[len(data):]
317 if buf:
317 if buf:
318 self._buffer = [buf]
318 self._buffer = [buf]
319 self._lenbuf = len(buf)
319 self._lenbuf = len(buf)
320 else:
320 else:
321 self._buffer = []
321 self._buffer = []
322 self._lenbuf = 0
322 self._lenbuf = 0
323 return data
323 return data
324
324
325 def _fillbuffer(self):
325 def _fillbuffer(self):
326 """read data to the buffer"""
326 """read data to the buffer"""
327 data = os.read(self._input.fileno(), _chunksize)
327 data = os.read(self._input.fileno(), _chunksize)
328 if not data:
328 if not data:
329 self._eof = True
329 self._eof = True
330 else:
330 else:
331 self._lenbuf += len(data)
331 self._lenbuf += len(data)
332 self._buffer.append(data)
332 self._buffer.append(data)
333
333
334 def popen2(cmd, env=None, newlines=False):
334 def popen2(cmd, env=None, newlines=False):
335 # Setting bufsize to -1 lets the system decide the buffer size.
335 # Setting bufsize to -1 lets the system decide the buffer size.
336 # The default for bufsize is 0, meaning unbuffered. This leads to
336 # The default for bufsize is 0, meaning unbuffered. This leads to
337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
339 close_fds=closefds,
339 close_fds=closefds,
340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
341 universal_newlines=newlines,
341 universal_newlines=newlines,
342 env=env)
342 env=env)
343 return p.stdin, p.stdout
343 return p.stdin, p.stdout
344
344
345 def popen3(cmd, env=None, newlines=False):
345 def popen3(cmd, env=None, newlines=False):
346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
347 return stdin, stdout, stderr
347 return stdin, stdout, stderr
348
348
349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
351 close_fds=closefds,
351 close_fds=closefds,
352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
353 stderr=subprocess.PIPE,
353 stderr=subprocess.PIPE,
354 universal_newlines=newlines,
354 universal_newlines=newlines,
355 env=env)
355 env=env)
356 return p.stdin, p.stdout, p.stderr, p
356 return p.stdin, p.stdout, p.stderr, p
357
357
358 def version():
358 def version():
359 """Return version information if available."""
359 """Return version information if available."""
360 try:
360 try:
361 import __version__
361 import __version__
362 return __version__.version
362 return __version__.version
363 except ImportError:
363 except ImportError:
364 return 'unknown'
364 return 'unknown'
365
365
366 # used by parsedate
366 # used by parsedate
367 defaultdateformats = (
367 defaultdateformats = (
368 '%Y-%m-%d %H:%M:%S',
368 '%Y-%m-%d %H:%M:%S',
369 '%Y-%m-%d %I:%M:%S%p',
369 '%Y-%m-%d %I:%M:%S%p',
370 '%Y-%m-%d %H:%M',
370 '%Y-%m-%d %H:%M',
371 '%Y-%m-%d %I:%M%p',
371 '%Y-%m-%d %I:%M%p',
372 '%Y-%m-%d',
372 '%Y-%m-%d',
373 '%m-%d',
373 '%m-%d',
374 '%m/%d',
374 '%m/%d',
375 '%m/%d/%y',
375 '%m/%d/%y',
376 '%m/%d/%Y',
376 '%m/%d/%Y',
377 '%a %b %d %H:%M:%S %Y',
377 '%a %b %d %H:%M:%S %Y',
378 '%a %b %d %I:%M:%S%p %Y',
378 '%a %b %d %I:%M:%S%p %Y',
379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
380 '%b %d %H:%M:%S %Y',
380 '%b %d %H:%M:%S %Y',
381 '%b %d %I:%M:%S%p %Y',
381 '%b %d %I:%M:%S%p %Y',
382 '%b %d %H:%M:%S',
382 '%b %d %H:%M:%S',
383 '%b %d %I:%M:%S%p',
383 '%b %d %I:%M:%S%p',
384 '%b %d %H:%M',
384 '%b %d %H:%M',
385 '%b %d %I:%M%p',
385 '%b %d %I:%M%p',
386 '%b %d %Y',
386 '%b %d %Y',
387 '%b %d',
387 '%b %d',
388 '%H:%M:%S',
388 '%H:%M:%S',
389 '%I:%M:%S%p',
389 '%I:%M:%S%p',
390 '%H:%M',
390 '%H:%M',
391 '%I:%M%p',
391 '%I:%M%p',
392 )
392 )
393
393
394 extendeddateformats = defaultdateformats + (
394 extendeddateformats = defaultdateformats + (
395 "%Y",
395 "%Y",
396 "%Y-%m",
396 "%Y-%m",
397 "%b",
397 "%b",
398 "%b %Y",
398 "%b %Y",
399 )
399 )
400
400
401 def cachefunc(func):
401 def cachefunc(func):
402 '''cache the result of function calls'''
402 '''cache the result of function calls'''
403 # XXX doesn't handle keywords args
403 # XXX doesn't handle keywords args
404 if func.func_code.co_argcount == 0:
404 if func.func_code.co_argcount == 0:
405 cache = []
405 cache = []
406 def f():
406 def f():
407 if len(cache) == 0:
407 if len(cache) == 0:
408 cache.append(func())
408 cache.append(func())
409 return cache[0]
409 return cache[0]
410 return f
410 return f
411 cache = {}
411 cache = {}
412 if func.func_code.co_argcount == 1:
412 if func.func_code.co_argcount == 1:
413 # we gain a small amount of time because
413 # we gain a small amount of time because
414 # we don't need to pack/unpack the list
414 # we don't need to pack/unpack the list
415 def f(arg):
415 def f(arg):
416 if arg not in cache:
416 if arg not in cache:
417 cache[arg] = func(arg)
417 cache[arg] = func(arg)
418 return cache[arg]
418 return cache[arg]
419 else:
419 else:
420 def f(*args):
420 def f(*args):
421 if args not in cache:
421 if args not in cache:
422 cache[args] = func(*args)
422 cache[args] = func(*args)
423 return cache[args]
423 return cache[args]
424
424
425 return f
425 return f
426
426
427 class sortdict(dict):
427 class sortdict(dict):
428 '''a simple sorted dictionary'''
428 '''a simple sorted dictionary'''
429 def __init__(self, data=None):
429 def __init__(self, data=None):
430 self._list = []
430 self._list = []
431 if data:
431 if data:
432 self.update(data)
432 self.update(data)
433 def copy(self):
433 def copy(self):
434 return sortdict(self)
434 return sortdict(self)
435 def __setitem__(self, key, val):
435 def __setitem__(self, key, val):
436 if key in self:
436 if key in self:
437 self._list.remove(key)
437 self._list.remove(key)
438 self._list.append(key)
438 self._list.append(key)
439 dict.__setitem__(self, key, val)
439 dict.__setitem__(self, key, val)
440 def __iter__(self):
440 def __iter__(self):
441 return self._list.__iter__()
441 return self._list.__iter__()
442 def update(self, src):
442 def update(self, src):
443 if isinstance(src, dict):
443 if isinstance(src, dict):
444 src = src.iteritems()
444 src = src.iteritems()
445 for k, v in src:
445 for k, v in src:
446 self[k] = v
446 self[k] = v
447 def clear(self):
447 def clear(self):
448 dict.clear(self)
448 dict.clear(self)
449 self._list = []
449 self._list = []
450 def items(self):
450 def items(self):
451 return [(k, self[k]) for k in self._list]
451 return [(k, self[k]) for k in self._list]
452 def __delitem__(self, key):
452 def __delitem__(self, key):
453 dict.__delitem__(self, key)
453 dict.__delitem__(self, key)
454 self._list.remove(key)
454 self._list.remove(key)
455 def pop(self, key, *args, **kwargs):
455 def pop(self, key, *args, **kwargs):
456 dict.pop(self, key, *args, **kwargs)
456 dict.pop(self, key, *args, **kwargs)
457 try:
457 try:
458 self._list.remove(key)
458 self._list.remove(key)
459 except ValueError:
459 except ValueError:
460 pass
460 pass
461 def keys(self):
461 def keys(self):
462 return self._list
462 return self._list
463 def iterkeys(self):
463 def iterkeys(self):
464 return self._list.__iter__()
464 return self._list.__iter__()
465 def iteritems(self):
465 def iteritems(self):
466 for k in self._list:
466 for k in self._list:
467 yield k, self[k]
467 yield k, self[k]
468 def insert(self, index, key, val):
468 def insert(self, index, key, val):
469 self._list.insert(index, key)
469 self._list.insert(index, key)
470 dict.__setitem__(self, key, val)
470 dict.__setitem__(self, key, val)
471
471
472 class lrucachedict(object):
472 class lrucachedict(object):
473 '''cache most recent gets from or sets to this dictionary'''
473 '''cache most recent gets from or sets to this dictionary'''
474 def __init__(self, maxsize):
474 def __init__(self, maxsize):
475 self._cache = {}
475 self._cache = {}
476 self._maxsize = maxsize
476 self._maxsize = maxsize
477 self._order = collections.deque()
477 self._order = collections.deque()
478
478
479 def __getitem__(self, key):
479 def __getitem__(self, key):
480 value = self._cache[key]
480 value = self._cache[key]
481 self._order.remove(key)
481 self._order.remove(key)
482 self._order.append(key)
482 self._order.append(key)
483 return value
483 return value
484
484
485 def __setitem__(self, key, value):
485 def __setitem__(self, key, value):
486 if key not in self._cache:
486 if key not in self._cache:
487 if len(self._cache) >= self._maxsize:
487 if len(self._cache) >= self._maxsize:
488 del self._cache[self._order.popleft()]
488 del self._cache[self._order.popleft()]
489 else:
489 else:
490 self._order.remove(key)
490 self._order.remove(key)
491 self._cache[key] = value
491 self._cache[key] = value
492 self._order.append(key)
492 self._order.append(key)
493
493
494 def __contains__(self, key):
494 def __contains__(self, key):
495 return key in self._cache
495 return key in self._cache
496
496
497 def clear(self):
497 def clear(self):
498 self._cache.clear()
498 self._cache.clear()
499 self._order = collections.deque()
499 self._order = collections.deque()
500
500
501 def lrucachefunc(func):
501 def lrucachefunc(func):
502 '''cache most recent results of function calls'''
502 '''cache most recent results of function calls'''
503 cache = {}
503 cache = {}
504 order = collections.deque()
504 order = collections.deque()
505 if func.func_code.co_argcount == 1:
505 if func.func_code.co_argcount == 1:
506 def f(arg):
506 def f(arg):
507 if arg not in cache:
507 if arg not in cache:
508 if len(cache) > 20:
508 if len(cache) > 20:
509 del cache[order.popleft()]
509 del cache[order.popleft()]
510 cache[arg] = func(arg)
510 cache[arg] = func(arg)
511 else:
511 else:
512 order.remove(arg)
512 order.remove(arg)
513 order.append(arg)
513 order.append(arg)
514 return cache[arg]
514 return cache[arg]
515 else:
515 else:
516 def f(*args):
516 def f(*args):
517 if args not in cache:
517 if args not in cache:
518 if len(cache) > 20:
518 if len(cache) > 20:
519 del cache[order.popleft()]
519 del cache[order.popleft()]
520 cache[args] = func(*args)
520 cache[args] = func(*args)
521 else:
521 else:
522 order.remove(args)
522 order.remove(args)
523 order.append(args)
523 order.append(args)
524 return cache[args]
524 return cache[args]
525
525
526 return f
526 return f
527
527
528 class propertycache(object):
528 class propertycache(object):
529 def __init__(self, func):
529 def __init__(self, func):
530 self.func = func
530 self.func = func
531 self.name = func.__name__
531 self.name = func.__name__
532 def __get__(self, obj, type=None):
532 def __get__(self, obj, type=None):
533 result = self.func(obj)
533 result = self.func(obj)
534 self.cachevalue(obj, result)
534 self.cachevalue(obj, result)
535 return result
535 return result
536
536
537 def cachevalue(self, obj, value):
537 def cachevalue(self, obj, value):
538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
539 obj.__dict__[self.name] = value
539 obj.__dict__[self.name] = value
540
540
541 def pipefilter(s, cmd):
541 def pipefilter(s, cmd):
542 '''filter string S through command CMD, returning its output'''
542 '''filter string S through command CMD, returning its output'''
543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
545 pout, perr = p.communicate(s)
545 pout, perr = p.communicate(s)
546 return pout
546 return pout
547
547
548 def tempfilter(s, cmd):
548 def tempfilter(s, cmd):
549 '''filter string S through a pair of temporary files with CMD.
549 '''filter string S through a pair of temporary files with CMD.
550 CMD is used as a template to create the real command to be run,
550 CMD is used as a template to create the real command to be run,
551 with the strings INFILE and OUTFILE replaced by the real names of
551 with the strings INFILE and OUTFILE replaced by the real names of
552 the temporary files generated.'''
552 the temporary files generated.'''
553 inname, outname = None, None
553 inname, outname = None, None
554 try:
554 try:
555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
556 fp = os.fdopen(infd, 'wb')
556 fp = os.fdopen(infd, 'wb')
557 fp.write(s)
557 fp.write(s)
558 fp.close()
558 fp.close()
559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
560 os.close(outfd)
560 os.close(outfd)
561 cmd = cmd.replace('INFILE', inname)
561 cmd = cmd.replace('INFILE', inname)
562 cmd = cmd.replace('OUTFILE', outname)
562 cmd = cmd.replace('OUTFILE', outname)
563 code = os.system(cmd)
563 code = os.system(cmd)
564 if sys.platform == 'OpenVMS' and code & 1:
564 if sys.platform == 'OpenVMS' and code & 1:
565 code = 0
565 code = 0
566 if code:
566 if code:
567 raise Abort(_("command '%s' failed: %s") %
567 raise Abort(_("command '%s' failed: %s") %
568 (cmd, explainexit(code)))
568 (cmd, explainexit(code)))
569 fp = open(outname, 'rb')
569 fp = open(outname, 'rb')
570 r = fp.read()
570 r = fp.read()
571 fp.close()
571 fp.close()
572 return r
572 return r
573 finally:
573 finally:
574 try:
574 try:
575 if inname:
575 if inname:
576 os.unlink(inname)
576 os.unlink(inname)
577 except OSError:
577 except OSError:
578 pass
578 pass
579 try:
579 try:
580 if outname:
580 if outname:
581 os.unlink(outname)
581 os.unlink(outname)
582 except OSError:
582 except OSError:
583 pass
583 pass
584
584
585 filtertable = {
585 filtertable = {
586 'tempfile:': tempfilter,
586 'tempfile:': tempfilter,
587 'pipe:': pipefilter,
587 'pipe:': pipefilter,
588 }
588 }
589
589
590 def filter(s, cmd):
590 def filter(s, cmd):
591 "filter a string through a command that transforms its input to its output"
591 "filter a string through a command that transforms its input to its output"
592 for name, fn in filtertable.iteritems():
592 for name, fn in filtertable.iteritems():
593 if cmd.startswith(name):
593 if cmd.startswith(name):
594 return fn(s, cmd[len(name):].lstrip())
594 return fn(s, cmd[len(name):].lstrip())
595 return pipefilter(s, cmd)
595 return pipefilter(s, cmd)
596
596
597 def binary(s):
597 def binary(s):
598 """return true if a string is binary data"""
598 """return true if a string is binary data"""
599 return bool(s and '\0' in s)
599 return bool(s and '\0' in s)
600
600
601 def increasingchunks(source, min=1024, max=65536):
601 def increasingchunks(source, min=1024, max=65536):
602 '''return no less than min bytes per chunk while data remains,
602 '''return no less than min bytes per chunk while data remains,
603 doubling min after each chunk until it reaches max'''
603 doubling min after each chunk until it reaches max'''
604 def log2(x):
604 def log2(x):
605 if not x:
605 if not x:
606 return 0
606 return 0
607 i = 0
607 i = 0
608 while x:
608 while x:
609 x >>= 1
609 x >>= 1
610 i += 1
610 i += 1
611 return i - 1
611 return i - 1
612
612
613 buf = []
613 buf = []
614 blen = 0
614 blen = 0
615 for chunk in source:
615 for chunk in source:
616 buf.append(chunk)
616 buf.append(chunk)
617 blen += len(chunk)
617 blen += len(chunk)
618 if blen >= min:
618 if blen >= min:
619 if min < max:
619 if min < max:
620 min = min << 1
620 min = min << 1
621 nmin = 1 << log2(blen)
621 nmin = 1 << log2(blen)
622 if nmin > min:
622 if nmin > min:
623 min = nmin
623 min = nmin
624 if min > max:
624 if min > max:
625 min = max
625 min = max
626 yield ''.join(buf)
626 yield ''.join(buf)
627 blen = 0
627 blen = 0
628 buf = []
628 buf = []
629 if buf:
629 if buf:
630 yield ''.join(buf)
630 yield ''.join(buf)
631
631
632 Abort = error.Abort
632 Abort = error.Abort
633
633
634 def always(fn):
634 def always(fn):
635 return True
635 return True
636
636
637 def never(fn):
637 def never(fn):
638 return False
638 return False
639
639
640 def nogc(func):
640 def nogc(func):
641 """disable garbage collector
641 """disable garbage collector
642
642
643 Python's garbage collector triggers a GC each time a certain number of
643 Python's garbage collector triggers a GC each time a certain number of
644 container objects (the number being defined by gc.get_threshold()) are
644 container objects (the number being defined by gc.get_threshold()) are
645 allocated even when marked not to be tracked by the collector. Tracking has
645 allocated even when marked not to be tracked by the collector. Tracking has
646 no effect on when GCs are triggered, only on what objects the GC looks
646 no effect on when GCs are triggered, only on what objects the GC looks
647 into. As a workaround, disable GC while building complex (huge)
647 into. As a workaround, disable GC while building complex (huge)
648 containers.
648 containers.
649
649
650 This garbage collector issue have been fixed in 2.7.
650 This garbage collector issue have been fixed in 2.7.
651 """
651 """
652 def wrapper(*args, **kwargs):
652 def wrapper(*args, **kwargs):
653 gcenabled = gc.isenabled()
653 gcenabled = gc.isenabled()
654 gc.disable()
654 gc.disable()
655 try:
655 try:
656 return func(*args, **kwargs)
656 return func(*args, **kwargs)
657 finally:
657 finally:
658 if gcenabled:
658 if gcenabled:
659 gc.enable()
659 gc.enable()
660 return wrapper
660 return wrapper
661
661
662 def pathto(root, n1, n2):
662 def pathto(root, n1, n2):
663 '''return the relative path from one place to another.
663 '''return the relative path from one place to another.
664 root should use os.sep to separate directories
664 root should use os.sep to separate directories
665 n1 should use os.sep to separate directories
665 n1 should use os.sep to separate directories
666 n2 should use "/" to separate directories
666 n2 should use "/" to separate directories
667 returns an os.sep-separated path.
667 returns an os.sep-separated path.
668
668
669 If n1 is a relative path, it's assumed it's
669 If n1 is a relative path, it's assumed it's
670 relative to root.
670 relative to root.
671 n2 should always be relative to root.
671 n2 should always be relative to root.
672 '''
672 '''
673 if not n1:
673 if not n1:
674 return localpath(n2)
674 return localpath(n2)
675 if os.path.isabs(n1):
675 if os.path.isabs(n1):
676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
677 return os.path.join(root, localpath(n2))
677 return os.path.join(root, localpath(n2))
678 n2 = '/'.join((pconvert(root), n2))
678 n2 = '/'.join((pconvert(root), n2))
679 a, b = splitpath(n1), n2.split('/')
679 a, b = splitpath(n1), n2.split('/')
680 a.reverse()
680 a.reverse()
681 b.reverse()
681 b.reverse()
682 while a and b and a[-1] == b[-1]:
682 while a and b and a[-1] == b[-1]:
683 a.pop()
683 a.pop()
684 b.pop()
684 b.pop()
685 b.reverse()
685 b.reverse()
686 return os.sep.join((['..'] * len(a)) + b) or '.'
686 return os.sep.join((['..'] * len(a)) + b) or '.'
687
687
688 def mainfrozen():
688 def mainfrozen():
689 """return True if we are a frozen executable.
689 """return True if we are a frozen executable.
690
690
691 The code supports py2exe (most common, Windows only) and tools/freeze
691 The code supports py2exe (most common, Windows only) and tools/freeze
692 (portable, not much used).
692 (portable, not much used).
693 """
693 """
694 return (safehasattr(sys, "frozen") or # new py2exe
694 return (safehasattr(sys, "frozen") or # new py2exe
695 safehasattr(sys, "importers") or # old py2exe
695 safehasattr(sys, "importers") or # old py2exe
696 imp.is_frozen("__main__")) # tools/freeze
696 imp.is_frozen("__main__")) # tools/freeze
697
697
698 # the location of data files matching the source code
698 # the location of data files matching the source code
699 if mainfrozen():
699 if mainfrozen():
700 # executable version (py2exe) doesn't support __file__
700 # executable version (py2exe) doesn't support __file__
701 datapath = os.path.dirname(sys.executable)
701 datapath = os.path.dirname(sys.executable)
702 else:
702 else:
703 datapath = os.path.dirname(__file__)
703 datapath = os.path.dirname(__file__)
704
704
705 i18n.setdatapath(datapath)
705 i18n.setdatapath(datapath)
706
706
707 _hgexecutable = None
707 _hgexecutable = None
708
708
709 def hgexecutable():
709 def hgexecutable():
710 """return location of the 'hg' executable.
710 """return location of the 'hg' executable.
711
711
712 Defaults to $HG or 'hg' in the search path.
712 Defaults to $HG or 'hg' in the search path.
713 """
713 """
714 if _hgexecutable is None:
714 if _hgexecutable is None:
715 hg = os.environ.get('HG')
715 hg = os.environ.get('HG')
716 mainmod = sys.modules['__main__']
716 mainmod = sys.modules['__main__']
717 if hg:
717 if hg:
718 _sethgexecutable(hg)
718 _sethgexecutable(hg)
719 elif mainfrozen():
719 elif mainfrozen():
720 _sethgexecutable(sys.executable)
720 _sethgexecutable(sys.executable)
721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
722 _sethgexecutable(mainmod.__file__)
722 _sethgexecutable(mainmod.__file__)
723 else:
723 else:
724 exe = findexe('hg') or os.path.basename(sys.argv[0])
724 exe = findexe('hg') or os.path.basename(sys.argv[0])
725 _sethgexecutable(exe)
725 _sethgexecutable(exe)
726 return _hgexecutable
726 return _hgexecutable
727
727
728 def _sethgexecutable(path):
728 def _sethgexecutable(path):
729 """set location of the 'hg' executable"""
729 """set location of the 'hg' executable"""
730 global _hgexecutable
730 global _hgexecutable
731 _hgexecutable = path
731 _hgexecutable = path
732
732
733 def _isstdout(f):
733 def _isstdout(f):
734 fileno = getattr(f, 'fileno', None)
734 fileno = getattr(f, 'fileno', None)
735 return fileno and fileno() == sys.__stdout__.fileno()
735 return fileno and fileno() == sys.__stdout__.fileno()
736
736
737 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
737 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
738 '''enhanced shell command execution.
738 '''enhanced shell command execution.
739 run with environment maybe modified, maybe in different dir.
739 run with environment maybe modified, maybe in different dir.
740
740
741 if command fails and onerr is None, return status, else raise onerr
741 if command fails and onerr is None, return status, else raise onerr
742 object as exception.
742 object as exception.
743
743
744 if out is specified, it is assumed to be a file-like object that has a
744 if out is specified, it is assumed to be a file-like object that has a
745 write() method. stdout and stderr will be redirected to out.'''
745 write() method. stdout and stderr will be redirected to out.'''
746 if environ is None:
746 if environ is None:
747 environ = {}
747 environ = {}
748 try:
748 try:
749 sys.stdout.flush()
749 sys.stdout.flush()
750 except Exception:
750 except Exception:
751 pass
751 pass
752 def py2shell(val):
752 def py2shell(val):
753 'convert python object into string that is useful to shell'
753 'convert python object into string that is useful to shell'
754 if val is None or val is False:
754 if val is None or val is False:
755 return '0'
755 return '0'
756 if val is True:
756 if val is True:
757 return '1'
757 return '1'
758 return str(val)
758 return str(val)
759 origcmd = cmd
759 origcmd = cmd
760 cmd = quotecommand(cmd)
760 cmd = quotecommand(cmd)
761 if sys.platform == 'plan9' and (sys.version_info[0] == 2
761 if sys.platform == 'plan9' and (sys.version_info[0] == 2
762 and sys.version_info[1] < 7):
762 and sys.version_info[1] < 7):
763 # subprocess kludge to work around issues in half-baked Python
763 # subprocess kludge to work around issues in half-baked Python
764 # ports, notably bichued/python:
764 # ports, notably bichued/python:
765 if not cwd is None:
765 if not cwd is None:
766 os.chdir(cwd)
766 os.chdir(cwd)
767 rc = os.system(cmd)
767 rc = os.system(cmd)
768 else:
768 else:
769 env = dict(os.environ)
769 env = dict(os.environ)
770 env.update((k, py2shell(v)) for k, v in environ.iteritems())
770 env.update((k, py2shell(v)) for k, v in environ.iteritems())
771 env['HG'] = hgexecutable()
771 env['HG'] = hgexecutable()
772 if out is None or _isstdout(out):
772 if out is None or _isstdout(out):
773 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
773 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
774 env=env, cwd=cwd)
774 env=env, cwd=cwd)
775 else:
775 else:
776 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
776 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
777 env=env, cwd=cwd, stdout=subprocess.PIPE,
777 env=env, cwd=cwd, stdout=subprocess.PIPE,
778 stderr=subprocess.STDOUT)
778 stderr=subprocess.STDOUT)
779 while True:
779 while True:
780 line = proc.stdout.readline()
780 line = proc.stdout.readline()
781 if not line:
781 if not line:
782 break
782 break
783 out.write(line)
783 out.write(line)
784 proc.wait()
784 proc.wait()
785 rc = proc.returncode
785 rc = proc.returncode
786 if sys.platform == 'OpenVMS' and rc & 1:
786 if sys.platform == 'OpenVMS' and rc & 1:
787 rc = 0
787 rc = 0
788 if rc and onerr:
788 if rc and onerr:
789 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
789 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
790 explainexit(rc)[0])
790 explainexit(rc)[0])
791 if errprefix:
791 if errprefix:
792 errmsg = '%s: %s' % (errprefix, errmsg)
792 errmsg = '%s: %s' % (errprefix, errmsg)
793 raise onerr(errmsg)
793 raise onerr(errmsg)
794 return rc
794 return rc
795
795
796 def checksignature(func):
796 def checksignature(func):
797 '''wrap a function with code to check for calling errors'''
797 '''wrap a function with code to check for calling errors'''
798 def check(*args, **kwargs):
798 def check(*args, **kwargs):
799 try:
799 try:
800 return func(*args, **kwargs)
800 return func(*args, **kwargs)
801 except TypeError:
801 except TypeError:
802 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
802 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
803 raise error.SignatureError
803 raise error.SignatureError
804 raise
804 raise
805
805
806 return check
806 return check
807
807
808 def copyfile(src, dest, hardlink=False):
808 def copyfile(src, dest, hardlink=False):
809 "copy a file, preserving mode and atime/mtime"
809 "copy a file, preserving mode and atime/mtime"
810 if os.path.lexists(dest):
810 if os.path.lexists(dest):
811 unlink(dest)
811 unlink(dest)
812 # hardlinks are problematic on CIFS, quietly ignore this flag
812 # hardlinks are problematic on CIFS, quietly ignore this flag
813 # until we find a way to work around it cleanly (issue4546)
813 # until we find a way to work around it cleanly (issue4546)
814 if False and hardlink:
814 if False and hardlink:
815 try:
815 try:
816 oslink(src, dest)
816 oslink(src, dest)
817 return
817 return
818 except (IOError, OSError):
818 except (IOError, OSError):
819 pass # fall back to normal copy
819 pass # fall back to normal copy
820 if os.path.islink(src):
820 if os.path.islink(src):
821 os.symlink(os.readlink(src), dest)
821 os.symlink(os.readlink(src), dest)
822 else:
822 else:
823 try:
823 try:
824 shutil.copyfile(src, dest)
824 shutil.copyfile(src, dest)
825 shutil.copymode(src, dest)
825 shutil.copymode(src, dest)
826 except shutil.Error as inst:
826 except shutil.Error as inst:
827 raise Abort(str(inst))
827 raise Abort(str(inst))
828
828
829 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
829 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
830 """Copy a directory tree using hardlinks if possible."""
830 """Copy a directory tree using hardlinks if possible."""
831 num = 0
831 num = 0
832
832
833 if hardlink is None:
833 if hardlink is None:
834 hardlink = (os.stat(src).st_dev ==
834 hardlink = (os.stat(src).st_dev ==
835 os.stat(os.path.dirname(dst)).st_dev)
835 os.stat(os.path.dirname(dst)).st_dev)
836 if hardlink:
836 if hardlink:
837 topic = _('linking')
837 topic = _('linking')
838 else:
838 else:
839 topic = _('copying')
839 topic = _('copying')
840
840
841 if os.path.isdir(src):
841 if os.path.isdir(src):
842 os.mkdir(dst)
842 os.mkdir(dst)
843 for name, kind in osutil.listdir(src):
843 for name, kind in osutil.listdir(src):
844 srcname = os.path.join(src, name)
844 srcname = os.path.join(src, name)
845 dstname = os.path.join(dst, name)
845 dstname = os.path.join(dst, name)
846 def nprog(t, pos):
846 def nprog(t, pos):
847 if pos is not None:
847 if pos is not None:
848 return progress(t, pos + num)
848 return progress(t, pos + num)
849 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
849 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
850 num += n
850 num += n
851 else:
851 else:
852 if hardlink:
852 if hardlink:
853 try:
853 try:
854 oslink(src, dst)
854 oslink(src, dst)
855 except (IOError, OSError):
855 except (IOError, OSError):
856 hardlink = False
856 hardlink = False
857 shutil.copy(src, dst)
857 shutil.copy(src, dst)
858 else:
858 else:
859 shutil.copy(src, dst)
859 shutil.copy(src, dst)
860 num += 1
860 num += 1
861 progress(topic, num)
861 progress(topic, num)
862 progress(topic, None)
862 progress(topic, None)
863
863
864 return hardlink, num
864 return hardlink, num
865
865
866 _winreservednames = '''con prn aux nul
866 _winreservednames = '''con prn aux nul
867 com1 com2 com3 com4 com5 com6 com7 com8 com9
867 com1 com2 com3 com4 com5 com6 com7 com8 com9
868 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
868 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
869 _winreservedchars = ':*?"<>|'
869 _winreservedchars = ':*?"<>|'
870 def checkwinfilename(path):
870 def checkwinfilename(path):
871 r'''Check that the base-relative path is a valid filename on Windows.
871 r'''Check that the base-relative path is a valid filename on Windows.
872 Returns None if the path is ok, or a UI string describing the problem.
872 Returns None if the path is ok, or a UI string describing the problem.
873
873
874 >>> checkwinfilename("just/a/normal/path")
874 >>> checkwinfilename("just/a/normal/path")
875 >>> checkwinfilename("foo/bar/con.xml")
875 >>> checkwinfilename("foo/bar/con.xml")
876 "filename contains 'con', which is reserved on Windows"
876 "filename contains 'con', which is reserved on Windows"
877 >>> checkwinfilename("foo/con.xml/bar")
877 >>> checkwinfilename("foo/con.xml/bar")
878 "filename contains 'con', which is reserved on Windows"
878 "filename contains 'con', which is reserved on Windows"
879 >>> checkwinfilename("foo/bar/xml.con")
879 >>> checkwinfilename("foo/bar/xml.con")
880 >>> checkwinfilename("foo/bar/AUX/bla.txt")
880 >>> checkwinfilename("foo/bar/AUX/bla.txt")
881 "filename contains 'AUX', which is reserved on Windows"
881 "filename contains 'AUX', which is reserved on Windows"
882 >>> checkwinfilename("foo/bar/bla:.txt")
882 >>> checkwinfilename("foo/bar/bla:.txt")
883 "filename contains ':', which is reserved on Windows"
883 "filename contains ':', which is reserved on Windows"
884 >>> checkwinfilename("foo/bar/b\07la.txt")
884 >>> checkwinfilename("foo/bar/b\07la.txt")
885 "filename contains '\\x07', which is invalid on Windows"
885 "filename contains '\\x07', which is invalid on Windows"
886 >>> checkwinfilename("foo/bar/bla ")
886 >>> checkwinfilename("foo/bar/bla ")
887 "filename ends with ' ', which is not allowed on Windows"
887 "filename ends with ' ', which is not allowed on Windows"
888 >>> checkwinfilename("../bar")
888 >>> checkwinfilename("../bar")
889 >>> checkwinfilename("foo\\")
889 >>> checkwinfilename("foo\\")
890 "filename ends with '\\', which is invalid on Windows"
890 "filename ends with '\\', which is invalid on Windows"
891 >>> checkwinfilename("foo\\/bar")
891 >>> checkwinfilename("foo\\/bar")
892 "directory name ends with '\\', which is invalid on Windows"
892 "directory name ends with '\\', which is invalid on Windows"
893 '''
893 '''
894 if path.endswith('\\'):
894 if path.endswith('\\'):
895 return _("filename ends with '\\', which is invalid on Windows")
895 return _("filename ends with '\\', which is invalid on Windows")
896 if '\\/' in path:
896 if '\\/' in path:
897 return _("directory name ends with '\\', which is invalid on Windows")
897 return _("directory name ends with '\\', which is invalid on Windows")
898 for n in path.replace('\\', '/').split('/'):
898 for n in path.replace('\\', '/').split('/'):
899 if not n:
899 if not n:
900 continue
900 continue
901 for c in n:
901 for c in n:
902 if c in _winreservedchars:
902 if c in _winreservedchars:
903 return _("filename contains '%s', which is reserved "
903 return _("filename contains '%s', which is reserved "
904 "on Windows") % c
904 "on Windows") % c
905 if ord(c) <= 31:
905 if ord(c) <= 31:
906 return _("filename contains %r, which is invalid "
906 return _("filename contains %r, which is invalid "
907 "on Windows") % c
907 "on Windows") % c
908 base = n.split('.')[0]
908 base = n.split('.')[0]
909 if base and base.lower() in _winreservednames:
909 if base and base.lower() in _winreservednames:
910 return _("filename contains '%s', which is reserved "
910 return _("filename contains '%s', which is reserved "
911 "on Windows") % base
911 "on Windows") % base
912 t = n[-1]
912 t = n[-1]
913 if t in '. ' and n not in '..':
913 if t in '. ' and n not in '..':
914 return _("filename ends with '%s', which is not allowed "
914 return _("filename ends with '%s', which is not allowed "
915 "on Windows") % t
915 "on Windows") % t
916
916
917 if os.name == 'nt':
917 if os.name == 'nt':
918 checkosfilename = checkwinfilename
918 checkosfilename = checkwinfilename
919 else:
919 else:
920 checkosfilename = platform.checkosfilename
920 checkosfilename = platform.checkosfilename
921
921
922 def makelock(info, pathname):
922 def makelock(info, pathname):
923 try:
923 try:
924 return os.symlink(info, pathname)
924 return os.symlink(info, pathname)
925 except OSError as why:
925 except OSError as why:
926 if why.errno == errno.EEXIST:
926 if why.errno == errno.EEXIST:
927 raise
927 raise
928 except AttributeError: # no symlink in os
928 except AttributeError: # no symlink in os
929 pass
929 pass
930
930
931 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
931 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
932 os.write(ld, info)
932 os.write(ld, info)
933 os.close(ld)
933 os.close(ld)
934
934
935 def readlock(pathname):
935 def readlock(pathname):
936 try:
936 try:
937 return os.readlink(pathname)
937 return os.readlink(pathname)
938 except OSError as why:
938 except OSError as why:
939 if why.errno not in (errno.EINVAL, errno.ENOSYS):
939 if why.errno not in (errno.EINVAL, errno.ENOSYS):
940 raise
940 raise
941 except AttributeError: # no symlink in os
941 except AttributeError: # no symlink in os
942 pass
942 pass
943 fp = posixfile(pathname)
943 fp = posixfile(pathname)
944 r = fp.read()
944 r = fp.read()
945 fp.close()
945 fp.close()
946 return r
946 return r
947
947
948 def fstat(fp):
948 def fstat(fp):
949 '''stat file object that may not have fileno method.'''
949 '''stat file object that may not have fileno method.'''
950 try:
950 try:
951 return os.fstat(fp.fileno())
951 return os.fstat(fp.fileno())
952 except AttributeError:
952 except AttributeError:
953 return os.stat(fp.name)
953 return os.stat(fp.name)
954
954
955 def statmtimesec(st):
956 return int(st.st_mtime)
957
955 # File system features
958 # File system features
956
959
957 def checkcase(path):
960 def checkcase(path):
958 """
961 """
959 Return true if the given path is on a case-sensitive filesystem
962 Return true if the given path is on a case-sensitive filesystem
960
963
961 Requires a path (like /foo/.hg) ending with a foldable final
964 Requires a path (like /foo/.hg) ending with a foldable final
962 directory component.
965 directory component.
963 """
966 """
964 s1 = os.lstat(path)
967 s1 = os.lstat(path)
965 d, b = os.path.split(path)
968 d, b = os.path.split(path)
966 b2 = b.upper()
969 b2 = b.upper()
967 if b == b2:
970 if b == b2:
968 b2 = b.lower()
971 b2 = b.lower()
969 if b == b2:
972 if b == b2:
970 return True # no evidence against case sensitivity
973 return True # no evidence against case sensitivity
971 p2 = os.path.join(d, b2)
974 p2 = os.path.join(d, b2)
972 try:
975 try:
973 s2 = os.lstat(p2)
976 s2 = os.lstat(p2)
974 if s2 == s1:
977 if s2 == s1:
975 return False
978 return False
976 return True
979 return True
977 except OSError:
980 except OSError:
978 return True
981 return True
979
982
980 try:
983 try:
981 import re2
984 import re2
982 _re2 = None
985 _re2 = None
983 except ImportError:
986 except ImportError:
984 _re2 = False
987 _re2 = False
985
988
986 class _re(object):
989 class _re(object):
987 def _checkre2(self):
990 def _checkre2(self):
988 global _re2
991 global _re2
989 try:
992 try:
990 # check if match works, see issue3964
993 # check if match works, see issue3964
991 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
994 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
992 except ImportError:
995 except ImportError:
993 _re2 = False
996 _re2 = False
994
997
995 def compile(self, pat, flags=0):
998 def compile(self, pat, flags=0):
996 '''Compile a regular expression, using re2 if possible
999 '''Compile a regular expression, using re2 if possible
997
1000
998 For best performance, use only re2-compatible regexp features. The
1001 For best performance, use only re2-compatible regexp features. The
999 only flags from the re module that are re2-compatible are
1002 only flags from the re module that are re2-compatible are
1000 IGNORECASE and MULTILINE.'''
1003 IGNORECASE and MULTILINE.'''
1001 if _re2 is None:
1004 if _re2 is None:
1002 self._checkre2()
1005 self._checkre2()
1003 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1006 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1004 if flags & remod.IGNORECASE:
1007 if flags & remod.IGNORECASE:
1005 pat = '(?i)' + pat
1008 pat = '(?i)' + pat
1006 if flags & remod.MULTILINE:
1009 if flags & remod.MULTILINE:
1007 pat = '(?m)' + pat
1010 pat = '(?m)' + pat
1008 try:
1011 try:
1009 return re2.compile(pat)
1012 return re2.compile(pat)
1010 except re2.error:
1013 except re2.error:
1011 pass
1014 pass
1012 return remod.compile(pat, flags)
1015 return remod.compile(pat, flags)
1013
1016
1014 @propertycache
1017 @propertycache
1015 def escape(self):
1018 def escape(self):
1016 '''Return the version of escape corresponding to self.compile.
1019 '''Return the version of escape corresponding to self.compile.
1017
1020
1018 This is imperfect because whether re2 or re is used for a particular
1021 This is imperfect because whether re2 or re is used for a particular
1019 function depends on the flags, etc, but it's the best we can do.
1022 function depends on the flags, etc, but it's the best we can do.
1020 '''
1023 '''
1021 global _re2
1024 global _re2
1022 if _re2 is None:
1025 if _re2 is None:
1023 self._checkre2()
1026 self._checkre2()
1024 if _re2:
1027 if _re2:
1025 return re2.escape
1028 return re2.escape
1026 else:
1029 else:
1027 return remod.escape
1030 return remod.escape
1028
1031
1029 re = _re()
1032 re = _re()
1030
1033
1031 _fspathcache = {}
1034 _fspathcache = {}
1032 def fspath(name, root):
1035 def fspath(name, root):
1033 '''Get name in the case stored in the filesystem
1036 '''Get name in the case stored in the filesystem
1034
1037
1035 The name should be relative to root, and be normcase-ed for efficiency.
1038 The name should be relative to root, and be normcase-ed for efficiency.
1036
1039
1037 Note that this function is unnecessary, and should not be
1040 Note that this function is unnecessary, and should not be
1038 called, for case-sensitive filesystems (simply because it's expensive).
1041 called, for case-sensitive filesystems (simply because it's expensive).
1039
1042
1040 The root should be normcase-ed, too.
1043 The root should be normcase-ed, too.
1041 '''
1044 '''
1042 def _makefspathcacheentry(dir):
1045 def _makefspathcacheentry(dir):
1043 return dict((normcase(n), n) for n in os.listdir(dir))
1046 return dict((normcase(n), n) for n in os.listdir(dir))
1044
1047
1045 seps = os.sep
1048 seps = os.sep
1046 if os.altsep:
1049 if os.altsep:
1047 seps = seps + os.altsep
1050 seps = seps + os.altsep
1048 # Protect backslashes. This gets silly very quickly.
1051 # Protect backslashes. This gets silly very quickly.
1049 seps.replace('\\','\\\\')
1052 seps.replace('\\','\\\\')
1050 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1053 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1051 dir = os.path.normpath(root)
1054 dir = os.path.normpath(root)
1052 result = []
1055 result = []
1053 for part, sep in pattern.findall(name):
1056 for part, sep in pattern.findall(name):
1054 if sep:
1057 if sep:
1055 result.append(sep)
1058 result.append(sep)
1056 continue
1059 continue
1057
1060
1058 if dir not in _fspathcache:
1061 if dir not in _fspathcache:
1059 _fspathcache[dir] = _makefspathcacheentry(dir)
1062 _fspathcache[dir] = _makefspathcacheentry(dir)
1060 contents = _fspathcache[dir]
1063 contents = _fspathcache[dir]
1061
1064
1062 found = contents.get(part)
1065 found = contents.get(part)
1063 if not found:
1066 if not found:
1064 # retry "once per directory" per "dirstate.walk" which
1067 # retry "once per directory" per "dirstate.walk" which
1065 # may take place for each patches of "hg qpush", for example
1068 # may take place for each patches of "hg qpush", for example
1066 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1069 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1067 found = contents.get(part)
1070 found = contents.get(part)
1068
1071
1069 result.append(found or part)
1072 result.append(found or part)
1070 dir = os.path.join(dir, part)
1073 dir = os.path.join(dir, part)
1071
1074
1072 return ''.join(result)
1075 return ''.join(result)
1073
1076
1074 def checknlink(testfile):
1077 def checknlink(testfile):
1075 '''check whether hardlink count reporting works properly'''
1078 '''check whether hardlink count reporting works properly'''
1076
1079
1077 # testfile may be open, so we need a separate file for checking to
1080 # testfile may be open, so we need a separate file for checking to
1078 # work around issue2543 (or testfile may get lost on Samba shares)
1081 # work around issue2543 (or testfile may get lost on Samba shares)
1079 f1 = testfile + ".hgtmp1"
1082 f1 = testfile + ".hgtmp1"
1080 if os.path.lexists(f1):
1083 if os.path.lexists(f1):
1081 return False
1084 return False
1082 try:
1085 try:
1083 posixfile(f1, 'w').close()
1086 posixfile(f1, 'w').close()
1084 except IOError:
1087 except IOError:
1085 return False
1088 return False
1086
1089
1087 f2 = testfile + ".hgtmp2"
1090 f2 = testfile + ".hgtmp2"
1088 fd = None
1091 fd = None
1089 try:
1092 try:
1090 oslink(f1, f2)
1093 oslink(f1, f2)
1091 # nlinks() may behave differently for files on Windows shares if
1094 # nlinks() may behave differently for files on Windows shares if
1092 # the file is open.
1095 # the file is open.
1093 fd = posixfile(f2)
1096 fd = posixfile(f2)
1094 return nlinks(f2) > 1
1097 return nlinks(f2) > 1
1095 except OSError:
1098 except OSError:
1096 return False
1099 return False
1097 finally:
1100 finally:
1098 if fd is not None:
1101 if fd is not None:
1099 fd.close()
1102 fd.close()
1100 for f in (f1, f2):
1103 for f in (f1, f2):
1101 try:
1104 try:
1102 os.unlink(f)
1105 os.unlink(f)
1103 except OSError:
1106 except OSError:
1104 pass
1107 pass
1105
1108
1106 def endswithsep(path):
1109 def endswithsep(path):
1107 '''Check path ends with os.sep or os.altsep.'''
1110 '''Check path ends with os.sep or os.altsep.'''
1108 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1111 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1109
1112
1110 def splitpath(path):
1113 def splitpath(path):
1111 '''Split path by os.sep.
1114 '''Split path by os.sep.
1112 Note that this function does not use os.altsep because this is
1115 Note that this function does not use os.altsep because this is
1113 an alternative of simple "xxx.split(os.sep)".
1116 an alternative of simple "xxx.split(os.sep)".
1114 It is recommended to use os.path.normpath() before using this
1117 It is recommended to use os.path.normpath() before using this
1115 function if need.'''
1118 function if need.'''
1116 return path.split(os.sep)
1119 return path.split(os.sep)
1117
1120
1118 def gui():
1121 def gui():
1119 '''Are we running in a GUI?'''
1122 '''Are we running in a GUI?'''
1120 if sys.platform == 'darwin':
1123 if sys.platform == 'darwin':
1121 if 'SSH_CONNECTION' in os.environ:
1124 if 'SSH_CONNECTION' in os.environ:
1122 # handle SSH access to a box where the user is logged in
1125 # handle SSH access to a box where the user is logged in
1123 return False
1126 return False
1124 elif getattr(osutil, 'isgui', None):
1127 elif getattr(osutil, 'isgui', None):
1125 # check if a CoreGraphics session is available
1128 # check if a CoreGraphics session is available
1126 return osutil.isgui()
1129 return osutil.isgui()
1127 else:
1130 else:
1128 # pure build; use a safe default
1131 # pure build; use a safe default
1129 return True
1132 return True
1130 else:
1133 else:
1131 return os.name == "nt" or os.environ.get("DISPLAY")
1134 return os.name == "nt" or os.environ.get("DISPLAY")
1132
1135
1133 def mktempcopy(name, emptyok=False, createmode=None):
1136 def mktempcopy(name, emptyok=False, createmode=None):
1134 """Create a temporary file with the same contents from name
1137 """Create a temporary file with the same contents from name
1135
1138
1136 The permission bits are copied from the original file.
1139 The permission bits are copied from the original file.
1137
1140
1138 If the temporary file is going to be truncated immediately, you
1141 If the temporary file is going to be truncated immediately, you
1139 can use emptyok=True as an optimization.
1142 can use emptyok=True as an optimization.
1140
1143
1141 Returns the name of the temporary file.
1144 Returns the name of the temporary file.
1142 """
1145 """
1143 d, fn = os.path.split(name)
1146 d, fn = os.path.split(name)
1144 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1147 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1145 os.close(fd)
1148 os.close(fd)
1146 # Temporary files are created with mode 0600, which is usually not
1149 # Temporary files are created with mode 0600, which is usually not
1147 # what we want. If the original file already exists, just copy
1150 # what we want. If the original file already exists, just copy
1148 # its mode. Otherwise, manually obey umask.
1151 # its mode. Otherwise, manually obey umask.
1149 copymode(name, temp, createmode)
1152 copymode(name, temp, createmode)
1150 if emptyok:
1153 if emptyok:
1151 return temp
1154 return temp
1152 try:
1155 try:
1153 try:
1156 try:
1154 ifp = posixfile(name, "rb")
1157 ifp = posixfile(name, "rb")
1155 except IOError as inst:
1158 except IOError as inst:
1156 if inst.errno == errno.ENOENT:
1159 if inst.errno == errno.ENOENT:
1157 return temp
1160 return temp
1158 if not getattr(inst, 'filename', None):
1161 if not getattr(inst, 'filename', None):
1159 inst.filename = name
1162 inst.filename = name
1160 raise
1163 raise
1161 ofp = posixfile(temp, "wb")
1164 ofp = posixfile(temp, "wb")
1162 for chunk in filechunkiter(ifp):
1165 for chunk in filechunkiter(ifp):
1163 ofp.write(chunk)
1166 ofp.write(chunk)
1164 ifp.close()
1167 ifp.close()
1165 ofp.close()
1168 ofp.close()
1166 except: # re-raises
1169 except: # re-raises
1167 try: os.unlink(temp)
1170 try: os.unlink(temp)
1168 except OSError: pass
1171 except OSError: pass
1169 raise
1172 raise
1170 return temp
1173 return temp
1171
1174
1172 class atomictempfile(object):
1175 class atomictempfile(object):
1173 '''writable file object that atomically updates a file
1176 '''writable file object that atomically updates a file
1174
1177
1175 All writes will go to a temporary copy of the original file. Call
1178 All writes will go to a temporary copy of the original file. Call
1176 close() when you are done writing, and atomictempfile will rename
1179 close() when you are done writing, and atomictempfile will rename
1177 the temporary copy to the original name, making the changes
1180 the temporary copy to the original name, making the changes
1178 visible. If the object is destroyed without being closed, all your
1181 visible. If the object is destroyed without being closed, all your
1179 writes are discarded.
1182 writes are discarded.
1180 '''
1183 '''
1181 def __init__(self, name, mode='w+b', createmode=None):
1184 def __init__(self, name, mode='w+b', createmode=None):
1182 self.__name = name # permanent name
1185 self.__name = name # permanent name
1183 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1186 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1184 createmode=createmode)
1187 createmode=createmode)
1185 self._fp = posixfile(self._tempname, mode)
1188 self._fp = posixfile(self._tempname, mode)
1186
1189
1187 # delegated methods
1190 # delegated methods
1188 self.write = self._fp.write
1191 self.write = self._fp.write
1189 self.seek = self._fp.seek
1192 self.seek = self._fp.seek
1190 self.tell = self._fp.tell
1193 self.tell = self._fp.tell
1191 self.fileno = self._fp.fileno
1194 self.fileno = self._fp.fileno
1192
1195
1193 def close(self):
1196 def close(self):
1194 if not self._fp.closed:
1197 if not self._fp.closed:
1195 self._fp.close()
1198 self._fp.close()
1196 rename(self._tempname, localpath(self.__name))
1199 rename(self._tempname, localpath(self.__name))
1197
1200
1198 def discard(self):
1201 def discard(self):
1199 if not self._fp.closed:
1202 if not self._fp.closed:
1200 try:
1203 try:
1201 os.unlink(self._tempname)
1204 os.unlink(self._tempname)
1202 except OSError:
1205 except OSError:
1203 pass
1206 pass
1204 self._fp.close()
1207 self._fp.close()
1205
1208
1206 def __del__(self):
1209 def __del__(self):
1207 if safehasattr(self, '_fp'): # constructor actually did something
1210 if safehasattr(self, '_fp'): # constructor actually did something
1208 self.discard()
1211 self.discard()
1209
1212
1210 def makedirs(name, mode=None, notindexed=False):
1213 def makedirs(name, mode=None, notindexed=False):
1211 """recursive directory creation with parent mode inheritance"""
1214 """recursive directory creation with parent mode inheritance"""
1212 try:
1215 try:
1213 makedir(name, notindexed)
1216 makedir(name, notindexed)
1214 except OSError as err:
1217 except OSError as err:
1215 if err.errno == errno.EEXIST:
1218 if err.errno == errno.EEXIST:
1216 return
1219 return
1217 if err.errno != errno.ENOENT or not name:
1220 if err.errno != errno.ENOENT or not name:
1218 raise
1221 raise
1219 parent = os.path.dirname(os.path.abspath(name))
1222 parent = os.path.dirname(os.path.abspath(name))
1220 if parent == name:
1223 if parent == name:
1221 raise
1224 raise
1222 makedirs(parent, mode, notindexed)
1225 makedirs(parent, mode, notindexed)
1223 makedir(name, notindexed)
1226 makedir(name, notindexed)
1224 if mode is not None:
1227 if mode is not None:
1225 os.chmod(name, mode)
1228 os.chmod(name, mode)
1226
1229
1227 def ensuredirs(name, mode=None, notindexed=False):
1230 def ensuredirs(name, mode=None, notindexed=False):
1228 """race-safe recursive directory creation
1231 """race-safe recursive directory creation
1229
1232
1230 Newly created directories are marked as "not to be indexed by
1233 Newly created directories are marked as "not to be indexed by
1231 the content indexing service", if ``notindexed`` is specified
1234 the content indexing service", if ``notindexed`` is specified
1232 for "write" mode access.
1235 for "write" mode access.
1233 """
1236 """
1234 if os.path.isdir(name):
1237 if os.path.isdir(name):
1235 return
1238 return
1236 parent = os.path.dirname(os.path.abspath(name))
1239 parent = os.path.dirname(os.path.abspath(name))
1237 if parent != name:
1240 if parent != name:
1238 ensuredirs(parent, mode, notindexed)
1241 ensuredirs(parent, mode, notindexed)
1239 try:
1242 try:
1240 makedir(name, notindexed)
1243 makedir(name, notindexed)
1241 except OSError as err:
1244 except OSError as err:
1242 if err.errno == errno.EEXIST and os.path.isdir(name):
1245 if err.errno == errno.EEXIST and os.path.isdir(name):
1243 # someone else seems to have won a directory creation race
1246 # someone else seems to have won a directory creation race
1244 return
1247 return
1245 raise
1248 raise
1246 if mode is not None:
1249 if mode is not None:
1247 os.chmod(name, mode)
1250 os.chmod(name, mode)
1248
1251
1249 def readfile(path):
1252 def readfile(path):
1250 fp = open(path, 'rb')
1253 fp = open(path, 'rb')
1251 try:
1254 try:
1252 return fp.read()
1255 return fp.read()
1253 finally:
1256 finally:
1254 fp.close()
1257 fp.close()
1255
1258
1256 def writefile(path, text):
1259 def writefile(path, text):
1257 fp = open(path, 'wb')
1260 fp = open(path, 'wb')
1258 try:
1261 try:
1259 fp.write(text)
1262 fp.write(text)
1260 finally:
1263 finally:
1261 fp.close()
1264 fp.close()
1262
1265
1263 def appendfile(path, text):
1266 def appendfile(path, text):
1264 fp = open(path, 'ab')
1267 fp = open(path, 'ab')
1265 try:
1268 try:
1266 fp.write(text)
1269 fp.write(text)
1267 finally:
1270 finally:
1268 fp.close()
1271 fp.close()
1269
1272
1270 class chunkbuffer(object):
1273 class chunkbuffer(object):
1271 """Allow arbitrary sized chunks of data to be efficiently read from an
1274 """Allow arbitrary sized chunks of data to be efficiently read from an
1272 iterator over chunks of arbitrary size."""
1275 iterator over chunks of arbitrary size."""
1273
1276
1274 def __init__(self, in_iter):
1277 def __init__(self, in_iter):
1275 """in_iter is the iterator that's iterating over the input chunks.
1278 """in_iter is the iterator that's iterating over the input chunks.
1276 targetsize is how big a buffer to try to maintain."""
1279 targetsize is how big a buffer to try to maintain."""
1277 def splitbig(chunks):
1280 def splitbig(chunks):
1278 for chunk in chunks:
1281 for chunk in chunks:
1279 if len(chunk) > 2**20:
1282 if len(chunk) > 2**20:
1280 pos = 0
1283 pos = 0
1281 while pos < len(chunk):
1284 while pos < len(chunk):
1282 end = pos + 2 ** 18
1285 end = pos + 2 ** 18
1283 yield chunk[pos:end]
1286 yield chunk[pos:end]
1284 pos = end
1287 pos = end
1285 else:
1288 else:
1286 yield chunk
1289 yield chunk
1287 self.iter = splitbig(in_iter)
1290 self.iter = splitbig(in_iter)
1288 self._queue = collections.deque()
1291 self._queue = collections.deque()
1289 self._chunkoffset = 0
1292 self._chunkoffset = 0
1290
1293
1291 def read(self, l=None):
1294 def read(self, l=None):
1292 """Read L bytes of data from the iterator of chunks of data.
1295 """Read L bytes of data from the iterator of chunks of data.
1293 Returns less than L bytes if the iterator runs dry.
1296 Returns less than L bytes if the iterator runs dry.
1294
1297
1295 If size parameter is omitted, read everything"""
1298 If size parameter is omitted, read everything"""
1296 if l is None:
1299 if l is None:
1297 return ''.join(self.iter)
1300 return ''.join(self.iter)
1298
1301
1299 left = l
1302 left = l
1300 buf = []
1303 buf = []
1301 queue = self._queue
1304 queue = self._queue
1302 while left > 0:
1305 while left > 0:
1303 # refill the queue
1306 # refill the queue
1304 if not queue:
1307 if not queue:
1305 target = 2**18
1308 target = 2**18
1306 for chunk in self.iter:
1309 for chunk in self.iter:
1307 queue.append(chunk)
1310 queue.append(chunk)
1308 target -= len(chunk)
1311 target -= len(chunk)
1309 if target <= 0:
1312 if target <= 0:
1310 break
1313 break
1311 if not queue:
1314 if not queue:
1312 break
1315 break
1313
1316
1314 # The easy way to do this would be to queue.popleft(), modify the
1317 # The easy way to do this would be to queue.popleft(), modify the
1315 # chunk (if necessary), then queue.appendleft(). However, for cases
1318 # chunk (if necessary), then queue.appendleft(). However, for cases
1316 # where we read partial chunk content, this incurs 2 dequeue
1319 # where we read partial chunk content, this incurs 2 dequeue
1317 # mutations and creates a new str for the remaining chunk in the
1320 # mutations and creates a new str for the remaining chunk in the
1318 # queue. Our code below avoids this overhead.
1321 # queue. Our code below avoids this overhead.
1319
1322
1320 chunk = queue[0]
1323 chunk = queue[0]
1321 chunkl = len(chunk)
1324 chunkl = len(chunk)
1322 offset = self._chunkoffset
1325 offset = self._chunkoffset
1323
1326
1324 # Use full chunk.
1327 # Use full chunk.
1325 if offset == 0 and left >= chunkl:
1328 if offset == 0 and left >= chunkl:
1326 left -= chunkl
1329 left -= chunkl
1327 queue.popleft()
1330 queue.popleft()
1328 buf.append(chunk)
1331 buf.append(chunk)
1329 # self._chunkoffset remains at 0.
1332 # self._chunkoffset remains at 0.
1330 continue
1333 continue
1331
1334
1332 chunkremaining = chunkl - offset
1335 chunkremaining = chunkl - offset
1333
1336
1334 # Use all of unconsumed part of chunk.
1337 # Use all of unconsumed part of chunk.
1335 if left >= chunkremaining:
1338 if left >= chunkremaining:
1336 left -= chunkremaining
1339 left -= chunkremaining
1337 queue.popleft()
1340 queue.popleft()
1338 # offset == 0 is enabled by block above, so this won't merely
1341 # offset == 0 is enabled by block above, so this won't merely
1339 # copy via ``chunk[0:]``.
1342 # copy via ``chunk[0:]``.
1340 buf.append(chunk[offset:])
1343 buf.append(chunk[offset:])
1341 self._chunkoffset = 0
1344 self._chunkoffset = 0
1342
1345
1343 # Partial chunk needed.
1346 # Partial chunk needed.
1344 else:
1347 else:
1345 buf.append(chunk[offset:offset + left])
1348 buf.append(chunk[offset:offset + left])
1346 self._chunkoffset += left
1349 self._chunkoffset += left
1347 left -= chunkremaining
1350 left -= chunkremaining
1348
1351
1349 return ''.join(buf)
1352 return ''.join(buf)
1350
1353
1351 def filechunkiter(f, size=65536, limit=None):
1354 def filechunkiter(f, size=65536, limit=None):
1352 """Create a generator that produces the data in the file size
1355 """Create a generator that produces the data in the file size
1353 (default 65536) bytes at a time, up to optional limit (default is
1356 (default 65536) bytes at a time, up to optional limit (default is
1354 to read all data). Chunks may be less than size bytes if the
1357 to read all data). Chunks may be less than size bytes if the
1355 chunk is the last chunk in the file, or the file is a socket or
1358 chunk is the last chunk in the file, or the file is a socket or
1356 some other type of file that sometimes reads less data than is
1359 some other type of file that sometimes reads less data than is
1357 requested."""
1360 requested."""
1358 assert size >= 0
1361 assert size >= 0
1359 assert limit is None or limit >= 0
1362 assert limit is None or limit >= 0
1360 while True:
1363 while True:
1361 if limit is None:
1364 if limit is None:
1362 nbytes = size
1365 nbytes = size
1363 else:
1366 else:
1364 nbytes = min(limit, size)
1367 nbytes = min(limit, size)
1365 s = nbytes and f.read(nbytes)
1368 s = nbytes and f.read(nbytes)
1366 if not s:
1369 if not s:
1367 break
1370 break
1368 if limit:
1371 if limit:
1369 limit -= len(s)
1372 limit -= len(s)
1370 yield s
1373 yield s
1371
1374
1372 def makedate(timestamp=None):
1375 def makedate(timestamp=None):
1373 '''Return a unix timestamp (or the current time) as a (unixtime,
1376 '''Return a unix timestamp (or the current time) as a (unixtime,
1374 offset) tuple based off the local timezone.'''
1377 offset) tuple based off the local timezone.'''
1375 if timestamp is None:
1378 if timestamp is None:
1376 timestamp = time.time()
1379 timestamp = time.time()
1377 if timestamp < 0:
1380 if timestamp < 0:
1378 hint = _("check your clock")
1381 hint = _("check your clock")
1379 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1382 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1380 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1383 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1381 datetime.datetime.fromtimestamp(timestamp))
1384 datetime.datetime.fromtimestamp(timestamp))
1382 tz = delta.days * 86400 + delta.seconds
1385 tz = delta.days * 86400 + delta.seconds
1383 return timestamp, tz
1386 return timestamp, tz
1384
1387
1385 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1388 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1386 """represent a (unixtime, offset) tuple as a localized time.
1389 """represent a (unixtime, offset) tuple as a localized time.
1387 unixtime is seconds since the epoch, and offset is the time zone's
1390 unixtime is seconds since the epoch, and offset is the time zone's
1388 number of seconds away from UTC. if timezone is false, do not
1391 number of seconds away from UTC. if timezone is false, do not
1389 append time zone to string."""
1392 append time zone to string."""
1390 t, tz = date or makedate()
1393 t, tz = date or makedate()
1391 if t < 0:
1394 if t < 0:
1392 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1395 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1393 tz = 0
1396 tz = 0
1394 if "%1" in format or "%2" in format or "%z" in format:
1397 if "%1" in format or "%2" in format or "%z" in format:
1395 sign = (tz > 0) and "-" or "+"
1398 sign = (tz > 0) and "-" or "+"
1396 minutes = abs(tz) // 60
1399 minutes = abs(tz) // 60
1397 format = format.replace("%z", "%1%2")
1400 format = format.replace("%z", "%1%2")
1398 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1401 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1399 format = format.replace("%2", "%02d" % (minutes % 60))
1402 format = format.replace("%2", "%02d" % (minutes % 60))
1400 try:
1403 try:
1401 t = time.gmtime(float(t) - tz)
1404 t = time.gmtime(float(t) - tz)
1402 except ValueError:
1405 except ValueError:
1403 # time was out of range
1406 # time was out of range
1404 t = time.gmtime(sys.maxint)
1407 t = time.gmtime(sys.maxint)
1405 s = time.strftime(format, t)
1408 s = time.strftime(format, t)
1406 return s
1409 return s
1407
1410
1408 def shortdate(date=None):
1411 def shortdate(date=None):
1409 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1412 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1410 return datestr(date, format='%Y-%m-%d')
1413 return datestr(date, format='%Y-%m-%d')
1411
1414
1412 def parsetimezone(tz):
1415 def parsetimezone(tz):
1413 """parse a timezone string and return an offset integer"""
1416 """parse a timezone string and return an offset integer"""
1414 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1417 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1415 sign = (tz[0] == "+") and 1 or -1
1418 sign = (tz[0] == "+") and 1 or -1
1416 hours = int(tz[1:3])
1419 hours = int(tz[1:3])
1417 minutes = int(tz[3:5])
1420 minutes = int(tz[3:5])
1418 return -sign * (hours * 60 + minutes) * 60
1421 return -sign * (hours * 60 + minutes) * 60
1419 if tz == "GMT" or tz == "UTC":
1422 if tz == "GMT" or tz == "UTC":
1420 return 0
1423 return 0
1421 return None
1424 return None
1422
1425
1423 def strdate(string, format, defaults=[]):
1426 def strdate(string, format, defaults=[]):
1424 """parse a localized time string and return a (unixtime, offset) tuple.
1427 """parse a localized time string and return a (unixtime, offset) tuple.
1425 if the string cannot be parsed, ValueError is raised."""
1428 if the string cannot be parsed, ValueError is raised."""
1426 # NOTE: unixtime = localunixtime + offset
1429 # NOTE: unixtime = localunixtime + offset
1427 offset, date = parsetimezone(string.split()[-1]), string
1430 offset, date = parsetimezone(string.split()[-1]), string
1428 if offset is not None:
1431 if offset is not None:
1429 date = " ".join(string.split()[:-1])
1432 date = " ".join(string.split()[:-1])
1430
1433
1431 # add missing elements from defaults
1434 # add missing elements from defaults
1432 usenow = False # default to using biased defaults
1435 usenow = False # default to using biased defaults
1433 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1436 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1434 found = [True for p in part if ("%"+p) in format]
1437 found = [True for p in part if ("%"+p) in format]
1435 if not found:
1438 if not found:
1436 date += "@" + defaults[part][usenow]
1439 date += "@" + defaults[part][usenow]
1437 format += "@%" + part[0]
1440 format += "@%" + part[0]
1438 else:
1441 else:
1439 # We've found a specific time element, less specific time
1442 # We've found a specific time element, less specific time
1440 # elements are relative to today
1443 # elements are relative to today
1441 usenow = True
1444 usenow = True
1442
1445
1443 timetuple = time.strptime(date, format)
1446 timetuple = time.strptime(date, format)
1444 localunixtime = int(calendar.timegm(timetuple))
1447 localunixtime = int(calendar.timegm(timetuple))
1445 if offset is None:
1448 if offset is None:
1446 # local timezone
1449 # local timezone
1447 unixtime = int(time.mktime(timetuple))
1450 unixtime = int(time.mktime(timetuple))
1448 offset = unixtime - localunixtime
1451 offset = unixtime - localunixtime
1449 else:
1452 else:
1450 unixtime = localunixtime + offset
1453 unixtime = localunixtime + offset
1451 return unixtime, offset
1454 return unixtime, offset
1452
1455
1453 def parsedate(date, formats=None, bias=None):
1456 def parsedate(date, formats=None, bias=None):
1454 """parse a localized date/time and return a (unixtime, offset) tuple.
1457 """parse a localized date/time and return a (unixtime, offset) tuple.
1455
1458
1456 The date may be a "unixtime offset" string or in one of the specified
1459 The date may be a "unixtime offset" string or in one of the specified
1457 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1460 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1458
1461
1459 >>> parsedate(' today ') == parsedate(\
1462 >>> parsedate(' today ') == parsedate(\
1460 datetime.date.today().strftime('%b %d'))
1463 datetime.date.today().strftime('%b %d'))
1461 True
1464 True
1462 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1465 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1463 datetime.timedelta(days=1)\
1466 datetime.timedelta(days=1)\
1464 ).strftime('%b %d'))
1467 ).strftime('%b %d'))
1465 True
1468 True
1466 >>> now, tz = makedate()
1469 >>> now, tz = makedate()
1467 >>> strnow, strtz = parsedate('now')
1470 >>> strnow, strtz = parsedate('now')
1468 >>> (strnow - now) < 1
1471 >>> (strnow - now) < 1
1469 True
1472 True
1470 >>> tz == strtz
1473 >>> tz == strtz
1471 True
1474 True
1472 """
1475 """
1473 if bias is None:
1476 if bias is None:
1474 bias = {}
1477 bias = {}
1475 if not date:
1478 if not date:
1476 return 0, 0
1479 return 0, 0
1477 if isinstance(date, tuple) and len(date) == 2:
1480 if isinstance(date, tuple) and len(date) == 2:
1478 return date
1481 return date
1479 if not formats:
1482 if not formats:
1480 formats = defaultdateformats
1483 formats = defaultdateformats
1481 date = date.strip()
1484 date = date.strip()
1482
1485
1483 if date == 'now' or date == _('now'):
1486 if date == 'now' or date == _('now'):
1484 return makedate()
1487 return makedate()
1485 if date == 'today' or date == _('today'):
1488 if date == 'today' or date == _('today'):
1486 date = datetime.date.today().strftime('%b %d')
1489 date = datetime.date.today().strftime('%b %d')
1487 elif date == 'yesterday' or date == _('yesterday'):
1490 elif date == 'yesterday' or date == _('yesterday'):
1488 date = (datetime.date.today() -
1491 date = (datetime.date.today() -
1489 datetime.timedelta(days=1)).strftime('%b %d')
1492 datetime.timedelta(days=1)).strftime('%b %d')
1490
1493
1491 try:
1494 try:
1492 when, offset = map(int, date.split(' '))
1495 when, offset = map(int, date.split(' '))
1493 except ValueError:
1496 except ValueError:
1494 # fill out defaults
1497 # fill out defaults
1495 now = makedate()
1498 now = makedate()
1496 defaults = {}
1499 defaults = {}
1497 for part in ("d", "mb", "yY", "HI", "M", "S"):
1500 for part in ("d", "mb", "yY", "HI", "M", "S"):
1498 # this piece is for rounding the specific end of unknowns
1501 # this piece is for rounding the specific end of unknowns
1499 b = bias.get(part)
1502 b = bias.get(part)
1500 if b is None:
1503 if b is None:
1501 if part[0] in "HMS":
1504 if part[0] in "HMS":
1502 b = "00"
1505 b = "00"
1503 else:
1506 else:
1504 b = "0"
1507 b = "0"
1505
1508
1506 # this piece is for matching the generic end to today's date
1509 # this piece is for matching the generic end to today's date
1507 n = datestr(now, "%" + part[0])
1510 n = datestr(now, "%" + part[0])
1508
1511
1509 defaults[part] = (b, n)
1512 defaults[part] = (b, n)
1510
1513
1511 for format in formats:
1514 for format in formats:
1512 try:
1515 try:
1513 when, offset = strdate(date, format, defaults)
1516 when, offset = strdate(date, format, defaults)
1514 except (ValueError, OverflowError):
1517 except (ValueError, OverflowError):
1515 pass
1518 pass
1516 else:
1519 else:
1517 break
1520 break
1518 else:
1521 else:
1519 raise Abort(_('invalid date: %r') % date)
1522 raise Abort(_('invalid date: %r') % date)
1520 # validate explicit (probably user-specified) date and
1523 # validate explicit (probably user-specified) date and
1521 # time zone offset. values must fit in signed 32 bits for
1524 # time zone offset. values must fit in signed 32 bits for
1522 # current 32-bit linux runtimes. timezones go from UTC-12
1525 # current 32-bit linux runtimes. timezones go from UTC-12
1523 # to UTC+14
1526 # to UTC+14
1524 if abs(when) > 0x7fffffff:
1527 if abs(when) > 0x7fffffff:
1525 raise Abort(_('date exceeds 32 bits: %d') % when)
1528 raise Abort(_('date exceeds 32 bits: %d') % when)
1526 if when < 0:
1529 if when < 0:
1527 raise Abort(_('negative date value: %d') % when)
1530 raise Abort(_('negative date value: %d') % when)
1528 if offset < -50400 or offset > 43200:
1531 if offset < -50400 or offset > 43200:
1529 raise Abort(_('impossible time zone offset: %d') % offset)
1532 raise Abort(_('impossible time zone offset: %d') % offset)
1530 return when, offset
1533 return when, offset
1531
1534
1532 def matchdate(date):
1535 def matchdate(date):
1533 """Return a function that matches a given date match specifier
1536 """Return a function that matches a given date match specifier
1534
1537
1535 Formats include:
1538 Formats include:
1536
1539
1537 '{date}' match a given date to the accuracy provided
1540 '{date}' match a given date to the accuracy provided
1538
1541
1539 '<{date}' on or before a given date
1542 '<{date}' on or before a given date
1540
1543
1541 '>{date}' on or after a given date
1544 '>{date}' on or after a given date
1542
1545
1543 >>> p1 = parsedate("10:29:59")
1546 >>> p1 = parsedate("10:29:59")
1544 >>> p2 = parsedate("10:30:00")
1547 >>> p2 = parsedate("10:30:00")
1545 >>> p3 = parsedate("10:30:59")
1548 >>> p3 = parsedate("10:30:59")
1546 >>> p4 = parsedate("10:31:00")
1549 >>> p4 = parsedate("10:31:00")
1547 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1550 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1548 >>> f = matchdate("10:30")
1551 >>> f = matchdate("10:30")
1549 >>> f(p1[0])
1552 >>> f(p1[0])
1550 False
1553 False
1551 >>> f(p2[0])
1554 >>> f(p2[0])
1552 True
1555 True
1553 >>> f(p3[0])
1556 >>> f(p3[0])
1554 True
1557 True
1555 >>> f(p4[0])
1558 >>> f(p4[0])
1556 False
1559 False
1557 >>> f(p5[0])
1560 >>> f(p5[0])
1558 False
1561 False
1559 """
1562 """
1560
1563
1561 def lower(date):
1564 def lower(date):
1562 d = {'mb': "1", 'd': "1"}
1565 d = {'mb': "1", 'd': "1"}
1563 return parsedate(date, extendeddateformats, d)[0]
1566 return parsedate(date, extendeddateformats, d)[0]
1564
1567
1565 def upper(date):
1568 def upper(date):
1566 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1569 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1567 for days in ("31", "30", "29"):
1570 for days in ("31", "30", "29"):
1568 try:
1571 try:
1569 d["d"] = days
1572 d["d"] = days
1570 return parsedate(date, extendeddateformats, d)[0]
1573 return parsedate(date, extendeddateformats, d)[0]
1571 except Abort:
1574 except Abort:
1572 pass
1575 pass
1573 d["d"] = "28"
1576 d["d"] = "28"
1574 return parsedate(date, extendeddateformats, d)[0]
1577 return parsedate(date, extendeddateformats, d)[0]
1575
1578
1576 date = date.strip()
1579 date = date.strip()
1577
1580
1578 if not date:
1581 if not date:
1579 raise Abort(_("dates cannot consist entirely of whitespace"))
1582 raise Abort(_("dates cannot consist entirely of whitespace"))
1580 elif date[0] == "<":
1583 elif date[0] == "<":
1581 if not date[1:]:
1584 if not date[1:]:
1582 raise Abort(_("invalid day spec, use '<DATE'"))
1585 raise Abort(_("invalid day spec, use '<DATE'"))
1583 when = upper(date[1:])
1586 when = upper(date[1:])
1584 return lambda x: x <= when
1587 return lambda x: x <= when
1585 elif date[0] == ">":
1588 elif date[0] == ">":
1586 if not date[1:]:
1589 if not date[1:]:
1587 raise Abort(_("invalid day spec, use '>DATE'"))
1590 raise Abort(_("invalid day spec, use '>DATE'"))
1588 when = lower(date[1:])
1591 when = lower(date[1:])
1589 return lambda x: x >= when
1592 return lambda x: x >= when
1590 elif date[0] == "-":
1593 elif date[0] == "-":
1591 try:
1594 try:
1592 days = int(date[1:])
1595 days = int(date[1:])
1593 except ValueError:
1596 except ValueError:
1594 raise Abort(_("invalid day spec: %s") % date[1:])
1597 raise Abort(_("invalid day spec: %s") % date[1:])
1595 if days < 0:
1598 if days < 0:
1596 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1599 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1597 % date[1:])
1600 % date[1:])
1598 when = makedate()[0] - days * 3600 * 24
1601 when = makedate()[0] - days * 3600 * 24
1599 return lambda x: x >= when
1602 return lambda x: x >= when
1600 elif " to " in date:
1603 elif " to " in date:
1601 a, b = date.split(" to ")
1604 a, b = date.split(" to ")
1602 start, stop = lower(a), upper(b)
1605 start, stop = lower(a), upper(b)
1603 return lambda x: x >= start and x <= stop
1606 return lambda x: x >= start and x <= stop
1604 else:
1607 else:
1605 start, stop = lower(date), upper(date)
1608 start, stop = lower(date), upper(date)
1606 return lambda x: x >= start and x <= stop
1609 return lambda x: x >= start and x <= stop
1607
1610
1608 def stringmatcher(pattern):
1611 def stringmatcher(pattern):
1609 """
1612 """
1610 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1613 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1611 returns the matcher name, pattern, and matcher function.
1614 returns the matcher name, pattern, and matcher function.
1612 missing or unknown prefixes are treated as literal matches.
1615 missing or unknown prefixes are treated as literal matches.
1613
1616
1614 helper for tests:
1617 helper for tests:
1615 >>> def test(pattern, *tests):
1618 >>> def test(pattern, *tests):
1616 ... kind, pattern, matcher = stringmatcher(pattern)
1619 ... kind, pattern, matcher = stringmatcher(pattern)
1617 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1620 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1618
1621
1619 exact matching (no prefix):
1622 exact matching (no prefix):
1620 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1623 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1621 ('literal', 'abcdefg', [False, False, True])
1624 ('literal', 'abcdefg', [False, False, True])
1622
1625
1623 regex matching ('re:' prefix)
1626 regex matching ('re:' prefix)
1624 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1627 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1625 ('re', 'a.+b', [False, False, True])
1628 ('re', 'a.+b', [False, False, True])
1626
1629
1627 force exact matches ('literal:' prefix)
1630 force exact matches ('literal:' prefix)
1628 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1631 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1629 ('literal', 're:foobar', [False, True])
1632 ('literal', 're:foobar', [False, True])
1630
1633
1631 unknown prefixes are ignored and treated as literals
1634 unknown prefixes are ignored and treated as literals
1632 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1635 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1633 ('literal', 'foo:bar', [False, False, True])
1636 ('literal', 'foo:bar', [False, False, True])
1634 """
1637 """
1635 if pattern.startswith('re:'):
1638 if pattern.startswith('re:'):
1636 pattern = pattern[3:]
1639 pattern = pattern[3:]
1637 try:
1640 try:
1638 regex = remod.compile(pattern)
1641 regex = remod.compile(pattern)
1639 except remod.error as e:
1642 except remod.error as e:
1640 raise error.ParseError(_('invalid regular expression: %s')
1643 raise error.ParseError(_('invalid regular expression: %s')
1641 % e)
1644 % e)
1642 return 're', pattern, regex.search
1645 return 're', pattern, regex.search
1643 elif pattern.startswith('literal:'):
1646 elif pattern.startswith('literal:'):
1644 pattern = pattern[8:]
1647 pattern = pattern[8:]
1645 return 'literal', pattern, pattern.__eq__
1648 return 'literal', pattern, pattern.__eq__
1646
1649
1647 def shortuser(user):
1650 def shortuser(user):
1648 """Return a short representation of a user name or email address."""
1651 """Return a short representation of a user name or email address."""
1649 f = user.find('@')
1652 f = user.find('@')
1650 if f >= 0:
1653 if f >= 0:
1651 user = user[:f]
1654 user = user[:f]
1652 f = user.find('<')
1655 f = user.find('<')
1653 if f >= 0:
1656 if f >= 0:
1654 user = user[f + 1:]
1657 user = user[f + 1:]
1655 f = user.find(' ')
1658 f = user.find(' ')
1656 if f >= 0:
1659 if f >= 0:
1657 user = user[:f]
1660 user = user[:f]
1658 f = user.find('.')
1661 f = user.find('.')
1659 if f >= 0:
1662 if f >= 0:
1660 user = user[:f]
1663 user = user[:f]
1661 return user
1664 return user
1662
1665
1663 def emailuser(user):
1666 def emailuser(user):
1664 """Return the user portion of an email address."""
1667 """Return the user portion of an email address."""
1665 f = user.find('@')
1668 f = user.find('@')
1666 if f >= 0:
1669 if f >= 0:
1667 user = user[:f]
1670 user = user[:f]
1668 f = user.find('<')
1671 f = user.find('<')
1669 if f >= 0:
1672 if f >= 0:
1670 user = user[f + 1:]
1673 user = user[f + 1:]
1671 return user
1674 return user
1672
1675
1673 def email(author):
1676 def email(author):
1674 '''get email of author.'''
1677 '''get email of author.'''
1675 r = author.find('>')
1678 r = author.find('>')
1676 if r == -1:
1679 if r == -1:
1677 r = None
1680 r = None
1678 return author[author.find('<') + 1:r]
1681 return author[author.find('<') + 1:r]
1679
1682
1680 def ellipsis(text, maxlength=400):
1683 def ellipsis(text, maxlength=400):
1681 """Trim string to at most maxlength (default: 400) columns in display."""
1684 """Trim string to at most maxlength (default: 400) columns in display."""
1682 return encoding.trim(text, maxlength, ellipsis='...')
1685 return encoding.trim(text, maxlength, ellipsis='...')
1683
1686
1684 def unitcountfn(*unittable):
1687 def unitcountfn(*unittable):
1685 '''return a function that renders a readable count of some quantity'''
1688 '''return a function that renders a readable count of some quantity'''
1686
1689
1687 def go(count):
1690 def go(count):
1688 for multiplier, divisor, format in unittable:
1691 for multiplier, divisor, format in unittable:
1689 if count >= divisor * multiplier:
1692 if count >= divisor * multiplier:
1690 return format % (count / float(divisor))
1693 return format % (count / float(divisor))
1691 return unittable[-1][2] % count
1694 return unittable[-1][2] % count
1692
1695
1693 return go
1696 return go
1694
1697
1695 bytecount = unitcountfn(
1698 bytecount = unitcountfn(
1696 (100, 1 << 30, _('%.0f GB')),
1699 (100, 1 << 30, _('%.0f GB')),
1697 (10, 1 << 30, _('%.1f GB')),
1700 (10, 1 << 30, _('%.1f GB')),
1698 (1, 1 << 30, _('%.2f GB')),
1701 (1, 1 << 30, _('%.2f GB')),
1699 (100, 1 << 20, _('%.0f MB')),
1702 (100, 1 << 20, _('%.0f MB')),
1700 (10, 1 << 20, _('%.1f MB')),
1703 (10, 1 << 20, _('%.1f MB')),
1701 (1, 1 << 20, _('%.2f MB')),
1704 (1, 1 << 20, _('%.2f MB')),
1702 (100, 1 << 10, _('%.0f KB')),
1705 (100, 1 << 10, _('%.0f KB')),
1703 (10, 1 << 10, _('%.1f KB')),
1706 (10, 1 << 10, _('%.1f KB')),
1704 (1, 1 << 10, _('%.2f KB')),
1707 (1, 1 << 10, _('%.2f KB')),
1705 (1, 1, _('%.0f bytes')),
1708 (1, 1, _('%.0f bytes')),
1706 )
1709 )
1707
1710
1708 def uirepr(s):
1711 def uirepr(s):
1709 # Avoid double backslash in Windows path repr()
1712 # Avoid double backslash in Windows path repr()
1710 return repr(s).replace('\\\\', '\\')
1713 return repr(s).replace('\\\\', '\\')
1711
1714
1712 # delay import of textwrap
1715 # delay import of textwrap
1713 def MBTextWrapper(**kwargs):
1716 def MBTextWrapper(**kwargs):
1714 class tw(textwrap.TextWrapper):
1717 class tw(textwrap.TextWrapper):
1715 """
1718 """
1716 Extend TextWrapper for width-awareness.
1719 Extend TextWrapper for width-awareness.
1717
1720
1718 Neither number of 'bytes' in any encoding nor 'characters' is
1721 Neither number of 'bytes' in any encoding nor 'characters' is
1719 appropriate to calculate terminal columns for specified string.
1722 appropriate to calculate terminal columns for specified string.
1720
1723
1721 Original TextWrapper implementation uses built-in 'len()' directly,
1724 Original TextWrapper implementation uses built-in 'len()' directly,
1722 so overriding is needed to use width information of each characters.
1725 so overriding is needed to use width information of each characters.
1723
1726
1724 In addition, characters classified into 'ambiguous' width are
1727 In addition, characters classified into 'ambiguous' width are
1725 treated as wide in East Asian area, but as narrow in other.
1728 treated as wide in East Asian area, but as narrow in other.
1726
1729
1727 This requires use decision to determine width of such characters.
1730 This requires use decision to determine width of such characters.
1728 """
1731 """
1729 def _cutdown(self, ucstr, space_left):
1732 def _cutdown(self, ucstr, space_left):
1730 l = 0
1733 l = 0
1731 colwidth = encoding.ucolwidth
1734 colwidth = encoding.ucolwidth
1732 for i in xrange(len(ucstr)):
1735 for i in xrange(len(ucstr)):
1733 l += colwidth(ucstr[i])
1736 l += colwidth(ucstr[i])
1734 if space_left < l:
1737 if space_left < l:
1735 return (ucstr[:i], ucstr[i:])
1738 return (ucstr[:i], ucstr[i:])
1736 return ucstr, ''
1739 return ucstr, ''
1737
1740
1738 # overriding of base class
1741 # overriding of base class
1739 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1742 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1740 space_left = max(width - cur_len, 1)
1743 space_left = max(width - cur_len, 1)
1741
1744
1742 if self.break_long_words:
1745 if self.break_long_words:
1743 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1746 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1744 cur_line.append(cut)
1747 cur_line.append(cut)
1745 reversed_chunks[-1] = res
1748 reversed_chunks[-1] = res
1746 elif not cur_line:
1749 elif not cur_line:
1747 cur_line.append(reversed_chunks.pop())
1750 cur_line.append(reversed_chunks.pop())
1748
1751
1749 # this overriding code is imported from TextWrapper of Python 2.6
1752 # this overriding code is imported from TextWrapper of Python 2.6
1750 # to calculate columns of string by 'encoding.ucolwidth()'
1753 # to calculate columns of string by 'encoding.ucolwidth()'
1751 def _wrap_chunks(self, chunks):
1754 def _wrap_chunks(self, chunks):
1752 colwidth = encoding.ucolwidth
1755 colwidth = encoding.ucolwidth
1753
1756
1754 lines = []
1757 lines = []
1755 if self.width <= 0:
1758 if self.width <= 0:
1756 raise ValueError("invalid width %r (must be > 0)" % self.width)
1759 raise ValueError("invalid width %r (must be > 0)" % self.width)
1757
1760
1758 # Arrange in reverse order so items can be efficiently popped
1761 # Arrange in reverse order so items can be efficiently popped
1759 # from a stack of chucks.
1762 # from a stack of chucks.
1760 chunks.reverse()
1763 chunks.reverse()
1761
1764
1762 while chunks:
1765 while chunks:
1763
1766
1764 # Start the list of chunks that will make up the current line.
1767 # Start the list of chunks that will make up the current line.
1765 # cur_len is just the length of all the chunks in cur_line.
1768 # cur_len is just the length of all the chunks in cur_line.
1766 cur_line = []
1769 cur_line = []
1767 cur_len = 0
1770 cur_len = 0
1768
1771
1769 # Figure out which static string will prefix this line.
1772 # Figure out which static string will prefix this line.
1770 if lines:
1773 if lines:
1771 indent = self.subsequent_indent
1774 indent = self.subsequent_indent
1772 else:
1775 else:
1773 indent = self.initial_indent
1776 indent = self.initial_indent
1774
1777
1775 # Maximum width for this line.
1778 # Maximum width for this line.
1776 width = self.width - len(indent)
1779 width = self.width - len(indent)
1777
1780
1778 # First chunk on line is whitespace -- drop it, unless this
1781 # First chunk on line is whitespace -- drop it, unless this
1779 # is the very beginning of the text (i.e. no lines started yet).
1782 # is the very beginning of the text (i.e. no lines started yet).
1780 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1783 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1781 del chunks[-1]
1784 del chunks[-1]
1782
1785
1783 while chunks:
1786 while chunks:
1784 l = colwidth(chunks[-1])
1787 l = colwidth(chunks[-1])
1785
1788
1786 # Can at least squeeze this chunk onto the current line.
1789 # Can at least squeeze this chunk onto the current line.
1787 if cur_len + l <= width:
1790 if cur_len + l <= width:
1788 cur_line.append(chunks.pop())
1791 cur_line.append(chunks.pop())
1789 cur_len += l
1792 cur_len += l
1790
1793
1791 # Nope, this line is full.
1794 # Nope, this line is full.
1792 else:
1795 else:
1793 break
1796 break
1794
1797
1795 # The current line is full, and the next chunk is too big to
1798 # The current line is full, and the next chunk is too big to
1796 # fit on *any* line (not just this one).
1799 # fit on *any* line (not just this one).
1797 if chunks and colwidth(chunks[-1]) > width:
1800 if chunks and colwidth(chunks[-1]) > width:
1798 self._handle_long_word(chunks, cur_line, cur_len, width)
1801 self._handle_long_word(chunks, cur_line, cur_len, width)
1799
1802
1800 # If the last chunk on this line is all whitespace, drop it.
1803 # If the last chunk on this line is all whitespace, drop it.
1801 if (self.drop_whitespace and
1804 if (self.drop_whitespace and
1802 cur_line and cur_line[-1].strip() == ''):
1805 cur_line and cur_line[-1].strip() == ''):
1803 del cur_line[-1]
1806 del cur_line[-1]
1804
1807
1805 # Convert current line back to a string and store it in list
1808 # Convert current line back to a string and store it in list
1806 # of all lines (return value).
1809 # of all lines (return value).
1807 if cur_line:
1810 if cur_line:
1808 lines.append(indent + ''.join(cur_line))
1811 lines.append(indent + ''.join(cur_line))
1809
1812
1810 return lines
1813 return lines
1811
1814
1812 global MBTextWrapper
1815 global MBTextWrapper
1813 MBTextWrapper = tw
1816 MBTextWrapper = tw
1814 return tw(**kwargs)
1817 return tw(**kwargs)
1815
1818
1816 def wrap(line, width, initindent='', hangindent=''):
1819 def wrap(line, width, initindent='', hangindent=''):
1817 maxindent = max(len(hangindent), len(initindent))
1820 maxindent = max(len(hangindent), len(initindent))
1818 if width <= maxindent:
1821 if width <= maxindent:
1819 # adjust for weird terminal size
1822 # adjust for weird terminal size
1820 width = max(78, maxindent + 1)
1823 width = max(78, maxindent + 1)
1821 line = line.decode(encoding.encoding, encoding.encodingmode)
1824 line = line.decode(encoding.encoding, encoding.encodingmode)
1822 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1825 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1823 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1826 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1824 wrapper = MBTextWrapper(width=width,
1827 wrapper = MBTextWrapper(width=width,
1825 initial_indent=initindent,
1828 initial_indent=initindent,
1826 subsequent_indent=hangindent)
1829 subsequent_indent=hangindent)
1827 return wrapper.fill(line).encode(encoding.encoding)
1830 return wrapper.fill(line).encode(encoding.encoding)
1828
1831
1829 def iterlines(iterator):
1832 def iterlines(iterator):
1830 for chunk in iterator:
1833 for chunk in iterator:
1831 for line in chunk.splitlines():
1834 for line in chunk.splitlines():
1832 yield line
1835 yield line
1833
1836
1834 def expandpath(path):
1837 def expandpath(path):
1835 return os.path.expanduser(os.path.expandvars(path))
1838 return os.path.expanduser(os.path.expandvars(path))
1836
1839
1837 def hgcmd():
1840 def hgcmd():
1838 """Return the command used to execute current hg
1841 """Return the command used to execute current hg
1839
1842
1840 This is different from hgexecutable() because on Windows we want
1843 This is different from hgexecutable() because on Windows we want
1841 to avoid things opening new shell windows like batch files, so we
1844 to avoid things opening new shell windows like batch files, so we
1842 get either the python call or current executable.
1845 get either the python call or current executable.
1843 """
1846 """
1844 if mainfrozen():
1847 if mainfrozen():
1845 return [sys.executable]
1848 return [sys.executable]
1846 return gethgcmd()
1849 return gethgcmd()
1847
1850
1848 def rundetached(args, condfn):
1851 def rundetached(args, condfn):
1849 """Execute the argument list in a detached process.
1852 """Execute the argument list in a detached process.
1850
1853
1851 condfn is a callable which is called repeatedly and should return
1854 condfn is a callable which is called repeatedly and should return
1852 True once the child process is known to have started successfully.
1855 True once the child process is known to have started successfully.
1853 At this point, the child process PID is returned. If the child
1856 At this point, the child process PID is returned. If the child
1854 process fails to start or finishes before condfn() evaluates to
1857 process fails to start or finishes before condfn() evaluates to
1855 True, return -1.
1858 True, return -1.
1856 """
1859 """
1857 # Windows case is easier because the child process is either
1860 # Windows case is easier because the child process is either
1858 # successfully starting and validating the condition or exiting
1861 # successfully starting and validating the condition or exiting
1859 # on failure. We just poll on its PID. On Unix, if the child
1862 # on failure. We just poll on its PID. On Unix, if the child
1860 # process fails to start, it will be left in a zombie state until
1863 # process fails to start, it will be left in a zombie state until
1861 # the parent wait on it, which we cannot do since we expect a long
1864 # the parent wait on it, which we cannot do since we expect a long
1862 # running process on success. Instead we listen for SIGCHLD telling
1865 # running process on success. Instead we listen for SIGCHLD telling
1863 # us our child process terminated.
1866 # us our child process terminated.
1864 terminated = set()
1867 terminated = set()
1865 def handler(signum, frame):
1868 def handler(signum, frame):
1866 terminated.add(os.wait())
1869 terminated.add(os.wait())
1867 prevhandler = None
1870 prevhandler = None
1868 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1871 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1869 if SIGCHLD is not None:
1872 if SIGCHLD is not None:
1870 prevhandler = signal.signal(SIGCHLD, handler)
1873 prevhandler = signal.signal(SIGCHLD, handler)
1871 try:
1874 try:
1872 pid = spawndetached(args)
1875 pid = spawndetached(args)
1873 while not condfn():
1876 while not condfn():
1874 if ((pid in terminated or not testpid(pid))
1877 if ((pid in terminated or not testpid(pid))
1875 and not condfn()):
1878 and not condfn()):
1876 return -1
1879 return -1
1877 time.sleep(0.1)
1880 time.sleep(0.1)
1878 return pid
1881 return pid
1879 finally:
1882 finally:
1880 if prevhandler is not None:
1883 if prevhandler is not None:
1881 signal.signal(signal.SIGCHLD, prevhandler)
1884 signal.signal(signal.SIGCHLD, prevhandler)
1882
1885
1883 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1886 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1884 """Return the result of interpolating items in the mapping into string s.
1887 """Return the result of interpolating items in the mapping into string s.
1885
1888
1886 prefix is a single character string, or a two character string with
1889 prefix is a single character string, or a two character string with
1887 a backslash as the first character if the prefix needs to be escaped in
1890 a backslash as the first character if the prefix needs to be escaped in
1888 a regular expression.
1891 a regular expression.
1889
1892
1890 fn is an optional function that will be applied to the replacement text
1893 fn is an optional function that will be applied to the replacement text
1891 just before replacement.
1894 just before replacement.
1892
1895
1893 escape_prefix is an optional flag that allows using doubled prefix for
1896 escape_prefix is an optional flag that allows using doubled prefix for
1894 its escaping.
1897 its escaping.
1895 """
1898 """
1896 fn = fn or (lambda s: s)
1899 fn = fn or (lambda s: s)
1897 patterns = '|'.join(mapping.keys())
1900 patterns = '|'.join(mapping.keys())
1898 if escape_prefix:
1901 if escape_prefix:
1899 patterns += '|' + prefix
1902 patterns += '|' + prefix
1900 if len(prefix) > 1:
1903 if len(prefix) > 1:
1901 prefix_char = prefix[1:]
1904 prefix_char = prefix[1:]
1902 else:
1905 else:
1903 prefix_char = prefix
1906 prefix_char = prefix
1904 mapping[prefix_char] = prefix_char
1907 mapping[prefix_char] = prefix_char
1905 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1908 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1906 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1909 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1907
1910
1908 def getport(port):
1911 def getport(port):
1909 """Return the port for a given network service.
1912 """Return the port for a given network service.
1910
1913
1911 If port is an integer, it's returned as is. If it's a string, it's
1914 If port is an integer, it's returned as is. If it's a string, it's
1912 looked up using socket.getservbyname(). If there's no matching
1915 looked up using socket.getservbyname(). If there's no matching
1913 service, util.Abort is raised.
1916 service, util.Abort is raised.
1914 """
1917 """
1915 try:
1918 try:
1916 return int(port)
1919 return int(port)
1917 except ValueError:
1920 except ValueError:
1918 pass
1921 pass
1919
1922
1920 try:
1923 try:
1921 return socket.getservbyname(port)
1924 return socket.getservbyname(port)
1922 except socket.error:
1925 except socket.error:
1923 raise Abort(_("no port number associated with service '%s'") % port)
1926 raise Abort(_("no port number associated with service '%s'") % port)
1924
1927
1925 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1928 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1926 '0': False, 'no': False, 'false': False, 'off': False,
1929 '0': False, 'no': False, 'false': False, 'off': False,
1927 'never': False}
1930 'never': False}
1928
1931
1929 def parsebool(s):
1932 def parsebool(s):
1930 """Parse s into a boolean.
1933 """Parse s into a boolean.
1931
1934
1932 If s is not a valid boolean, returns None.
1935 If s is not a valid boolean, returns None.
1933 """
1936 """
1934 return _booleans.get(s.lower(), None)
1937 return _booleans.get(s.lower(), None)
1935
1938
1936 _hexdig = '0123456789ABCDEFabcdef'
1939 _hexdig = '0123456789ABCDEFabcdef'
1937 _hextochr = dict((a + b, chr(int(a + b, 16)))
1940 _hextochr = dict((a + b, chr(int(a + b, 16)))
1938 for a in _hexdig for b in _hexdig)
1941 for a in _hexdig for b in _hexdig)
1939
1942
1940 def _urlunquote(s):
1943 def _urlunquote(s):
1941 """Decode HTTP/HTML % encoding.
1944 """Decode HTTP/HTML % encoding.
1942
1945
1943 >>> _urlunquote('abc%20def')
1946 >>> _urlunquote('abc%20def')
1944 'abc def'
1947 'abc def'
1945 """
1948 """
1946 res = s.split('%')
1949 res = s.split('%')
1947 # fastpath
1950 # fastpath
1948 if len(res) == 1:
1951 if len(res) == 1:
1949 return s
1952 return s
1950 s = res[0]
1953 s = res[0]
1951 for item in res[1:]:
1954 for item in res[1:]:
1952 try:
1955 try:
1953 s += _hextochr[item[:2]] + item[2:]
1956 s += _hextochr[item[:2]] + item[2:]
1954 except KeyError:
1957 except KeyError:
1955 s += '%' + item
1958 s += '%' + item
1956 except UnicodeDecodeError:
1959 except UnicodeDecodeError:
1957 s += unichr(int(item[:2], 16)) + item[2:]
1960 s += unichr(int(item[:2], 16)) + item[2:]
1958 return s
1961 return s
1959
1962
1960 class url(object):
1963 class url(object):
1961 r"""Reliable URL parser.
1964 r"""Reliable URL parser.
1962
1965
1963 This parses URLs and provides attributes for the following
1966 This parses URLs and provides attributes for the following
1964 components:
1967 components:
1965
1968
1966 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1969 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1967
1970
1968 Missing components are set to None. The only exception is
1971 Missing components are set to None. The only exception is
1969 fragment, which is set to '' if present but empty.
1972 fragment, which is set to '' if present but empty.
1970
1973
1971 If parsefragment is False, fragment is included in query. If
1974 If parsefragment is False, fragment is included in query. If
1972 parsequery is False, query is included in path. If both are
1975 parsequery is False, query is included in path. If both are
1973 False, both fragment and query are included in path.
1976 False, both fragment and query are included in path.
1974
1977
1975 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1978 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1976
1979
1977 Note that for backward compatibility reasons, bundle URLs do not
1980 Note that for backward compatibility reasons, bundle URLs do not
1978 take host names. That means 'bundle://../' has a path of '../'.
1981 take host names. That means 'bundle://../' has a path of '../'.
1979
1982
1980 Examples:
1983 Examples:
1981
1984
1982 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1985 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1983 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1986 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1984 >>> url('ssh://[::1]:2200//home/joe/repo')
1987 >>> url('ssh://[::1]:2200//home/joe/repo')
1985 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1988 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1986 >>> url('file:///home/joe/repo')
1989 >>> url('file:///home/joe/repo')
1987 <url scheme: 'file', path: '/home/joe/repo'>
1990 <url scheme: 'file', path: '/home/joe/repo'>
1988 >>> url('file:///c:/temp/foo/')
1991 >>> url('file:///c:/temp/foo/')
1989 <url scheme: 'file', path: 'c:/temp/foo/'>
1992 <url scheme: 'file', path: 'c:/temp/foo/'>
1990 >>> url('bundle:foo')
1993 >>> url('bundle:foo')
1991 <url scheme: 'bundle', path: 'foo'>
1994 <url scheme: 'bundle', path: 'foo'>
1992 >>> url('bundle://../foo')
1995 >>> url('bundle://../foo')
1993 <url scheme: 'bundle', path: '../foo'>
1996 <url scheme: 'bundle', path: '../foo'>
1994 >>> url(r'c:\foo\bar')
1997 >>> url(r'c:\foo\bar')
1995 <url path: 'c:\\foo\\bar'>
1998 <url path: 'c:\\foo\\bar'>
1996 >>> url(r'\\blah\blah\blah')
1999 >>> url(r'\\blah\blah\blah')
1997 <url path: '\\\\blah\\blah\\blah'>
2000 <url path: '\\\\blah\\blah\\blah'>
1998 >>> url(r'\\blah\blah\blah#baz')
2001 >>> url(r'\\blah\blah\blah#baz')
1999 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2002 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2000 >>> url(r'file:///C:\users\me')
2003 >>> url(r'file:///C:\users\me')
2001 <url scheme: 'file', path: 'C:\\users\\me'>
2004 <url scheme: 'file', path: 'C:\\users\\me'>
2002
2005
2003 Authentication credentials:
2006 Authentication credentials:
2004
2007
2005 >>> url('ssh://joe:xyz@x/repo')
2008 >>> url('ssh://joe:xyz@x/repo')
2006 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2009 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2007 >>> url('ssh://joe@x/repo')
2010 >>> url('ssh://joe@x/repo')
2008 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2011 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2009
2012
2010 Query strings and fragments:
2013 Query strings and fragments:
2011
2014
2012 >>> url('http://host/a?b#c')
2015 >>> url('http://host/a?b#c')
2013 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2016 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2014 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2017 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2015 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2018 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2016 """
2019 """
2017
2020
2018 _safechars = "!~*'()+"
2021 _safechars = "!~*'()+"
2019 _safepchars = "/!~*'()+:\\"
2022 _safepchars = "/!~*'()+:\\"
2020 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2023 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2021
2024
2022 def __init__(self, path, parsequery=True, parsefragment=True):
2025 def __init__(self, path, parsequery=True, parsefragment=True):
2023 # We slowly chomp away at path until we have only the path left
2026 # We slowly chomp away at path until we have only the path left
2024 self.scheme = self.user = self.passwd = self.host = None
2027 self.scheme = self.user = self.passwd = self.host = None
2025 self.port = self.path = self.query = self.fragment = None
2028 self.port = self.path = self.query = self.fragment = None
2026 self._localpath = True
2029 self._localpath = True
2027 self._hostport = ''
2030 self._hostport = ''
2028 self._origpath = path
2031 self._origpath = path
2029
2032
2030 if parsefragment and '#' in path:
2033 if parsefragment and '#' in path:
2031 path, self.fragment = path.split('#', 1)
2034 path, self.fragment = path.split('#', 1)
2032 if not path:
2035 if not path:
2033 path = None
2036 path = None
2034
2037
2035 # special case for Windows drive letters and UNC paths
2038 # special case for Windows drive letters and UNC paths
2036 if hasdriveletter(path) or path.startswith(r'\\'):
2039 if hasdriveletter(path) or path.startswith(r'\\'):
2037 self.path = path
2040 self.path = path
2038 return
2041 return
2039
2042
2040 # For compatibility reasons, we can't handle bundle paths as
2043 # For compatibility reasons, we can't handle bundle paths as
2041 # normal URLS
2044 # normal URLS
2042 if path.startswith('bundle:'):
2045 if path.startswith('bundle:'):
2043 self.scheme = 'bundle'
2046 self.scheme = 'bundle'
2044 path = path[7:]
2047 path = path[7:]
2045 if path.startswith('//'):
2048 if path.startswith('//'):
2046 path = path[2:]
2049 path = path[2:]
2047 self.path = path
2050 self.path = path
2048 return
2051 return
2049
2052
2050 if self._matchscheme(path):
2053 if self._matchscheme(path):
2051 parts = path.split(':', 1)
2054 parts = path.split(':', 1)
2052 if parts[0]:
2055 if parts[0]:
2053 self.scheme, path = parts
2056 self.scheme, path = parts
2054 self._localpath = False
2057 self._localpath = False
2055
2058
2056 if not path:
2059 if not path:
2057 path = None
2060 path = None
2058 if self._localpath:
2061 if self._localpath:
2059 self.path = ''
2062 self.path = ''
2060 return
2063 return
2061 else:
2064 else:
2062 if self._localpath:
2065 if self._localpath:
2063 self.path = path
2066 self.path = path
2064 return
2067 return
2065
2068
2066 if parsequery and '?' in path:
2069 if parsequery and '?' in path:
2067 path, self.query = path.split('?', 1)
2070 path, self.query = path.split('?', 1)
2068 if not path:
2071 if not path:
2069 path = None
2072 path = None
2070 if not self.query:
2073 if not self.query:
2071 self.query = None
2074 self.query = None
2072
2075
2073 # // is required to specify a host/authority
2076 # // is required to specify a host/authority
2074 if path and path.startswith('//'):
2077 if path and path.startswith('//'):
2075 parts = path[2:].split('/', 1)
2078 parts = path[2:].split('/', 1)
2076 if len(parts) > 1:
2079 if len(parts) > 1:
2077 self.host, path = parts
2080 self.host, path = parts
2078 else:
2081 else:
2079 self.host = parts[0]
2082 self.host = parts[0]
2080 path = None
2083 path = None
2081 if not self.host:
2084 if not self.host:
2082 self.host = None
2085 self.host = None
2083 # path of file:///d is /d
2086 # path of file:///d is /d
2084 # path of file:///d:/ is d:/, not /d:/
2087 # path of file:///d:/ is d:/, not /d:/
2085 if path and not hasdriveletter(path):
2088 if path and not hasdriveletter(path):
2086 path = '/' + path
2089 path = '/' + path
2087
2090
2088 if self.host and '@' in self.host:
2091 if self.host and '@' in self.host:
2089 self.user, self.host = self.host.rsplit('@', 1)
2092 self.user, self.host = self.host.rsplit('@', 1)
2090 if ':' in self.user:
2093 if ':' in self.user:
2091 self.user, self.passwd = self.user.split(':', 1)
2094 self.user, self.passwd = self.user.split(':', 1)
2092 if not self.host:
2095 if not self.host:
2093 self.host = None
2096 self.host = None
2094
2097
2095 # Don't split on colons in IPv6 addresses without ports
2098 # Don't split on colons in IPv6 addresses without ports
2096 if (self.host and ':' in self.host and
2099 if (self.host and ':' in self.host and
2097 not (self.host.startswith('[') and self.host.endswith(']'))):
2100 not (self.host.startswith('[') and self.host.endswith(']'))):
2098 self._hostport = self.host
2101 self._hostport = self.host
2099 self.host, self.port = self.host.rsplit(':', 1)
2102 self.host, self.port = self.host.rsplit(':', 1)
2100 if not self.host:
2103 if not self.host:
2101 self.host = None
2104 self.host = None
2102
2105
2103 if (self.host and self.scheme == 'file' and
2106 if (self.host and self.scheme == 'file' and
2104 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2107 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2105 raise Abort(_('file:// URLs can only refer to localhost'))
2108 raise Abort(_('file:// URLs can only refer to localhost'))
2106
2109
2107 self.path = path
2110 self.path = path
2108
2111
2109 # leave the query string escaped
2112 # leave the query string escaped
2110 for a in ('user', 'passwd', 'host', 'port',
2113 for a in ('user', 'passwd', 'host', 'port',
2111 'path', 'fragment'):
2114 'path', 'fragment'):
2112 v = getattr(self, a)
2115 v = getattr(self, a)
2113 if v is not None:
2116 if v is not None:
2114 setattr(self, a, _urlunquote(v))
2117 setattr(self, a, _urlunquote(v))
2115
2118
2116 def __repr__(self):
2119 def __repr__(self):
2117 attrs = []
2120 attrs = []
2118 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2121 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2119 'query', 'fragment'):
2122 'query', 'fragment'):
2120 v = getattr(self, a)
2123 v = getattr(self, a)
2121 if v is not None:
2124 if v is not None:
2122 attrs.append('%s: %r' % (a, v))
2125 attrs.append('%s: %r' % (a, v))
2123 return '<url %s>' % ', '.join(attrs)
2126 return '<url %s>' % ', '.join(attrs)
2124
2127
2125 def __str__(self):
2128 def __str__(self):
2126 r"""Join the URL's components back into a URL string.
2129 r"""Join the URL's components back into a URL string.
2127
2130
2128 Examples:
2131 Examples:
2129
2132
2130 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2133 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2131 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2134 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2132 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2135 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2133 'http://user:pw@host:80/?foo=bar&baz=42'
2136 'http://user:pw@host:80/?foo=bar&baz=42'
2134 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2137 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2135 'http://user:pw@host:80/?foo=bar%3dbaz'
2138 'http://user:pw@host:80/?foo=bar%3dbaz'
2136 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2139 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2137 'ssh://user:pw@[::1]:2200//home/joe#'
2140 'ssh://user:pw@[::1]:2200//home/joe#'
2138 >>> str(url('http://localhost:80//'))
2141 >>> str(url('http://localhost:80//'))
2139 'http://localhost:80//'
2142 'http://localhost:80//'
2140 >>> str(url('http://localhost:80/'))
2143 >>> str(url('http://localhost:80/'))
2141 'http://localhost:80/'
2144 'http://localhost:80/'
2142 >>> str(url('http://localhost:80'))
2145 >>> str(url('http://localhost:80'))
2143 'http://localhost:80/'
2146 'http://localhost:80/'
2144 >>> str(url('bundle:foo'))
2147 >>> str(url('bundle:foo'))
2145 'bundle:foo'
2148 'bundle:foo'
2146 >>> str(url('bundle://../foo'))
2149 >>> str(url('bundle://../foo'))
2147 'bundle:../foo'
2150 'bundle:../foo'
2148 >>> str(url('path'))
2151 >>> str(url('path'))
2149 'path'
2152 'path'
2150 >>> str(url('file:///tmp/foo/bar'))
2153 >>> str(url('file:///tmp/foo/bar'))
2151 'file:///tmp/foo/bar'
2154 'file:///tmp/foo/bar'
2152 >>> str(url('file:///c:/tmp/foo/bar'))
2155 >>> str(url('file:///c:/tmp/foo/bar'))
2153 'file:///c:/tmp/foo/bar'
2156 'file:///c:/tmp/foo/bar'
2154 >>> print url(r'bundle:foo\bar')
2157 >>> print url(r'bundle:foo\bar')
2155 bundle:foo\bar
2158 bundle:foo\bar
2156 >>> print url(r'file:///D:\data\hg')
2159 >>> print url(r'file:///D:\data\hg')
2157 file:///D:\data\hg
2160 file:///D:\data\hg
2158 """
2161 """
2159 if self._localpath:
2162 if self._localpath:
2160 s = self.path
2163 s = self.path
2161 if self.scheme == 'bundle':
2164 if self.scheme == 'bundle':
2162 s = 'bundle:' + s
2165 s = 'bundle:' + s
2163 if self.fragment:
2166 if self.fragment:
2164 s += '#' + self.fragment
2167 s += '#' + self.fragment
2165 return s
2168 return s
2166
2169
2167 s = self.scheme + ':'
2170 s = self.scheme + ':'
2168 if self.user or self.passwd or self.host:
2171 if self.user or self.passwd or self.host:
2169 s += '//'
2172 s += '//'
2170 elif self.scheme and (not self.path or self.path.startswith('/')
2173 elif self.scheme and (not self.path or self.path.startswith('/')
2171 or hasdriveletter(self.path)):
2174 or hasdriveletter(self.path)):
2172 s += '//'
2175 s += '//'
2173 if hasdriveletter(self.path):
2176 if hasdriveletter(self.path):
2174 s += '/'
2177 s += '/'
2175 if self.user:
2178 if self.user:
2176 s += urllib.quote(self.user, safe=self._safechars)
2179 s += urllib.quote(self.user, safe=self._safechars)
2177 if self.passwd:
2180 if self.passwd:
2178 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2181 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2179 if self.user or self.passwd:
2182 if self.user or self.passwd:
2180 s += '@'
2183 s += '@'
2181 if self.host:
2184 if self.host:
2182 if not (self.host.startswith('[') and self.host.endswith(']')):
2185 if not (self.host.startswith('[') and self.host.endswith(']')):
2183 s += urllib.quote(self.host)
2186 s += urllib.quote(self.host)
2184 else:
2187 else:
2185 s += self.host
2188 s += self.host
2186 if self.port:
2189 if self.port:
2187 s += ':' + urllib.quote(self.port)
2190 s += ':' + urllib.quote(self.port)
2188 if self.host:
2191 if self.host:
2189 s += '/'
2192 s += '/'
2190 if self.path:
2193 if self.path:
2191 # TODO: similar to the query string, we should not unescape the
2194 # TODO: similar to the query string, we should not unescape the
2192 # path when we store it, the path might contain '%2f' = '/',
2195 # path when we store it, the path might contain '%2f' = '/',
2193 # which we should *not* escape.
2196 # which we should *not* escape.
2194 s += urllib.quote(self.path, safe=self._safepchars)
2197 s += urllib.quote(self.path, safe=self._safepchars)
2195 if self.query:
2198 if self.query:
2196 # we store the query in escaped form.
2199 # we store the query in escaped form.
2197 s += '?' + self.query
2200 s += '?' + self.query
2198 if self.fragment is not None:
2201 if self.fragment is not None:
2199 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2202 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2200 return s
2203 return s
2201
2204
2202 def authinfo(self):
2205 def authinfo(self):
2203 user, passwd = self.user, self.passwd
2206 user, passwd = self.user, self.passwd
2204 try:
2207 try:
2205 self.user, self.passwd = None, None
2208 self.user, self.passwd = None, None
2206 s = str(self)
2209 s = str(self)
2207 finally:
2210 finally:
2208 self.user, self.passwd = user, passwd
2211 self.user, self.passwd = user, passwd
2209 if not self.user:
2212 if not self.user:
2210 return (s, None)
2213 return (s, None)
2211 # authinfo[1] is passed to urllib2 password manager, and its
2214 # authinfo[1] is passed to urllib2 password manager, and its
2212 # URIs must not contain credentials. The host is passed in the
2215 # URIs must not contain credentials. The host is passed in the
2213 # URIs list because Python < 2.4.3 uses only that to search for
2216 # URIs list because Python < 2.4.3 uses only that to search for
2214 # a password.
2217 # a password.
2215 return (s, (None, (s, self.host),
2218 return (s, (None, (s, self.host),
2216 self.user, self.passwd or ''))
2219 self.user, self.passwd or ''))
2217
2220
2218 def isabs(self):
2221 def isabs(self):
2219 if self.scheme and self.scheme != 'file':
2222 if self.scheme and self.scheme != 'file':
2220 return True # remote URL
2223 return True # remote URL
2221 if hasdriveletter(self.path):
2224 if hasdriveletter(self.path):
2222 return True # absolute for our purposes - can't be joined()
2225 return True # absolute for our purposes - can't be joined()
2223 if self.path.startswith(r'\\'):
2226 if self.path.startswith(r'\\'):
2224 return True # Windows UNC path
2227 return True # Windows UNC path
2225 if self.path.startswith('/'):
2228 if self.path.startswith('/'):
2226 return True # POSIX-style
2229 return True # POSIX-style
2227 return False
2230 return False
2228
2231
2229 def localpath(self):
2232 def localpath(self):
2230 if self.scheme == 'file' or self.scheme == 'bundle':
2233 if self.scheme == 'file' or self.scheme == 'bundle':
2231 path = self.path or '/'
2234 path = self.path or '/'
2232 # For Windows, we need to promote hosts containing drive
2235 # For Windows, we need to promote hosts containing drive
2233 # letters to paths with drive letters.
2236 # letters to paths with drive letters.
2234 if hasdriveletter(self._hostport):
2237 if hasdriveletter(self._hostport):
2235 path = self._hostport + '/' + self.path
2238 path = self._hostport + '/' + self.path
2236 elif (self.host is not None and self.path
2239 elif (self.host is not None and self.path
2237 and not hasdriveletter(path)):
2240 and not hasdriveletter(path)):
2238 path = '/' + path
2241 path = '/' + path
2239 return path
2242 return path
2240 return self._origpath
2243 return self._origpath
2241
2244
2242 def islocal(self):
2245 def islocal(self):
2243 '''whether localpath will return something that posixfile can open'''
2246 '''whether localpath will return something that posixfile can open'''
2244 return (not self.scheme or self.scheme == 'file'
2247 return (not self.scheme or self.scheme == 'file'
2245 or self.scheme == 'bundle')
2248 or self.scheme == 'bundle')
2246
2249
2247 def hasscheme(path):
2250 def hasscheme(path):
2248 return bool(url(path).scheme)
2251 return bool(url(path).scheme)
2249
2252
2250 def hasdriveletter(path):
2253 def hasdriveletter(path):
2251 return path and path[1:2] == ':' and path[0:1].isalpha()
2254 return path and path[1:2] == ':' and path[0:1].isalpha()
2252
2255
2253 def urllocalpath(path):
2256 def urllocalpath(path):
2254 return url(path, parsequery=False, parsefragment=False).localpath()
2257 return url(path, parsequery=False, parsefragment=False).localpath()
2255
2258
2256 def hidepassword(u):
2259 def hidepassword(u):
2257 '''hide user credential in a url string'''
2260 '''hide user credential in a url string'''
2258 u = url(u)
2261 u = url(u)
2259 if u.passwd:
2262 if u.passwd:
2260 u.passwd = '***'
2263 u.passwd = '***'
2261 return str(u)
2264 return str(u)
2262
2265
2263 def removeauth(u):
2266 def removeauth(u):
2264 '''remove all authentication information from a url string'''
2267 '''remove all authentication information from a url string'''
2265 u = url(u)
2268 u = url(u)
2266 u.user = u.passwd = None
2269 u.user = u.passwd = None
2267 return str(u)
2270 return str(u)
2268
2271
2269 def isatty(fd):
2272 def isatty(fd):
2270 try:
2273 try:
2271 return fd.isatty()
2274 return fd.isatty()
2272 except AttributeError:
2275 except AttributeError:
2273 return False
2276 return False
2274
2277
2275 timecount = unitcountfn(
2278 timecount = unitcountfn(
2276 (1, 1e3, _('%.0f s')),
2279 (1, 1e3, _('%.0f s')),
2277 (100, 1, _('%.1f s')),
2280 (100, 1, _('%.1f s')),
2278 (10, 1, _('%.2f s')),
2281 (10, 1, _('%.2f s')),
2279 (1, 1, _('%.3f s')),
2282 (1, 1, _('%.3f s')),
2280 (100, 0.001, _('%.1f ms')),
2283 (100, 0.001, _('%.1f ms')),
2281 (10, 0.001, _('%.2f ms')),
2284 (10, 0.001, _('%.2f ms')),
2282 (1, 0.001, _('%.3f ms')),
2285 (1, 0.001, _('%.3f ms')),
2283 (100, 0.000001, _('%.1f us')),
2286 (100, 0.000001, _('%.1f us')),
2284 (10, 0.000001, _('%.2f us')),
2287 (10, 0.000001, _('%.2f us')),
2285 (1, 0.000001, _('%.3f us')),
2288 (1, 0.000001, _('%.3f us')),
2286 (100, 0.000000001, _('%.1f ns')),
2289 (100, 0.000000001, _('%.1f ns')),
2287 (10, 0.000000001, _('%.2f ns')),
2290 (10, 0.000000001, _('%.2f ns')),
2288 (1, 0.000000001, _('%.3f ns')),
2291 (1, 0.000000001, _('%.3f ns')),
2289 )
2292 )
2290
2293
2291 _timenesting = [0]
2294 _timenesting = [0]
2292
2295
2293 def timed(func):
2296 def timed(func):
2294 '''Report the execution time of a function call to stderr.
2297 '''Report the execution time of a function call to stderr.
2295
2298
2296 During development, use as a decorator when you need to measure
2299 During development, use as a decorator when you need to measure
2297 the cost of a function, e.g. as follows:
2300 the cost of a function, e.g. as follows:
2298
2301
2299 @util.timed
2302 @util.timed
2300 def foo(a, b, c):
2303 def foo(a, b, c):
2301 pass
2304 pass
2302 '''
2305 '''
2303
2306
2304 def wrapper(*args, **kwargs):
2307 def wrapper(*args, **kwargs):
2305 start = time.time()
2308 start = time.time()
2306 indent = 2
2309 indent = 2
2307 _timenesting[0] += indent
2310 _timenesting[0] += indent
2308 try:
2311 try:
2309 return func(*args, **kwargs)
2312 return func(*args, **kwargs)
2310 finally:
2313 finally:
2311 elapsed = time.time() - start
2314 elapsed = time.time() - start
2312 _timenesting[0] -= indent
2315 _timenesting[0] -= indent
2313 sys.stderr.write('%s%s: %s\n' %
2316 sys.stderr.write('%s%s: %s\n' %
2314 (' ' * _timenesting[0], func.__name__,
2317 (' ' * _timenesting[0], func.__name__,
2315 timecount(elapsed)))
2318 timecount(elapsed)))
2316 return wrapper
2319 return wrapper
2317
2320
2318 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2321 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2319 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2322 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2320
2323
2321 def sizetoint(s):
2324 def sizetoint(s):
2322 '''Convert a space specifier to a byte count.
2325 '''Convert a space specifier to a byte count.
2323
2326
2324 >>> sizetoint('30')
2327 >>> sizetoint('30')
2325 30
2328 30
2326 >>> sizetoint('2.2kb')
2329 >>> sizetoint('2.2kb')
2327 2252
2330 2252
2328 >>> sizetoint('6M')
2331 >>> sizetoint('6M')
2329 6291456
2332 6291456
2330 '''
2333 '''
2331 t = s.strip().lower()
2334 t = s.strip().lower()
2332 try:
2335 try:
2333 for k, u in _sizeunits:
2336 for k, u in _sizeunits:
2334 if t.endswith(k):
2337 if t.endswith(k):
2335 return int(float(t[:-len(k)]) * u)
2338 return int(float(t[:-len(k)]) * u)
2336 return int(t)
2339 return int(t)
2337 except ValueError:
2340 except ValueError:
2338 raise error.ParseError(_("couldn't parse size: %s") % s)
2341 raise error.ParseError(_("couldn't parse size: %s") % s)
2339
2342
2340 class hooks(object):
2343 class hooks(object):
2341 '''A collection of hook functions that can be used to extend a
2344 '''A collection of hook functions that can be used to extend a
2342 function's behavior. Hooks are called in lexicographic order,
2345 function's behavior. Hooks are called in lexicographic order,
2343 based on the names of their sources.'''
2346 based on the names of their sources.'''
2344
2347
2345 def __init__(self):
2348 def __init__(self):
2346 self._hooks = []
2349 self._hooks = []
2347
2350
2348 def add(self, source, hook):
2351 def add(self, source, hook):
2349 self._hooks.append((source, hook))
2352 self._hooks.append((source, hook))
2350
2353
2351 def __call__(self, *args):
2354 def __call__(self, *args):
2352 self._hooks.sort(key=lambda x: x[0])
2355 self._hooks.sort(key=lambda x: x[0])
2353 results = []
2356 results = []
2354 for source, hook in self._hooks:
2357 for source, hook in self._hooks:
2355 results.append(hook(*args))
2358 results.append(hook(*args))
2356 return results
2359 return results
2357
2360
2358 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2361 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2359 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2362 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2360 Skips the 'skip' last entries. By default it will flush stdout first.
2363 Skips the 'skip' last entries. By default it will flush stdout first.
2361 It can be used everywhere and do intentionally not require an ui object.
2364 It can be used everywhere and do intentionally not require an ui object.
2362 Not be used in production code but very convenient while developing.
2365 Not be used in production code but very convenient while developing.
2363 '''
2366 '''
2364 if otherf:
2367 if otherf:
2365 otherf.flush()
2368 otherf.flush()
2366 f.write('%s at:\n' % msg)
2369 f.write('%s at:\n' % msg)
2367 entries = [('%s:%s' % (fn, ln), func)
2370 entries = [('%s:%s' % (fn, ln), func)
2368 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2371 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2369 if entries:
2372 if entries:
2370 fnmax = max(len(entry[0]) for entry in entries)
2373 fnmax = max(len(entry[0]) for entry in entries)
2371 for fnln, func in entries:
2374 for fnln, func in entries:
2372 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2375 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2373 f.flush()
2376 f.flush()
2374
2377
2375 class dirs(object):
2378 class dirs(object):
2376 '''a multiset of directory names from a dirstate or manifest'''
2379 '''a multiset of directory names from a dirstate or manifest'''
2377
2380
2378 def __init__(self, map, skip=None):
2381 def __init__(self, map, skip=None):
2379 self._dirs = {}
2382 self._dirs = {}
2380 addpath = self.addpath
2383 addpath = self.addpath
2381 if safehasattr(map, 'iteritems') and skip is not None:
2384 if safehasattr(map, 'iteritems') and skip is not None:
2382 for f, s in map.iteritems():
2385 for f, s in map.iteritems():
2383 if s[0] != skip:
2386 if s[0] != skip:
2384 addpath(f)
2387 addpath(f)
2385 else:
2388 else:
2386 for f in map:
2389 for f in map:
2387 addpath(f)
2390 addpath(f)
2388
2391
2389 def addpath(self, path):
2392 def addpath(self, path):
2390 dirs = self._dirs
2393 dirs = self._dirs
2391 for base in finddirs(path):
2394 for base in finddirs(path):
2392 if base in dirs:
2395 if base in dirs:
2393 dirs[base] += 1
2396 dirs[base] += 1
2394 return
2397 return
2395 dirs[base] = 1
2398 dirs[base] = 1
2396
2399
2397 def delpath(self, path):
2400 def delpath(self, path):
2398 dirs = self._dirs
2401 dirs = self._dirs
2399 for base in finddirs(path):
2402 for base in finddirs(path):
2400 if dirs[base] > 1:
2403 if dirs[base] > 1:
2401 dirs[base] -= 1
2404 dirs[base] -= 1
2402 return
2405 return
2403 del dirs[base]
2406 del dirs[base]
2404
2407
2405 def __iter__(self):
2408 def __iter__(self):
2406 return self._dirs.iterkeys()
2409 return self._dirs.iterkeys()
2407
2410
2408 def __contains__(self, d):
2411 def __contains__(self, d):
2409 return d in self._dirs
2412 return d in self._dirs
2410
2413
2411 if safehasattr(parsers, 'dirs'):
2414 if safehasattr(parsers, 'dirs'):
2412 dirs = parsers.dirs
2415 dirs = parsers.dirs
2413
2416
2414 def finddirs(path):
2417 def finddirs(path):
2415 pos = path.rfind('/')
2418 pos = path.rfind('/')
2416 while pos != -1:
2419 while pos != -1:
2417 yield path[:pos]
2420 yield path[:pos]
2418 pos = path.rfind('/', 0, pos)
2421 pos = path.rfind('/', 0, pos)
2419
2422
2420 # compression utility
2423 # compression utility
2421
2424
2422 class nocompress(object):
2425 class nocompress(object):
2423 def compress(self, x):
2426 def compress(self, x):
2424 return x
2427 return x
2425 def flush(self):
2428 def flush(self):
2426 return ""
2429 return ""
2427
2430
2428 compressors = {
2431 compressors = {
2429 None: nocompress,
2432 None: nocompress,
2430 # lambda to prevent early import
2433 # lambda to prevent early import
2431 'BZ': lambda: bz2.BZ2Compressor(),
2434 'BZ': lambda: bz2.BZ2Compressor(),
2432 'GZ': lambda: zlib.compressobj(),
2435 'GZ': lambda: zlib.compressobj(),
2433 }
2436 }
2434 # also support the old form by courtesies
2437 # also support the old form by courtesies
2435 compressors['UN'] = compressors[None]
2438 compressors['UN'] = compressors[None]
2436
2439
2437 def _makedecompressor(decompcls):
2440 def _makedecompressor(decompcls):
2438 def generator(f):
2441 def generator(f):
2439 d = decompcls()
2442 d = decompcls()
2440 for chunk in filechunkiter(f):
2443 for chunk in filechunkiter(f):
2441 yield d.decompress(chunk)
2444 yield d.decompress(chunk)
2442 def func(fh):
2445 def func(fh):
2443 return chunkbuffer(generator(fh))
2446 return chunkbuffer(generator(fh))
2444 return func
2447 return func
2445
2448
2446 def _bz2():
2449 def _bz2():
2447 d = bz2.BZ2Decompressor()
2450 d = bz2.BZ2Decompressor()
2448 # Bzip2 stream start with BZ, but we stripped it.
2451 # Bzip2 stream start with BZ, but we stripped it.
2449 # we put it back for good measure.
2452 # we put it back for good measure.
2450 d.decompress('BZ')
2453 d.decompress('BZ')
2451 return d
2454 return d
2452
2455
2453 decompressors = {None: lambda fh: fh,
2456 decompressors = {None: lambda fh: fh,
2454 '_truncatedBZ': _makedecompressor(_bz2),
2457 '_truncatedBZ': _makedecompressor(_bz2),
2455 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2458 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2456 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2459 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2457 }
2460 }
2458 # also support the old form by courtesies
2461 # also support the old form by courtesies
2459 decompressors['UN'] = decompressors[None]
2462 decompressors['UN'] = decompressors[None]
2460
2463
2461 # convenient shortcut
2464 # convenient shortcut
2462 dst = debugstacktrace
2465 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now