##// END OF EJS Templates
scmutil: speed up new-style range extension...
Bryan O'Sullivan -
r17037:41996612 default
parent child Browse files
Show More
@@ -1,887 +1,886 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import util, error, osutil, revset, similar, encoding
9 import util, error, osutil, revset, similar, encoding
10 import match as matchmod
10 import match as matchmod
11 import os, errno, re, stat, sys, glob
11 import os, errno, re, stat, sys, glob
12
12
13 def nochangesfound(ui, secretlist=None):
13 def nochangesfound(ui, secretlist=None):
14 '''report no changes for push/pull'''
14 '''report no changes for push/pull'''
15 if secretlist:
15 if secretlist:
16 ui.status(_("no changes found (ignored %d secret changesets)\n")
16 ui.status(_("no changes found (ignored %d secret changesets)\n")
17 % len(secretlist))
17 % len(secretlist))
18 else:
18 else:
19 ui.status(_("no changes found\n"))
19 ui.status(_("no changes found\n"))
20
20
21 def checkfilename(f):
21 def checkfilename(f):
22 '''Check that the filename f is an acceptable filename for a tracked file'''
22 '''Check that the filename f is an acceptable filename for a tracked file'''
23 if '\r' in f or '\n' in f:
23 if '\r' in f or '\n' in f:
24 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
24 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
25
25
26 def checkportable(ui, f):
26 def checkportable(ui, f):
27 '''Check if filename f is portable and warn or abort depending on config'''
27 '''Check if filename f is portable and warn or abort depending on config'''
28 checkfilename(f)
28 checkfilename(f)
29 abort, warn = checkportabilityalert(ui)
29 abort, warn = checkportabilityalert(ui)
30 if abort or warn:
30 if abort or warn:
31 msg = util.checkwinfilename(f)
31 msg = util.checkwinfilename(f)
32 if msg:
32 if msg:
33 msg = "%s: %r" % (msg, f)
33 msg = "%s: %r" % (msg, f)
34 if abort:
34 if abort:
35 raise util.Abort(msg)
35 raise util.Abort(msg)
36 ui.warn(_("warning: %s\n") % msg)
36 ui.warn(_("warning: %s\n") % msg)
37
37
38 def checkportabilityalert(ui):
38 def checkportabilityalert(ui):
39 '''check if the user's config requests nothing, a warning, or abort for
39 '''check if the user's config requests nothing, a warning, or abort for
40 non-portable filenames'''
40 non-portable filenames'''
41 val = ui.config('ui', 'portablefilenames', 'warn')
41 val = ui.config('ui', 'portablefilenames', 'warn')
42 lval = val.lower()
42 lval = val.lower()
43 bval = util.parsebool(val)
43 bval = util.parsebool(val)
44 abort = os.name == 'nt' or lval == 'abort'
44 abort = os.name == 'nt' or lval == 'abort'
45 warn = bval or lval == 'warn'
45 warn = bval or lval == 'warn'
46 if bval is None and not (warn or abort or lval == 'ignore'):
46 if bval is None and not (warn or abort or lval == 'ignore'):
47 raise error.ConfigError(
47 raise error.ConfigError(
48 _("ui.portablefilenames value is invalid ('%s')") % val)
48 _("ui.portablefilenames value is invalid ('%s')") % val)
49 return abort, warn
49 return abort, warn
50
50
51 class casecollisionauditor(object):
51 class casecollisionauditor(object):
52 def __init__(self, ui, abort, existingiter):
52 def __init__(self, ui, abort, existingiter):
53 self._ui = ui
53 self._ui = ui
54 self._abort = abort
54 self._abort = abort
55 self._map = {}
55 self._map = {}
56 for f in existingiter:
56 for f in existingiter:
57 self._map[encoding.lower(f)] = f
57 self._map[encoding.lower(f)] = f
58
58
59 def __call__(self, f):
59 def __call__(self, f):
60 fl = encoding.lower(f)
60 fl = encoding.lower(f)
61 map = self._map
61 map = self._map
62 if fl in map and map[fl] != f:
62 if fl in map and map[fl] != f:
63 msg = _('possible case-folding collision for %s') % f
63 msg = _('possible case-folding collision for %s') % f
64 if self._abort:
64 if self._abort:
65 raise util.Abort(msg)
65 raise util.Abort(msg)
66 self._ui.warn(_("warning: %s\n") % msg)
66 self._ui.warn(_("warning: %s\n") % msg)
67 map[fl] = f
67 map[fl] = f
68
68
69 class pathauditor(object):
69 class pathauditor(object):
70 '''ensure that a filesystem path contains no banned components.
70 '''ensure that a filesystem path contains no banned components.
71 the following properties of a path are checked:
71 the following properties of a path are checked:
72
72
73 - ends with a directory separator
73 - ends with a directory separator
74 - under top-level .hg
74 - under top-level .hg
75 - starts at the root of a windows drive
75 - starts at the root of a windows drive
76 - contains ".."
76 - contains ".."
77 - traverses a symlink (e.g. a/symlink_here/b)
77 - traverses a symlink (e.g. a/symlink_here/b)
78 - inside a nested repository (a callback can be used to approve
78 - inside a nested repository (a callback can be used to approve
79 some nested repositories, e.g., subrepositories)
79 some nested repositories, e.g., subrepositories)
80 '''
80 '''
81
81
82 def __init__(self, root, callback=None):
82 def __init__(self, root, callback=None):
83 self.audited = set()
83 self.audited = set()
84 self.auditeddir = set()
84 self.auditeddir = set()
85 self.root = root
85 self.root = root
86 self.callback = callback
86 self.callback = callback
87 if os.path.lexists(root) and not util.checkcase(root):
87 if os.path.lexists(root) and not util.checkcase(root):
88 self.normcase = util.normcase
88 self.normcase = util.normcase
89 else:
89 else:
90 self.normcase = lambda x: x
90 self.normcase = lambda x: x
91
91
92 def __call__(self, path):
92 def __call__(self, path):
93 '''Check the relative path.
93 '''Check the relative path.
94 path may contain a pattern (e.g. foodir/**.txt)'''
94 path may contain a pattern (e.g. foodir/**.txt)'''
95
95
96 path = util.localpath(path)
96 path = util.localpath(path)
97 normpath = self.normcase(path)
97 normpath = self.normcase(path)
98 if normpath in self.audited:
98 if normpath in self.audited:
99 return
99 return
100 # AIX ignores "/" at end of path, others raise EISDIR.
100 # AIX ignores "/" at end of path, others raise EISDIR.
101 if util.endswithsep(path):
101 if util.endswithsep(path):
102 raise util.Abort(_("path ends in directory separator: %s") % path)
102 raise util.Abort(_("path ends in directory separator: %s") % path)
103 parts = util.splitpath(path)
103 parts = util.splitpath(path)
104 if (os.path.splitdrive(path)[0]
104 if (os.path.splitdrive(path)[0]
105 or parts[0].lower() in ('.hg', '.hg.', '')
105 or parts[0].lower() in ('.hg', '.hg.', '')
106 or os.pardir in parts):
106 or os.pardir in parts):
107 raise util.Abort(_("path contains illegal component: %s") % path)
107 raise util.Abort(_("path contains illegal component: %s") % path)
108 if '.hg' in path.lower():
108 if '.hg' in path.lower():
109 lparts = [p.lower() for p in parts]
109 lparts = [p.lower() for p in parts]
110 for p in '.hg', '.hg.':
110 for p in '.hg', '.hg.':
111 if p in lparts[1:]:
111 if p in lparts[1:]:
112 pos = lparts.index(p)
112 pos = lparts.index(p)
113 base = os.path.join(*parts[:pos])
113 base = os.path.join(*parts[:pos])
114 raise util.Abort(_("path '%s' is inside nested repo %r")
114 raise util.Abort(_("path '%s' is inside nested repo %r")
115 % (path, base))
115 % (path, base))
116
116
117 normparts = util.splitpath(normpath)
117 normparts = util.splitpath(normpath)
118 assert len(parts) == len(normparts)
118 assert len(parts) == len(normparts)
119
119
120 parts.pop()
120 parts.pop()
121 normparts.pop()
121 normparts.pop()
122 prefixes = []
122 prefixes = []
123 while parts:
123 while parts:
124 prefix = os.sep.join(parts)
124 prefix = os.sep.join(parts)
125 normprefix = os.sep.join(normparts)
125 normprefix = os.sep.join(normparts)
126 if normprefix in self.auditeddir:
126 if normprefix in self.auditeddir:
127 break
127 break
128 curpath = os.path.join(self.root, prefix)
128 curpath = os.path.join(self.root, prefix)
129 try:
129 try:
130 st = os.lstat(curpath)
130 st = os.lstat(curpath)
131 except OSError, err:
131 except OSError, err:
132 # EINVAL can be raised as invalid path syntax under win32.
132 # EINVAL can be raised as invalid path syntax under win32.
133 # They must be ignored for patterns can be checked too.
133 # They must be ignored for patterns can be checked too.
134 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
134 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
135 raise
135 raise
136 else:
136 else:
137 if stat.S_ISLNK(st.st_mode):
137 if stat.S_ISLNK(st.st_mode):
138 raise util.Abort(
138 raise util.Abort(
139 _('path %r traverses symbolic link %r')
139 _('path %r traverses symbolic link %r')
140 % (path, prefix))
140 % (path, prefix))
141 elif (stat.S_ISDIR(st.st_mode) and
141 elif (stat.S_ISDIR(st.st_mode) and
142 os.path.isdir(os.path.join(curpath, '.hg'))):
142 os.path.isdir(os.path.join(curpath, '.hg'))):
143 if not self.callback or not self.callback(curpath):
143 if not self.callback or not self.callback(curpath):
144 raise util.Abort(_("path '%s' is inside nested "
144 raise util.Abort(_("path '%s' is inside nested "
145 "repo %r")
145 "repo %r")
146 % (path, prefix))
146 % (path, prefix))
147 prefixes.append(normprefix)
147 prefixes.append(normprefix)
148 parts.pop()
148 parts.pop()
149 normparts.pop()
149 normparts.pop()
150
150
151 self.audited.add(normpath)
151 self.audited.add(normpath)
152 # only add prefixes to the cache after checking everything: we don't
152 # only add prefixes to the cache after checking everything: we don't
153 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
153 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
154 self.auditeddir.update(prefixes)
154 self.auditeddir.update(prefixes)
155
155
156 class abstractopener(object):
156 class abstractopener(object):
157 """Abstract base class; cannot be instantiated"""
157 """Abstract base class; cannot be instantiated"""
158
158
159 def __init__(self, *args, **kwargs):
159 def __init__(self, *args, **kwargs):
160 '''Prevent instantiation; don't call this from subclasses.'''
160 '''Prevent instantiation; don't call this from subclasses.'''
161 raise NotImplementedError('attempted instantiating ' + str(type(self)))
161 raise NotImplementedError('attempted instantiating ' + str(type(self)))
162
162
163 def tryread(self, path):
163 def tryread(self, path):
164 '''gracefully return an empty string for missing files'''
164 '''gracefully return an empty string for missing files'''
165 try:
165 try:
166 return self.read(path)
166 return self.read(path)
167 except IOError, inst:
167 except IOError, inst:
168 if inst.errno != errno.ENOENT:
168 if inst.errno != errno.ENOENT:
169 raise
169 raise
170 return ""
170 return ""
171
171
172 def read(self, path):
172 def read(self, path):
173 fp = self(path, 'rb')
173 fp = self(path, 'rb')
174 try:
174 try:
175 return fp.read()
175 return fp.read()
176 finally:
176 finally:
177 fp.close()
177 fp.close()
178
178
179 def write(self, path, data):
179 def write(self, path, data):
180 fp = self(path, 'wb')
180 fp = self(path, 'wb')
181 try:
181 try:
182 return fp.write(data)
182 return fp.write(data)
183 finally:
183 finally:
184 fp.close()
184 fp.close()
185
185
186 def append(self, path, data):
186 def append(self, path, data):
187 fp = self(path, 'ab')
187 fp = self(path, 'ab')
188 try:
188 try:
189 return fp.write(data)
189 return fp.write(data)
190 finally:
190 finally:
191 fp.close()
191 fp.close()
192
192
193 class opener(abstractopener):
193 class opener(abstractopener):
194 '''Open files relative to a base directory
194 '''Open files relative to a base directory
195
195
196 This class is used to hide the details of COW semantics and
196 This class is used to hide the details of COW semantics and
197 remote file access from higher level code.
197 remote file access from higher level code.
198 '''
198 '''
199 def __init__(self, base, audit=True):
199 def __init__(self, base, audit=True):
200 self.base = base
200 self.base = base
201 self._audit = audit
201 self._audit = audit
202 if audit:
202 if audit:
203 self.auditor = pathauditor(base)
203 self.auditor = pathauditor(base)
204 else:
204 else:
205 self.auditor = util.always
205 self.auditor = util.always
206 self.createmode = None
206 self.createmode = None
207 self._trustnlink = None
207 self._trustnlink = None
208
208
209 @util.propertycache
209 @util.propertycache
210 def _cansymlink(self):
210 def _cansymlink(self):
211 return util.checklink(self.base)
211 return util.checklink(self.base)
212
212
213 def _fixfilemode(self, name):
213 def _fixfilemode(self, name):
214 if self.createmode is None:
214 if self.createmode is None:
215 return
215 return
216 os.chmod(name, self.createmode & 0666)
216 os.chmod(name, self.createmode & 0666)
217
217
218 def __call__(self, path, mode="r", text=False, atomictemp=False):
218 def __call__(self, path, mode="r", text=False, atomictemp=False):
219 if self._audit:
219 if self._audit:
220 r = util.checkosfilename(path)
220 r = util.checkosfilename(path)
221 if r:
221 if r:
222 raise util.Abort("%s: %r" % (r, path))
222 raise util.Abort("%s: %r" % (r, path))
223 self.auditor(path)
223 self.auditor(path)
224 f = self.join(path)
224 f = self.join(path)
225
225
226 if not text and "b" not in mode:
226 if not text and "b" not in mode:
227 mode += "b" # for that other OS
227 mode += "b" # for that other OS
228
228
229 nlink = -1
229 nlink = -1
230 dirname, basename = os.path.split(f)
230 dirname, basename = os.path.split(f)
231 # If basename is empty, then the path is malformed because it points
231 # If basename is empty, then the path is malformed because it points
232 # to a directory. Let the posixfile() call below raise IOError.
232 # to a directory. Let the posixfile() call below raise IOError.
233 if basename and mode not in ('r', 'rb'):
233 if basename and mode not in ('r', 'rb'):
234 if atomictemp:
234 if atomictemp:
235 if not os.path.isdir(dirname):
235 if not os.path.isdir(dirname):
236 util.makedirs(dirname, self.createmode)
236 util.makedirs(dirname, self.createmode)
237 return util.atomictempfile(f, mode, self.createmode)
237 return util.atomictempfile(f, mode, self.createmode)
238 try:
238 try:
239 if 'w' in mode:
239 if 'w' in mode:
240 util.unlink(f)
240 util.unlink(f)
241 nlink = 0
241 nlink = 0
242 else:
242 else:
243 # nlinks() may behave differently for files on Windows
243 # nlinks() may behave differently for files on Windows
244 # shares if the file is open.
244 # shares if the file is open.
245 fd = util.posixfile(f)
245 fd = util.posixfile(f)
246 nlink = util.nlinks(f)
246 nlink = util.nlinks(f)
247 if nlink < 1:
247 if nlink < 1:
248 nlink = 2 # force mktempcopy (issue1922)
248 nlink = 2 # force mktempcopy (issue1922)
249 fd.close()
249 fd.close()
250 except (OSError, IOError), e:
250 except (OSError, IOError), e:
251 if e.errno != errno.ENOENT:
251 if e.errno != errno.ENOENT:
252 raise
252 raise
253 nlink = 0
253 nlink = 0
254 if not os.path.isdir(dirname):
254 if not os.path.isdir(dirname):
255 util.makedirs(dirname, self.createmode)
255 util.makedirs(dirname, self.createmode)
256 if nlink > 0:
256 if nlink > 0:
257 if self._trustnlink is None:
257 if self._trustnlink is None:
258 self._trustnlink = nlink > 1 or util.checknlink(f)
258 self._trustnlink = nlink > 1 or util.checknlink(f)
259 if nlink > 1 or not self._trustnlink:
259 if nlink > 1 or not self._trustnlink:
260 util.rename(util.mktempcopy(f), f)
260 util.rename(util.mktempcopy(f), f)
261 fp = util.posixfile(f, mode)
261 fp = util.posixfile(f, mode)
262 if nlink == 0:
262 if nlink == 0:
263 self._fixfilemode(f)
263 self._fixfilemode(f)
264 return fp
264 return fp
265
265
266 def symlink(self, src, dst):
266 def symlink(self, src, dst):
267 self.auditor(dst)
267 self.auditor(dst)
268 linkname = self.join(dst)
268 linkname = self.join(dst)
269 try:
269 try:
270 os.unlink(linkname)
270 os.unlink(linkname)
271 except OSError:
271 except OSError:
272 pass
272 pass
273
273
274 dirname = os.path.dirname(linkname)
274 dirname = os.path.dirname(linkname)
275 if not os.path.exists(dirname):
275 if not os.path.exists(dirname):
276 util.makedirs(dirname, self.createmode)
276 util.makedirs(dirname, self.createmode)
277
277
278 if self._cansymlink:
278 if self._cansymlink:
279 try:
279 try:
280 os.symlink(src, linkname)
280 os.symlink(src, linkname)
281 except OSError, err:
281 except OSError, err:
282 raise OSError(err.errno, _('could not symlink to %r: %s') %
282 raise OSError(err.errno, _('could not symlink to %r: %s') %
283 (src, err.strerror), linkname)
283 (src, err.strerror), linkname)
284 else:
284 else:
285 f = self(dst, "w")
285 f = self(dst, "w")
286 f.write(src)
286 f.write(src)
287 f.close()
287 f.close()
288 self._fixfilemode(dst)
288 self._fixfilemode(dst)
289
289
290 def audit(self, path):
290 def audit(self, path):
291 self.auditor(path)
291 self.auditor(path)
292
292
293 def join(self, path):
293 def join(self, path):
294 return os.path.join(self.base, path)
294 return os.path.join(self.base, path)
295
295
296 class filteropener(abstractopener):
296 class filteropener(abstractopener):
297 '''Wrapper opener for filtering filenames with a function.'''
297 '''Wrapper opener for filtering filenames with a function.'''
298
298
299 def __init__(self, opener, filter):
299 def __init__(self, opener, filter):
300 self._filter = filter
300 self._filter = filter
301 self._orig = opener
301 self._orig = opener
302
302
303 def __call__(self, path, *args, **kwargs):
303 def __call__(self, path, *args, **kwargs):
304 return self._orig(self._filter(path), *args, **kwargs)
304 return self._orig(self._filter(path), *args, **kwargs)
305
305
306 def canonpath(root, cwd, myname, auditor=None):
306 def canonpath(root, cwd, myname, auditor=None):
307 '''return the canonical path of myname, given cwd and root'''
307 '''return the canonical path of myname, given cwd and root'''
308 if util.endswithsep(root):
308 if util.endswithsep(root):
309 rootsep = root
309 rootsep = root
310 else:
310 else:
311 rootsep = root + os.sep
311 rootsep = root + os.sep
312 name = myname
312 name = myname
313 if not os.path.isabs(name):
313 if not os.path.isabs(name):
314 name = os.path.join(root, cwd, name)
314 name = os.path.join(root, cwd, name)
315 name = os.path.normpath(name)
315 name = os.path.normpath(name)
316 if auditor is None:
316 if auditor is None:
317 auditor = pathauditor(root)
317 auditor = pathauditor(root)
318 if name != rootsep and name.startswith(rootsep):
318 if name != rootsep and name.startswith(rootsep):
319 name = name[len(rootsep):]
319 name = name[len(rootsep):]
320 auditor(name)
320 auditor(name)
321 return util.pconvert(name)
321 return util.pconvert(name)
322 elif name == root:
322 elif name == root:
323 return ''
323 return ''
324 else:
324 else:
325 # Determine whether `name' is in the hierarchy at or beneath `root',
325 # Determine whether `name' is in the hierarchy at or beneath `root',
326 # by iterating name=dirname(name) until that causes no change (can't
326 # by iterating name=dirname(name) until that causes no change (can't
327 # check name == '/', because that doesn't work on windows). The list
327 # check name == '/', because that doesn't work on windows). The list
328 # `rel' holds the reversed list of components making up the relative
328 # `rel' holds the reversed list of components making up the relative
329 # file name we want.
329 # file name we want.
330 rel = []
330 rel = []
331 while True:
331 while True:
332 try:
332 try:
333 s = util.samefile(name, root)
333 s = util.samefile(name, root)
334 except OSError:
334 except OSError:
335 s = False
335 s = False
336 if s:
336 if s:
337 if not rel:
337 if not rel:
338 # name was actually the same as root (maybe a symlink)
338 # name was actually the same as root (maybe a symlink)
339 return ''
339 return ''
340 rel.reverse()
340 rel.reverse()
341 name = os.path.join(*rel)
341 name = os.path.join(*rel)
342 auditor(name)
342 auditor(name)
343 return util.pconvert(name)
343 return util.pconvert(name)
344 dirname, basename = os.path.split(name)
344 dirname, basename = os.path.split(name)
345 rel.append(basename)
345 rel.append(basename)
346 if dirname == name:
346 if dirname == name:
347 break
347 break
348 name = dirname
348 name = dirname
349
349
350 raise util.Abort('%s not under root' % myname)
350 raise util.Abort('%s not under root' % myname)
351
351
352 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
352 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
353 '''yield every hg repository under path, recursively.'''
353 '''yield every hg repository under path, recursively.'''
354 def errhandler(err):
354 def errhandler(err):
355 if err.filename == path:
355 if err.filename == path:
356 raise err
356 raise err
357 samestat = getattr(os.path, 'samestat', None)
357 samestat = getattr(os.path, 'samestat', None)
358 if followsym and samestat is not None:
358 if followsym and samestat is not None:
359 def adddir(dirlst, dirname):
359 def adddir(dirlst, dirname):
360 match = False
360 match = False
361 dirstat = os.stat(dirname)
361 dirstat = os.stat(dirname)
362 for lstdirstat in dirlst:
362 for lstdirstat in dirlst:
363 if samestat(dirstat, lstdirstat):
363 if samestat(dirstat, lstdirstat):
364 match = True
364 match = True
365 break
365 break
366 if not match:
366 if not match:
367 dirlst.append(dirstat)
367 dirlst.append(dirstat)
368 return not match
368 return not match
369 else:
369 else:
370 followsym = False
370 followsym = False
371
371
372 if (seen_dirs is None) and followsym:
372 if (seen_dirs is None) and followsym:
373 seen_dirs = []
373 seen_dirs = []
374 adddir(seen_dirs, path)
374 adddir(seen_dirs, path)
375 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
375 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
376 dirs.sort()
376 dirs.sort()
377 if '.hg' in dirs:
377 if '.hg' in dirs:
378 yield root # found a repository
378 yield root # found a repository
379 qroot = os.path.join(root, '.hg', 'patches')
379 qroot = os.path.join(root, '.hg', 'patches')
380 if os.path.isdir(os.path.join(qroot, '.hg')):
380 if os.path.isdir(os.path.join(qroot, '.hg')):
381 yield qroot # we have a patch queue repo here
381 yield qroot # we have a patch queue repo here
382 if recurse:
382 if recurse:
383 # avoid recursing inside the .hg directory
383 # avoid recursing inside the .hg directory
384 dirs.remove('.hg')
384 dirs.remove('.hg')
385 else:
385 else:
386 dirs[:] = [] # don't descend further
386 dirs[:] = [] # don't descend further
387 elif followsym:
387 elif followsym:
388 newdirs = []
388 newdirs = []
389 for d in dirs:
389 for d in dirs:
390 fname = os.path.join(root, d)
390 fname = os.path.join(root, d)
391 if adddir(seen_dirs, fname):
391 if adddir(seen_dirs, fname):
392 if os.path.islink(fname):
392 if os.path.islink(fname):
393 for hgname in walkrepos(fname, True, seen_dirs):
393 for hgname in walkrepos(fname, True, seen_dirs):
394 yield hgname
394 yield hgname
395 else:
395 else:
396 newdirs.append(d)
396 newdirs.append(d)
397 dirs[:] = newdirs
397 dirs[:] = newdirs
398
398
399 def osrcpath():
399 def osrcpath():
400 '''return default os-specific hgrc search path'''
400 '''return default os-specific hgrc search path'''
401 path = systemrcpath()
401 path = systemrcpath()
402 path.extend(userrcpath())
402 path.extend(userrcpath())
403 path = [os.path.normpath(f) for f in path]
403 path = [os.path.normpath(f) for f in path]
404 return path
404 return path
405
405
406 _rcpath = None
406 _rcpath = None
407
407
408 def rcpath():
408 def rcpath():
409 '''return hgrc search path. if env var HGRCPATH is set, use it.
409 '''return hgrc search path. if env var HGRCPATH is set, use it.
410 for each item in path, if directory, use files ending in .rc,
410 for each item in path, if directory, use files ending in .rc,
411 else use item.
411 else use item.
412 make HGRCPATH empty to only look in .hg/hgrc of current repo.
412 make HGRCPATH empty to only look in .hg/hgrc of current repo.
413 if no HGRCPATH, use default os-specific path.'''
413 if no HGRCPATH, use default os-specific path.'''
414 global _rcpath
414 global _rcpath
415 if _rcpath is None:
415 if _rcpath is None:
416 if 'HGRCPATH' in os.environ:
416 if 'HGRCPATH' in os.environ:
417 _rcpath = []
417 _rcpath = []
418 for p in os.environ['HGRCPATH'].split(os.pathsep):
418 for p in os.environ['HGRCPATH'].split(os.pathsep):
419 if not p:
419 if not p:
420 continue
420 continue
421 p = util.expandpath(p)
421 p = util.expandpath(p)
422 if os.path.isdir(p):
422 if os.path.isdir(p):
423 for f, kind in osutil.listdir(p):
423 for f, kind in osutil.listdir(p):
424 if f.endswith('.rc'):
424 if f.endswith('.rc'):
425 _rcpath.append(os.path.join(p, f))
425 _rcpath.append(os.path.join(p, f))
426 else:
426 else:
427 _rcpath.append(p)
427 _rcpath.append(p)
428 else:
428 else:
429 _rcpath = osrcpath()
429 _rcpath = osrcpath()
430 return _rcpath
430 return _rcpath
431
431
432 if os.name != 'nt':
432 if os.name != 'nt':
433
433
434 def rcfiles(path):
434 def rcfiles(path):
435 rcs = [os.path.join(path, 'hgrc')]
435 rcs = [os.path.join(path, 'hgrc')]
436 rcdir = os.path.join(path, 'hgrc.d')
436 rcdir = os.path.join(path, 'hgrc.d')
437 try:
437 try:
438 rcs.extend([os.path.join(rcdir, f)
438 rcs.extend([os.path.join(rcdir, f)
439 for f, kind in osutil.listdir(rcdir)
439 for f, kind in osutil.listdir(rcdir)
440 if f.endswith(".rc")])
440 if f.endswith(".rc")])
441 except OSError:
441 except OSError:
442 pass
442 pass
443 return rcs
443 return rcs
444
444
445 def systemrcpath():
445 def systemrcpath():
446 path = []
446 path = []
447 if sys.platform == 'plan9':
447 if sys.platform == 'plan9':
448 root = 'lib/mercurial'
448 root = 'lib/mercurial'
449 else:
449 else:
450 root = 'etc/mercurial'
450 root = 'etc/mercurial'
451 # old mod_python does not set sys.argv
451 # old mod_python does not set sys.argv
452 if len(getattr(sys, 'argv', [])) > 0:
452 if len(getattr(sys, 'argv', [])) > 0:
453 p = os.path.dirname(os.path.dirname(sys.argv[0]))
453 p = os.path.dirname(os.path.dirname(sys.argv[0]))
454 path.extend(rcfiles(os.path.join(p, root)))
454 path.extend(rcfiles(os.path.join(p, root)))
455 path.extend(rcfiles('/' + root))
455 path.extend(rcfiles('/' + root))
456 return path
456 return path
457
457
458 def userrcpath():
458 def userrcpath():
459 if sys.platform == 'plan9':
459 if sys.platform == 'plan9':
460 return [os.environ['home'] + '/lib/hgrc']
460 return [os.environ['home'] + '/lib/hgrc']
461 else:
461 else:
462 return [os.path.expanduser('~/.hgrc')]
462 return [os.path.expanduser('~/.hgrc')]
463
463
464 else:
464 else:
465
465
466 import _winreg
466 import _winreg
467
467
468 def systemrcpath():
468 def systemrcpath():
469 '''return default os-specific hgrc search path'''
469 '''return default os-specific hgrc search path'''
470 rcpath = []
470 rcpath = []
471 filename = util.executablepath()
471 filename = util.executablepath()
472 # Use mercurial.ini found in directory with hg.exe
472 # Use mercurial.ini found in directory with hg.exe
473 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
473 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
474 if os.path.isfile(progrc):
474 if os.path.isfile(progrc):
475 rcpath.append(progrc)
475 rcpath.append(progrc)
476 return rcpath
476 return rcpath
477 # Use hgrc.d found in directory with hg.exe
477 # Use hgrc.d found in directory with hg.exe
478 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
478 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
479 if os.path.isdir(progrcd):
479 if os.path.isdir(progrcd):
480 for f, kind in osutil.listdir(progrcd):
480 for f, kind in osutil.listdir(progrcd):
481 if f.endswith('.rc'):
481 if f.endswith('.rc'):
482 rcpath.append(os.path.join(progrcd, f))
482 rcpath.append(os.path.join(progrcd, f))
483 return rcpath
483 return rcpath
484 # else look for a system rcpath in the registry
484 # else look for a system rcpath in the registry
485 value = util.lookupreg('SOFTWARE\\Mercurial', None,
485 value = util.lookupreg('SOFTWARE\\Mercurial', None,
486 _winreg.HKEY_LOCAL_MACHINE)
486 _winreg.HKEY_LOCAL_MACHINE)
487 if not isinstance(value, str) or not value:
487 if not isinstance(value, str) or not value:
488 return rcpath
488 return rcpath
489 value = util.localpath(value)
489 value = util.localpath(value)
490 for p in value.split(os.pathsep):
490 for p in value.split(os.pathsep):
491 if p.lower().endswith('mercurial.ini'):
491 if p.lower().endswith('mercurial.ini'):
492 rcpath.append(p)
492 rcpath.append(p)
493 elif os.path.isdir(p):
493 elif os.path.isdir(p):
494 for f, kind in osutil.listdir(p):
494 for f, kind in osutil.listdir(p):
495 if f.endswith('.rc'):
495 if f.endswith('.rc'):
496 rcpath.append(os.path.join(p, f))
496 rcpath.append(os.path.join(p, f))
497 return rcpath
497 return rcpath
498
498
499 def userrcpath():
499 def userrcpath():
500 '''return os-specific hgrc search path to the user dir'''
500 '''return os-specific hgrc search path to the user dir'''
501 home = os.path.expanduser('~')
501 home = os.path.expanduser('~')
502 path = [os.path.join(home, 'mercurial.ini'),
502 path = [os.path.join(home, 'mercurial.ini'),
503 os.path.join(home, '.hgrc')]
503 os.path.join(home, '.hgrc')]
504 userprofile = os.environ.get('USERPROFILE')
504 userprofile = os.environ.get('USERPROFILE')
505 if userprofile:
505 if userprofile:
506 path.append(os.path.join(userprofile, 'mercurial.ini'))
506 path.append(os.path.join(userprofile, 'mercurial.ini'))
507 path.append(os.path.join(userprofile, '.hgrc'))
507 path.append(os.path.join(userprofile, '.hgrc'))
508 return path
508 return path
509
509
510 def revsingle(repo, revspec, default='.'):
510 def revsingle(repo, revspec, default='.'):
511 if not revspec:
511 if not revspec:
512 return repo[default]
512 return repo[default]
513
513
514 l = revrange(repo, [revspec])
514 l = revrange(repo, [revspec])
515 if len(l) < 1:
515 if len(l) < 1:
516 raise util.Abort(_('empty revision set'))
516 raise util.Abort(_('empty revision set'))
517 return repo[l[-1]]
517 return repo[l[-1]]
518
518
519 def revpair(repo, revs):
519 def revpair(repo, revs):
520 if not revs:
520 if not revs:
521 return repo.dirstate.p1(), None
521 return repo.dirstate.p1(), None
522
522
523 l = revrange(repo, revs)
523 l = revrange(repo, revs)
524
524
525 if len(l) == 0:
525 if len(l) == 0:
526 if revs:
526 if revs:
527 raise util.Abort(_('empty revision range'))
527 raise util.Abort(_('empty revision range'))
528 return repo.dirstate.p1(), None
528 return repo.dirstate.p1(), None
529
529
530 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
530 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
531 return repo.lookup(l[0]), None
531 return repo.lookup(l[0]), None
532
532
533 return repo.lookup(l[0]), repo.lookup(l[-1])
533 return repo.lookup(l[0]), repo.lookup(l[-1])
534
534
535 _revrangesep = ':'
535 _revrangesep = ':'
536
536
537 def revrange(repo, revs):
537 def revrange(repo, revs):
538 """Yield revision as strings from a list of revision specifications."""
538 """Yield revision as strings from a list of revision specifications."""
539
539
540 def revfix(repo, val, defval):
540 def revfix(repo, val, defval):
541 if not val and val != 0 and defval is not None:
541 if not val and val != 0 and defval is not None:
542 return defval
542 return defval
543 return repo[val].rev()
543 return repo[val].rev()
544
544
545 seen, l = set(), []
545 seen, l = set(), []
546 for spec in revs:
546 for spec in revs:
547 if l and not seen:
547 if l and not seen:
548 seen = set(l)
548 seen = set(l)
549 # attempt to parse old-style ranges first to deal with
549 # attempt to parse old-style ranges first to deal with
550 # things like old-tag which contain query metacharacters
550 # things like old-tag which contain query metacharacters
551 try:
551 try:
552 if isinstance(spec, int):
552 if isinstance(spec, int):
553 seen.add(spec)
553 seen.add(spec)
554 l.append(spec)
554 l.append(spec)
555 continue
555 continue
556
556
557 if _revrangesep in spec:
557 if _revrangesep in spec:
558 start, end = spec.split(_revrangesep, 1)
558 start, end = spec.split(_revrangesep, 1)
559 start = revfix(repo, start, 0)
559 start = revfix(repo, start, 0)
560 end = revfix(repo, end, len(repo) - 1)
560 end = revfix(repo, end, len(repo) - 1)
561 step = start > end and -1 or 1
561 step = start > end and -1 or 1
562 if not seen and not l:
562 if not seen and not l:
563 # by far the most common case: revs = ["-1:0"]
563 # by far the most common case: revs = ["-1:0"]
564 l = range(start, end + step, step)
564 l = range(start, end + step, step)
565 # defer syncing seen until next iteration
565 # defer syncing seen until next iteration
566 continue
566 continue
567 newrevs = set(xrange(start, end + step, step))
567 newrevs = set(xrange(start, end + step, step))
568 if seen:
568 if seen:
569 newrevs.difference_update(seen)
569 newrevs.difference_update(seen)
570 seen.update(newrevs)
570 seen.update(newrevs)
571 else:
571 else:
572 seen = newrevs
572 seen = newrevs
573 l.extend(sorted(newrevs, reverse=start > end))
573 l.extend(sorted(newrevs, reverse=start > end))
574 continue
574 continue
575 elif spec and spec in repo: # single unquoted rev
575 elif spec and spec in repo: # single unquoted rev
576 rev = revfix(repo, spec, None)
576 rev = revfix(repo, spec, None)
577 if rev in seen:
577 if rev in seen:
578 continue
578 continue
579 seen.add(rev)
579 seen.add(rev)
580 l.append(rev)
580 l.append(rev)
581 continue
581 continue
582 except error.RepoLookupError:
582 except error.RepoLookupError:
583 pass
583 pass
584
584
585 # fall through to new-style queries if old-style fails
585 # fall through to new-style queries if old-style fails
586 m = revset.match(repo.ui, spec)
586 m = revset.match(repo.ui, spec)
587 for r in m(repo, range(len(repo))):
587 dl = [r for r in m(repo, xrange(len(repo))) if r not in seen]
588 if r not in seen:
588 l.extend(dl)
589 l.append(r)
589 seen.update(dl)
590 seen.update(l)
591
590
592 return l
591 return l
593
592
594 def expandpats(pats):
593 def expandpats(pats):
595 if not util.expandglobs:
594 if not util.expandglobs:
596 return list(pats)
595 return list(pats)
597 ret = []
596 ret = []
598 for p in pats:
597 for p in pats:
599 kind, name = matchmod._patsplit(p, None)
598 kind, name = matchmod._patsplit(p, None)
600 if kind is None:
599 if kind is None:
601 try:
600 try:
602 globbed = glob.glob(name)
601 globbed = glob.glob(name)
603 except re.error:
602 except re.error:
604 globbed = [name]
603 globbed = [name]
605 if globbed:
604 if globbed:
606 ret.extend(globbed)
605 ret.extend(globbed)
607 continue
606 continue
608 ret.append(p)
607 ret.append(p)
609 return ret
608 return ret
610
609
611 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
610 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
612 if pats == ("",):
611 if pats == ("",):
613 pats = []
612 pats = []
614 if not globbed and default == 'relpath':
613 if not globbed and default == 'relpath':
615 pats = expandpats(pats or [])
614 pats = expandpats(pats or [])
616
615
617 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
616 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
618 default)
617 default)
619 def badfn(f, msg):
618 def badfn(f, msg):
620 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
619 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
621 m.bad = badfn
620 m.bad = badfn
622 return m, pats
621 return m, pats
623
622
624 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
623 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
625 return matchandpats(ctx, pats, opts, globbed, default)[0]
624 return matchandpats(ctx, pats, opts, globbed, default)[0]
626
625
627 def matchall(repo):
626 def matchall(repo):
628 return matchmod.always(repo.root, repo.getcwd())
627 return matchmod.always(repo.root, repo.getcwd())
629
628
630 def matchfiles(repo, files):
629 def matchfiles(repo, files):
631 return matchmod.exact(repo.root, repo.getcwd(), files)
630 return matchmod.exact(repo.root, repo.getcwd(), files)
632
631
633 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
632 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
634 if dry_run is None:
633 if dry_run is None:
635 dry_run = opts.get('dry_run')
634 dry_run = opts.get('dry_run')
636 if similarity is None:
635 if similarity is None:
637 similarity = float(opts.get('similarity') or 0)
636 similarity = float(opts.get('similarity') or 0)
638 # we'd use status here, except handling of symlinks and ignore is tricky
637 # we'd use status here, except handling of symlinks and ignore is tricky
639 added, unknown, deleted, removed = [], [], [], []
638 added, unknown, deleted, removed = [], [], [], []
640 audit_path = pathauditor(repo.root)
639 audit_path = pathauditor(repo.root)
641 m = match(repo[None], pats, opts)
640 m = match(repo[None], pats, opts)
642 rejected = []
641 rejected = []
643 m.bad = lambda x, y: rejected.append(x)
642 m.bad = lambda x, y: rejected.append(x)
644
643
645 for abs in repo.walk(m):
644 for abs in repo.walk(m):
646 target = repo.wjoin(abs)
645 target = repo.wjoin(abs)
647 good = True
646 good = True
648 try:
647 try:
649 audit_path(abs)
648 audit_path(abs)
650 except (OSError, util.Abort):
649 except (OSError, util.Abort):
651 good = False
650 good = False
652 rel = m.rel(abs)
651 rel = m.rel(abs)
653 exact = m.exact(abs)
652 exact = m.exact(abs)
654 if good and abs not in repo.dirstate:
653 if good and abs not in repo.dirstate:
655 unknown.append(abs)
654 unknown.append(abs)
656 if repo.ui.verbose or not exact:
655 if repo.ui.verbose or not exact:
657 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
656 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
658 elif (repo.dirstate[abs] != 'r' and
657 elif (repo.dirstate[abs] != 'r' and
659 (not good or not os.path.lexists(target) or
658 (not good or not os.path.lexists(target) or
660 (os.path.isdir(target) and not os.path.islink(target)))):
659 (os.path.isdir(target) and not os.path.islink(target)))):
661 deleted.append(abs)
660 deleted.append(abs)
662 if repo.ui.verbose or not exact:
661 if repo.ui.verbose or not exact:
663 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
662 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
664 # for finding renames
663 # for finding renames
665 elif repo.dirstate[abs] == 'r':
664 elif repo.dirstate[abs] == 'r':
666 removed.append(abs)
665 removed.append(abs)
667 elif repo.dirstate[abs] == 'a':
666 elif repo.dirstate[abs] == 'a':
668 added.append(abs)
667 added.append(abs)
669 copies = {}
668 copies = {}
670 if similarity > 0:
669 if similarity > 0:
671 for old, new, score in similar.findrenames(repo,
670 for old, new, score in similar.findrenames(repo,
672 added + unknown, removed + deleted, similarity):
671 added + unknown, removed + deleted, similarity):
673 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
672 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
674 repo.ui.status(_('recording removal of %s as rename to %s '
673 repo.ui.status(_('recording removal of %s as rename to %s '
675 '(%d%% similar)\n') %
674 '(%d%% similar)\n') %
676 (m.rel(old), m.rel(new), score * 100))
675 (m.rel(old), m.rel(new), score * 100))
677 copies[new] = old
676 copies[new] = old
678
677
679 if not dry_run:
678 if not dry_run:
680 wctx = repo[None]
679 wctx = repo[None]
681 wlock = repo.wlock()
680 wlock = repo.wlock()
682 try:
681 try:
683 wctx.forget(deleted)
682 wctx.forget(deleted)
684 wctx.add(unknown)
683 wctx.add(unknown)
685 for new, old in copies.iteritems():
684 for new, old in copies.iteritems():
686 wctx.copy(old, new)
685 wctx.copy(old, new)
687 finally:
686 finally:
688 wlock.release()
687 wlock.release()
689
688
690 for f in rejected:
689 for f in rejected:
691 if f in m.files():
690 if f in m.files():
692 return 1
691 return 1
693 return 0
692 return 0
694
693
695 def updatedir(ui, repo, patches, similarity=0):
694 def updatedir(ui, repo, patches, similarity=0):
696 '''Update dirstate after patch application according to metadata'''
695 '''Update dirstate after patch application according to metadata'''
697 if not patches:
696 if not patches:
698 return []
697 return []
699 copies = []
698 copies = []
700 removes = set()
699 removes = set()
701 cfiles = patches.keys()
700 cfiles = patches.keys()
702 cwd = repo.getcwd()
701 cwd = repo.getcwd()
703 if cwd:
702 if cwd:
704 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
703 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
705 for f in patches:
704 for f in patches:
706 gp = patches[f]
705 gp = patches[f]
707 if not gp:
706 if not gp:
708 continue
707 continue
709 if gp.op == 'RENAME':
708 if gp.op == 'RENAME':
710 copies.append((gp.oldpath, gp.path))
709 copies.append((gp.oldpath, gp.path))
711 removes.add(gp.oldpath)
710 removes.add(gp.oldpath)
712 elif gp.op == 'COPY':
711 elif gp.op == 'COPY':
713 copies.append((gp.oldpath, gp.path))
712 copies.append((gp.oldpath, gp.path))
714 elif gp.op == 'DELETE':
713 elif gp.op == 'DELETE':
715 removes.add(gp.path)
714 removes.add(gp.path)
716
715
717 wctx = repo[None]
716 wctx = repo[None]
718 for src, dst in copies:
717 for src, dst in copies:
719 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
718 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
720 if (not similarity) and removes:
719 if (not similarity) and removes:
721 wctx.remove(sorted(removes), True)
720 wctx.remove(sorted(removes), True)
722
721
723 for f in patches:
722 for f in patches:
724 gp = patches[f]
723 gp = patches[f]
725 if gp and gp.mode:
724 if gp and gp.mode:
726 islink, isexec = gp.mode
725 islink, isexec = gp.mode
727 dst = repo.wjoin(gp.path)
726 dst = repo.wjoin(gp.path)
728 # patch won't create empty files
727 # patch won't create empty files
729 if gp.op == 'ADD' and not os.path.lexists(dst):
728 if gp.op == 'ADD' and not os.path.lexists(dst):
730 flags = (isexec and 'x' or '') + (islink and 'l' or '')
729 flags = (isexec and 'x' or '') + (islink and 'l' or '')
731 repo.wwrite(gp.path, '', flags)
730 repo.wwrite(gp.path, '', flags)
732 util.setflags(dst, islink, isexec)
731 util.setflags(dst, islink, isexec)
733 addremove(repo, cfiles, similarity=similarity)
732 addremove(repo, cfiles, similarity=similarity)
734 files = patches.keys()
733 files = patches.keys()
735 files.extend([r for r in removes if r not in files])
734 files.extend([r for r in removes if r not in files])
736 return sorted(files)
735 return sorted(files)
737
736
738 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
737 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
739 """Update the dirstate to reflect the intent of copying src to dst. For
738 """Update the dirstate to reflect the intent of copying src to dst. For
740 different reasons it might not end with dst being marked as copied from src.
739 different reasons it might not end with dst being marked as copied from src.
741 """
740 """
742 origsrc = repo.dirstate.copied(src) or src
741 origsrc = repo.dirstate.copied(src) or src
743 if dst == origsrc: # copying back a copy?
742 if dst == origsrc: # copying back a copy?
744 if repo.dirstate[dst] not in 'mn' and not dryrun:
743 if repo.dirstate[dst] not in 'mn' and not dryrun:
745 repo.dirstate.normallookup(dst)
744 repo.dirstate.normallookup(dst)
746 else:
745 else:
747 if repo.dirstate[origsrc] == 'a' and origsrc == src:
746 if repo.dirstate[origsrc] == 'a' and origsrc == src:
748 if not ui.quiet:
747 if not ui.quiet:
749 ui.warn(_("%s has not been committed yet, so no copy "
748 ui.warn(_("%s has not been committed yet, so no copy "
750 "data will be stored for %s.\n")
749 "data will be stored for %s.\n")
751 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
750 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
752 if repo.dirstate[dst] in '?r' and not dryrun:
751 if repo.dirstate[dst] in '?r' and not dryrun:
753 wctx.add([dst])
752 wctx.add([dst])
754 elif not dryrun:
753 elif not dryrun:
755 wctx.copy(origsrc, dst)
754 wctx.copy(origsrc, dst)
756
755
757 def readrequires(opener, supported):
756 def readrequires(opener, supported):
758 '''Reads and parses .hg/requires and checks if all entries found
757 '''Reads and parses .hg/requires and checks if all entries found
759 are in the list of supported features.'''
758 are in the list of supported features.'''
760 requirements = set(opener.read("requires").splitlines())
759 requirements = set(opener.read("requires").splitlines())
761 missings = []
760 missings = []
762 for r in requirements:
761 for r in requirements:
763 if r not in supported:
762 if r not in supported:
764 if not r or not r[0].isalnum():
763 if not r or not r[0].isalnum():
765 raise error.RequirementError(_(".hg/requires file is corrupt"))
764 raise error.RequirementError(_(".hg/requires file is corrupt"))
766 missings.append(r)
765 missings.append(r)
767 missings.sort()
766 missings.sort()
768 if missings:
767 if missings:
769 raise error.RequirementError(
768 raise error.RequirementError(
770 _("unknown repository format: requires features '%s' (upgrade "
769 _("unknown repository format: requires features '%s' (upgrade "
771 "Mercurial)") % "', '".join(missings))
770 "Mercurial)") % "', '".join(missings))
772 return requirements
771 return requirements
773
772
774 class filecacheentry(object):
773 class filecacheentry(object):
775 def __init__(self, path):
774 def __init__(self, path):
776 self.path = path
775 self.path = path
777 self.cachestat = filecacheentry.stat(self.path)
776 self.cachestat = filecacheentry.stat(self.path)
778
777
779 if self.cachestat:
778 if self.cachestat:
780 self._cacheable = self.cachestat.cacheable()
779 self._cacheable = self.cachestat.cacheable()
781 else:
780 else:
782 # None means we don't know yet
781 # None means we don't know yet
783 self._cacheable = None
782 self._cacheable = None
784
783
785 def refresh(self):
784 def refresh(self):
786 if self.cacheable():
785 if self.cacheable():
787 self.cachestat = filecacheentry.stat(self.path)
786 self.cachestat = filecacheentry.stat(self.path)
788
787
789 def cacheable(self):
788 def cacheable(self):
790 if self._cacheable is not None:
789 if self._cacheable is not None:
791 return self._cacheable
790 return self._cacheable
792
791
793 # we don't know yet, assume it is for now
792 # we don't know yet, assume it is for now
794 return True
793 return True
795
794
796 def changed(self):
795 def changed(self):
797 # no point in going further if we can't cache it
796 # no point in going further if we can't cache it
798 if not self.cacheable():
797 if not self.cacheable():
799 return True
798 return True
800
799
801 newstat = filecacheentry.stat(self.path)
800 newstat = filecacheentry.stat(self.path)
802
801
803 # we may not know if it's cacheable yet, check again now
802 # we may not know if it's cacheable yet, check again now
804 if newstat and self._cacheable is None:
803 if newstat and self._cacheable is None:
805 self._cacheable = newstat.cacheable()
804 self._cacheable = newstat.cacheable()
806
805
807 # check again
806 # check again
808 if not self._cacheable:
807 if not self._cacheable:
809 return True
808 return True
810
809
811 if self.cachestat != newstat:
810 if self.cachestat != newstat:
812 self.cachestat = newstat
811 self.cachestat = newstat
813 return True
812 return True
814 else:
813 else:
815 return False
814 return False
816
815
817 @staticmethod
816 @staticmethod
818 def stat(path):
817 def stat(path):
819 try:
818 try:
820 return util.cachestat(path)
819 return util.cachestat(path)
821 except OSError, e:
820 except OSError, e:
822 if e.errno != errno.ENOENT:
821 if e.errno != errno.ENOENT:
823 raise
822 raise
824
823
825 class filecache(object):
824 class filecache(object):
826 '''A property like decorator that tracks a file under .hg/ for updates.
825 '''A property like decorator that tracks a file under .hg/ for updates.
827
826
828 Records stat info when called in _filecache.
827 Records stat info when called in _filecache.
829
828
830 On subsequent calls, compares old stat info with new info, and recreates
829 On subsequent calls, compares old stat info with new info, and recreates
831 the object when needed, updating the new stat info in _filecache.
830 the object when needed, updating the new stat info in _filecache.
832
831
833 Mercurial either atomic renames or appends for files under .hg,
832 Mercurial either atomic renames or appends for files under .hg,
834 so to ensure the cache is reliable we need the filesystem to be able
833 so to ensure the cache is reliable we need the filesystem to be able
835 to tell us if a file has been replaced. If it can't, we fallback to
834 to tell us if a file has been replaced. If it can't, we fallback to
836 recreating the object on every call (essentially the same behaviour as
835 recreating the object on every call (essentially the same behaviour as
837 propertycache).'''
836 propertycache).'''
838 def __init__(self, path):
837 def __init__(self, path):
839 self.path = path
838 self.path = path
840
839
841 def join(self, obj, fname):
840 def join(self, obj, fname):
842 """Used to compute the runtime path of the cached file.
841 """Used to compute the runtime path of the cached file.
843
842
844 Users should subclass filecache and provide their own version of this
843 Users should subclass filecache and provide their own version of this
845 function to call the appropriate join function on 'obj' (an instance
844 function to call the appropriate join function on 'obj' (an instance
846 of the class that its member function was decorated).
845 of the class that its member function was decorated).
847 """
846 """
848 return obj.join(fname)
847 return obj.join(fname)
849
848
850 def __call__(self, func):
849 def __call__(self, func):
851 self.func = func
850 self.func = func
852 self.name = func.__name__
851 self.name = func.__name__
853 return self
852 return self
854
853
855 def __get__(self, obj, type=None):
854 def __get__(self, obj, type=None):
856 # do we need to check if the file changed?
855 # do we need to check if the file changed?
857 if self.name in obj.__dict__:
856 if self.name in obj.__dict__:
858 return obj.__dict__[self.name]
857 return obj.__dict__[self.name]
859
858
860 entry = obj._filecache.get(self.name)
859 entry = obj._filecache.get(self.name)
861
860
862 if entry:
861 if entry:
863 if entry.changed():
862 if entry.changed():
864 entry.obj = self.func(obj)
863 entry.obj = self.func(obj)
865 else:
864 else:
866 path = self.join(obj, self.path)
865 path = self.join(obj, self.path)
867
866
868 # We stat -before- creating the object so our cache doesn't lie if
867 # We stat -before- creating the object so our cache doesn't lie if
869 # a writer modified between the time we read and stat
868 # a writer modified between the time we read and stat
870 entry = filecacheentry(path)
869 entry = filecacheentry(path)
871 entry.obj = self.func(obj)
870 entry.obj = self.func(obj)
872
871
873 obj._filecache[self.name] = entry
872 obj._filecache[self.name] = entry
874
873
875 obj.__dict__[self.name] = entry.obj
874 obj.__dict__[self.name] = entry.obj
876 return entry.obj
875 return entry.obj
877
876
878 def __set__(self, obj, value):
877 def __set__(self, obj, value):
879 if self.name in obj._filecache:
878 if self.name in obj._filecache:
880 obj._filecache[self.name].obj = value # update cached copy
879 obj._filecache[self.name].obj = value # update cached copy
881 obj.__dict__[self.name] = value # update copy returned by obj.x
880 obj.__dict__[self.name] = value # update copy returned by obj.x
882
881
883 def __delete__(self, obj):
882 def __delete__(self, obj):
884 try:
883 try:
885 del obj.__dict__[self.name]
884 del obj.__dict__[self.name]
886 except KeyError:
885 except KeyError:
887 raise AttributeError, self.name
886 raise AttributeError, self.name
General Comments 0
You need to be logged in to leave comments. Login now