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