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