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