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