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