##// END OF EJS Templates
scmutil: add mustaudit delegation to filtervfs (issue3673)
Bryan O'Sullivan -
r17846:f42cf308 stable
parent child Browse files
Show More
@@ -1,962 +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 %s name") %
35 raise util.Abort(_("%r cannot be used in a %s name") %
36 (c, kind))
36 (c, kind))
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.auditor = pathauditor(self.base)
256 self.auditor = pathauditor(self.base)
257 else:
257 else:
258 self.auditor = util.always
258 self.auditor = 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 def _fixfilemode(self, name):
266 def _fixfilemode(self, name):
267 if self.createmode is None:
267 if self.createmode is None:
268 return
268 return
269 os.chmod(name, self.createmode & 0666)
269 os.chmod(name, self.createmode & 0666)
270
270
271 def __call__(self, path, mode="r", text=False, atomictemp=False):
271 def __call__(self, path, mode="r", text=False, atomictemp=False):
272 if self._audit:
272 if self._audit:
273 r = util.checkosfilename(path)
273 r = util.checkosfilename(path)
274 if r:
274 if r:
275 raise util.Abort("%s: %r" % (r, path))
275 raise util.Abort("%s: %r" % (r, path))
276 self.auditor(path)
276 self.auditor(path)
277 f = self.join(path)
277 f = self.join(path)
278
278
279 if not text and "b" not in mode:
279 if not text and "b" not in mode:
280 mode += "b" # for that other OS
280 mode += "b" # for that other OS
281
281
282 nlink = -1
282 nlink = -1
283 dirname, basename = util.split(f)
283 dirname, basename = util.split(f)
284 # If basename is empty, then the path is malformed because it points
284 # If basename is empty, then the path is malformed because it points
285 # to a directory. Let the posixfile() call below raise IOError.
285 # to a directory. Let the posixfile() call below raise IOError.
286 if basename and mode not in ('r', 'rb'):
286 if basename and mode not in ('r', 'rb'):
287 if atomictemp:
287 if atomictemp:
288 if not os.path.isdir(dirname):
288 if not os.path.isdir(dirname):
289 util.makedirs(dirname, self.createmode)
289 util.makedirs(dirname, self.createmode)
290 return util.atomictempfile(f, mode, self.createmode)
290 return util.atomictempfile(f, mode, self.createmode)
291 try:
291 try:
292 if 'w' in mode:
292 if 'w' in mode:
293 util.unlink(f)
293 util.unlink(f)
294 nlink = 0
294 nlink = 0
295 else:
295 else:
296 # nlinks() may behave differently for files on Windows
296 # nlinks() may behave differently for files on Windows
297 # shares if the file is open.
297 # shares if the file is open.
298 fd = util.posixfile(f)
298 fd = util.posixfile(f)
299 nlink = util.nlinks(f)
299 nlink = util.nlinks(f)
300 if nlink < 1:
300 if nlink < 1:
301 nlink = 2 # force mktempcopy (issue1922)
301 nlink = 2 # force mktempcopy (issue1922)
302 fd.close()
302 fd.close()
303 except (OSError, IOError), e:
303 except (OSError, IOError), e:
304 if e.errno != errno.ENOENT:
304 if e.errno != errno.ENOENT:
305 raise
305 raise
306 nlink = 0
306 nlink = 0
307 if not os.path.isdir(dirname):
307 if not os.path.isdir(dirname):
308 util.makedirs(dirname, self.createmode)
308 util.makedirs(dirname, self.createmode)
309 if nlink > 0:
309 if nlink > 0:
310 if self._trustnlink is None:
310 if self._trustnlink is None:
311 self._trustnlink = nlink > 1 or util.checknlink(f)
311 self._trustnlink = nlink > 1 or util.checknlink(f)
312 if nlink > 1 or not self._trustnlink:
312 if nlink > 1 or not self._trustnlink:
313 util.rename(util.mktempcopy(f), f)
313 util.rename(util.mktempcopy(f), f)
314 fp = util.posixfile(f, mode)
314 fp = util.posixfile(f, mode)
315 if nlink == 0:
315 if nlink == 0:
316 self._fixfilemode(f)
316 self._fixfilemode(f)
317 return fp
317 return fp
318
318
319 def symlink(self, src, dst):
319 def symlink(self, src, dst):
320 self.auditor(dst)
320 self.auditor(dst)
321 linkname = self.join(dst)
321 linkname = self.join(dst)
322 try:
322 try:
323 os.unlink(linkname)
323 os.unlink(linkname)
324 except OSError:
324 except OSError:
325 pass
325 pass
326
326
327 dirname = os.path.dirname(linkname)
327 dirname = os.path.dirname(linkname)
328 if not os.path.exists(dirname):
328 if not os.path.exists(dirname):
329 util.makedirs(dirname, self.createmode)
329 util.makedirs(dirname, self.createmode)
330
330
331 if self._cansymlink:
331 if self._cansymlink:
332 try:
332 try:
333 os.symlink(src, linkname)
333 os.symlink(src, linkname)
334 except OSError, err:
334 except OSError, err:
335 raise OSError(err.errno, _('could not symlink to %r: %s') %
335 raise OSError(err.errno, _('could not symlink to %r: %s') %
336 (src, err.strerror), linkname)
336 (src, err.strerror), linkname)
337 else:
337 else:
338 self.write(dst, src)
338 self.write(dst, src)
339
339
340 def audit(self, path):
340 def audit(self, path):
341 self.auditor(path)
341 self.auditor(path)
342
342
343 def join(self, path):
343 def join(self, path):
344 if path:
344 if path:
345 return os.path.join(self.base, path)
345 return os.path.join(self.base, path)
346 else:
346 else:
347 return self.base
347 return self.base
348
348
349 opener = vfs
349 opener = vfs
350
350
351 class auditvfs(object):
351 class auditvfs(object):
352 def __init__(self, vfs):
352 def __init__(self, vfs):
353 self.vfs = vfs
353 self.vfs = vfs
354
354
355 def _getmustaudit(self):
355 def _getmustaudit(self):
356 return self.vfs.mustaudit
356 return self.vfs.mustaudit
357
357
358 def _setmustaudit(self, onoff):
358 def _setmustaudit(self, onoff):
359 self.vfs.mustaudit = onoff
359 self.vfs.mustaudit = onoff
360
360
361 mustaudit = property(_getmustaudit, _setmustaudit)
361 mustaudit = property(_getmustaudit, _setmustaudit)
362
362
363 class filtervfs(abstractvfs):
363 class filtervfs(abstractvfs, auditvfs):
364 '''Wrapper vfs for filtering filenames with a function.'''
364 '''Wrapper vfs for filtering filenames with a function.'''
365
365
366 def __init__(self, opener, filter):
366 def __init__(self, vfs, filter):
367 auditvfs.__init__(self, vfs)
367 self._filter = filter
368 self._filter = filter
368 self._orig = opener
369
369
370 def __call__(self, path, *args, **kwargs):
370 def __call__(self, path, *args, **kwargs):
371 return self._orig(self._filter(path), *args, **kwargs)
371 return self.vfs(self._filter(path), *args, **kwargs)
372
372
373 def join(self, path):
373 def join(self, path):
374 if path:
374 if path:
375 return self._orig.join(self._filter(path))
375 return self.vfs.join(self._filter(path))
376 else:
376 else:
377 return self._orig.join(path)
377 return self.vfs.join(path)
378
378
379 filteropener = filtervfs
379 filteropener = filtervfs
380
380
381 def canonpath(root, cwd, myname, auditor=None):
381 def canonpath(root, cwd, myname, auditor=None):
382 '''return the canonical path of myname, given cwd and root'''
382 '''return the canonical path of myname, given cwd and root'''
383 if util.endswithsep(root):
383 if util.endswithsep(root):
384 rootsep = root
384 rootsep = root
385 else:
385 else:
386 rootsep = root + os.sep
386 rootsep = root + os.sep
387 name = myname
387 name = myname
388 if not os.path.isabs(name):
388 if not os.path.isabs(name):
389 name = os.path.join(root, cwd, name)
389 name = os.path.join(root, cwd, name)
390 name = os.path.normpath(name)
390 name = os.path.normpath(name)
391 if auditor is None:
391 if auditor is None:
392 auditor = pathauditor(root)
392 auditor = pathauditor(root)
393 if name != rootsep and name.startswith(rootsep):
393 if name != rootsep and name.startswith(rootsep):
394 name = name[len(rootsep):]
394 name = name[len(rootsep):]
395 auditor(name)
395 auditor(name)
396 return util.pconvert(name)
396 return util.pconvert(name)
397 elif name == root:
397 elif name == root:
398 return ''
398 return ''
399 else:
399 else:
400 # Determine whether `name' is in the hierarchy at or beneath `root',
400 # Determine whether `name' is in the hierarchy at or beneath `root',
401 # 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
402 # check name == '/', because that doesn't work on windows). The list
402 # check name == '/', because that doesn't work on windows). The list
403 # `rel' holds the reversed list of components making up the relative
403 # `rel' holds the reversed list of components making up the relative
404 # file name we want.
404 # file name we want.
405 rel = []
405 rel = []
406 while True:
406 while True:
407 try:
407 try:
408 s = util.samefile(name, root)
408 s = util.samefile(name, root)
409 except OSError:
409 except OSError:
410 s = False
410 s = False
411 if s:
411 if s:
412 if not rel:
412 if not rel:
413 # name was actually the same as root (maybe a symlink)
413 # name was actually the same as root (maybe a symlink)
414 return ''
414 return ''
415 rel.reverse()
415 rel.reverse()
416 name = os.path.join(*rel)
416 name = os.path.join(*rel)
417 auditor(name)
417 auditor(name)
418 return util.pconvert(name)
418 return util.pconvert(name)
419 dirname, basename = util.split(name)
419 dirname, basename = util.split(name)
420 rel.append(basename)
420 rel.append(basename)
421 if dirname == name:
421 if dirname == name:
422 break
422 break
423 name = dirname
423 name = dirname
424
424
425 raise util.Abort('%s not under root' % myname)
425 raise util.Abort('%s not under root' % myname)
426
426
427 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
427 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
428 '''yield every hg repository under path, always recursively.
428 '''yield every hg repository under path, always recursively.
429 The recurse flag will only control recursion into repo working dirs'''
429 The recurse flag will only control recursion into repo working dirs'''
430 def errhandler(err):
430 def errhandler(err):
431 if err.filename == path:
431 if err.filename == path:
432 raise err
432 raise err
433 samestat = getattr(os.path, 'samestat', None)
433 samestat = getattr(os.path, 'samestat', None)
434 if followsym and samestat is not None:
434 if followsym and samestat is not None:
435 def adddir(dirlst, dirname):
435 def adddir(dirlst, dirname):
436 match = False
436 match = False
437 dirstat = os.stat(dirname)
437 dirstat = os.stat(dirname)
438 for lstdirstat in dirlst:
438 for lstdirstat in dirlst:
439 if samestat(dirstat, lstdirstat):
439 if samestat(dirstat, lstdirstat):
440 match = True
440 match = True
441 break
441 break
442 if not match:
442 if not match:
443 dirlst.append(dirstat)
443 dirlst.append(dirstat)
444 return not match
444 return not match
445 else:
445 else:
446 followsym = False
446 followsym = False
447
447
448 if (seen_dirs is None) and followsym:
448 if (seen_dirs is None) and followsym:
449 seen_dirs = []
449 seen_dirs = []
450 adddir(seen_dirs, path)
450 adddir(seen_dirs, path)
451 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):
452 dirs.sort()
452 dirs.sort()
453 if '.hg' in dirs:
453 if '.hg' in dirs:
454 yield root # found a repository
454 yield root # found a repository
455 qroot = os.path.join(root, '.hg', 'patches')
455 qroot = os.path.join(root, '.hg', 'patches')
456 if os.path.isdir(os.path.join(qroot, '.hg')):
456 if os.path.isdir(os.path.join(qroot, '.hg')):
457 yield qroot # we have a patch queue repo here
457 yield qroot # we have a patch queue repo here
458 if recurse:
458 if recurse:
459 # avoid recursing inside the .hg directory
459 # avoid recursing inside the .hg directory
460 dirs.remove('.hg')
460 dirs.remove('.hg')
461 else:
461 else:
462 dirs[:] = [] # don't descend further
462 dirs[:] = [] # don't descend further
463 elif followsym:
463 elif followsym:
464 newdirs = []
464 newdirs = []
465 for d in dirs:
465 for d in dirs:
466 fname = os.path.join(root, d)
466 fname = os.path.join(root, d)
467 if adddir(seen_dirs, fname):
467 if adddir(seen_dirs, fname):
468 if os.path.islink(fname):
468 if os.path.islink(fname):
469 for hgname in walkrepos(fname, True, seen_dirs):
469 for hgname in walkrepos(fname, True, seen_dirs):
470 yield hgname
470 yield hgname
471 else:
471 else:
472 newdirs.append(d)
472 newdirs.append(d)
473 dirs[:] = newdirs
473 dirs[:] = newdirs
474
474
475 def osrcpath():
475 def osrcpath():
476 '''return default os-specific hgrc search path'''
476 '''return default os-specific hgrc search path'''
477 path = systemrcpath()
477 path = systemrcpath()
478 path.extend(userrcpath())
478 path.extend(userrcpath())
479 path = [os.path.normpath(f) for f in path]
479 path = [os.path.normpath(f) for f in path]
480 return path
480 return path
481
481
482 _rcpath = None
482 _rcpath = None
483
483
484 def rcpath():
484 def rcpath():
485 '''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.
486 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,
487 else use item.
487 else use item.
488 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.
489 if no HGRCPATH, use default os-specific path.'''
489 if no HGRCPATH, use default os-specific path.'''
490 global _rcpath
490 global _rcpath
491 if _rcpath is None:
491 if _rcpath is None:
492 if 'HGRCPATH' in os.environ:
492 if 'HGRCPATH' in os.environ:
493 _rcpath = []
493 _rcpath = []
494 for p in os.environ['HGRCPATH'].split(os.pathsep):
494 for p in os.environ['HGRCPATH'].split(os.pathsep):
495 if not p:
495 if not p:
496 continue
496 continue
497 p = util.expandpath(p)
497 p = util.expandpath(p)
498 if os.path.isdir(p):
498 if os.path.isdir(p):
499 for f, kind in osutil.listdir(p):
499 for f, kind in osutil.listdir(p):
500 if f.endswith('.rc'):
500 if f.endswith('.rc'):
501 _rcpath.append(os.path.join(p, f))
501 _rcpath.append(os.path.join(p, f))
502 else:
502 else:
503 _rcpath.append(p)
503 _rcpath.append(p)
504 else:
504 else:
505 _rcpath = osrcpath()
505 _rcpath = osrcpath()
506 return _rcpath
506 return _rcpath
507
507
508 if os.name != 'nt':
508 if os.name != 'nt':
509
509
510 def rcfiles(path):
510 def rcfiles(path):
511 rcs = [os.path.join(path, 'hgrc')]
511 rcs = [os.path.join(path, 'hgrc')]
512 rcdir = os.path.join(path, 'hgrc.d')
512 rcdir = os.path.join(path, 'hgrc.d')
513 try:
513 try:
514 rcs.extend([os.path.join(rcdir, f)
514 rcs.extend([os.path.join(rcdir, f)
515 for f, kind in osutil.listdir(rcdir)
515 for f, kind in osutil.listdir(rcdir)
516 if f.endswith(".rc")])
516 if f.endswith(".rc")])
517 except OSError:
517 except OSError:
518 pass
518 pass
519 return rcs
519 return rcs
520
520
521 def systemrcpath():
521 def systemrcpath():
522 path = []
522 path = []
523 if sys.platform == 'plan9':
523 if sys.platform == 'plan9':
524 root = 'lib/mercurial'
524 root = 'lib/mercurial'
525 else:
525 else:
526 root = 'etc/mercurial'
526 root = 'etc/mercurial'
527 # old mod_python does not set sys.argv
527 # old mod_python does not set sys.argv
528 if len(getattr(sys, 'argv', [])) > 0:
528 if len(getattr(sys, 'argv', [])) > 0:
529 p = os.path.dirname(os.path.dirname(sys.argv[0]))
529 p = os.path.dirname(os.path.dirname(sys.argv[0]))
530 path.extend(rcfiles(os.path.join(p, root)))
530 path.extend(rcfiles(os.path.join(p, root)))
531 path.extend(rcfiles('/' + root))
531 path.extend(rcfiles('/' + root))
532 return path
532 return path
533
533
534 def userrcpath():
534 def userrcpath():
535 if sys.platform == 'plan9':
535 if sys.platform == 'plan9':
536 return [os.environ['home'] + '/lib/hgrc']
536 return [os.environ['home'] + '/lib/hgrc']
537 else:
537 else:
538 return [os.path.expanduser('~/.hgrc')]
538 return [os.path.expanduser('~/.hgrc')]
539
539
540 else:
540 else:
541
541
542 import _winreg
542 import _winreg
543
543
544 def systemrcpath():
544 def systemrcpath():
545 '''return default os-specific hgrc search path'''
545 '''return default os-specific hgrc search path'''
546 rcpath = []
546 rcpath = []
547 filename = util.executablepath()
547 filename = util.executablepath()
548 # Use mercurial.ini found in directory with hg.exe
548 # Use mercurial.ini found in directory with hg.exe
549 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
549 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
550 if os.path.isfile(progrc):
550 if os.path.isfile(progrc):
551 rcpath.append(progrc)
551 rcpath.append(progrc)
552 return rcpath
552 return rcpath
553 # Use hgrc.d found in directory with hg.exe
553 # Use hgrc.d found in directory with hg.exe
554 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
554 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
555 if os.path.isdir(progrcd):
555 if os.path.isdir(progrcd):
556 for f, kind in osutil.listdir(progrcd):
556 for f, kind in osutil.listdir(progrcd):
557 if f.endswith('.rc'):
557 if f.endswith('.rc'):
558 rcpath.append(os.path.join(progrcd, f))
558 rcpath.append(os.path.join(progrcd, f))
559 return rcpath
559 return rcpath
560 # else look for a system rcpath in the registry
560 # else look for a system rcpath in the registry
561 value = util.lookupreg('SOFTWARE\\Mercurial', None,
561 value = util.lookupreg('SOFTWARE\\Mercurial', None,
562 _winreg.HKEY_LOCAL_MACHINE)
562 _winreg.HKEY_LOCAL_MACHINE)
563 if not isinstance(value, str) or not value:
563 if not isinstance(value, str) or not value:
564 return rcpath
564 return rcpath
565 value = util.localpath(value)
565 value = util.localpath(value)
566 for p in value.split(os.pathsep):
566 for p in value.split(os.pathsep):
567 if p.lower().endswith('mercurial.ini'):
567 if p.lower().endswith('mercurial.ini'):
568 rcpath.append(p)
568 rcpath.append(p)
569 elif os.path.isdir(p):
569 elif os.path.isdir(p):
570 for f, kind in osutil.listdir(p):
570 for f, kind in osutil.listdir(p):
571 if f.endswith('.rc'):
571 if f.endswith('.rc'):
572 rcpath.append(os.path.join(p, f))
572 rcpath.append(os.path.join(p, f))
573 return rcpath
573 return rcpath
574
574
575 def userrcpath():
575 def userrcpath():
576 '''return os-specific hgrc search path to the user dir'''
576 '''return os-specific hgrc search path to the user dir'''
577 home = os.path.expanduser('~')
577 home = os.path.expanduser('~')
578 path = [os.path.join(home, 'mercurial.ini'),
578 path = [os.path.join(home, 'mercurial.ini'),
579 os.path.join(home, '.hgrc')]
579 os.path.join(home, '.hgrc')]
580 userprofile = os.environ.get('USERPROFILE')
580 userprofile = os.environ.get('USERPROFILE')
581 if userprofile:
581 if userprofile:
582 path.append(os.path.join(userprofile, 'mercurial.ini'))
582 path.append(os.path.join(userprofile, 'mercurial.ini'))
583 path.append(os.path.join(userprofile, '.hgrc'))
583 path.append(os.path.join(userprofile, '.hgrc'))
584 return path
584 return path
585
585
586 def revsingle(repo, revspec, default='.'):
586 def revsingle(repo, revspec, default='.'):
587 if not revspec:
587 if not revspec:
588 return repo[default]
588 return repo[default]
589
589
590 l = revrange(repo, [revspec])
590 l = revrange(repo, [revspec])
591 if len(l) < 1:
591 if len(l) < 1:
592 raise util.Abort(_('empty revision set'))
592 raise util.Abort(_('empty revision set'))
593 return repo[l[-1]]
593 return repo[l[-1]]
594
594
595 def revpair(repo, revs):
595 def revpair(repo, revs):
596 if not revs:
596 if not revs:
597 return repo.dirstate.p1(), None
597 return repo.dirstate.p1(), None
598
598
599 l = revrange(repo, revs)
599 l = revrange(repo, revs)
600
600
601 if len(l) == 0:
601 if len(l) == 0:
602 if revs:
602 if revs:
603 raise util.Abort(_('empty revision range'))
603 raise util.Abort(_('empty revision range'))
604 return repo.dirstate.p1(), None
604 return repo.dirstate.p1(), None
605
605
606 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]:
607 return repo.lookup(l[0]), None
607 return repo.lookup(l[0]), None
608
608
609 return repo.lookup(l[0]), repo.lookup(l[-1])
609 return repo.lookup(l[0]), repo.lookup(l[-1])
610
610
611 _revrangesep = ':'
611 _revrangesep = ':'
612
612
613 def revrange(repo, revs):
613 def revrange(repo, revs):
614 """Yield revision as strings from a list of revision specifications."""
614 """Yield revision as strings from a list of revision specifications."""
615
615
616 def revfix(repo, val, defval):
616 def revfix(repo, val, defval):
617 if not val and val != 0 and defval is not None:
617 if not val and val != 0 and defval is not None:
618 return defval
618 return defval
619 return repo[val].rev()
619 return repo[val].rev()
620
620
621 seen, l = set(), []
621 seen, l = set(), []
622 for spec in revs:
622 for spec in revs:
623 if l and not seen:
623 if l and not seen:
624 seen = set(l)
624 seen = set(l)
625 # attempt to parse old-style ranges first to deal with
625 # attempt to parse old-style ranges first to deal with
626 # things like old-tag which contain query metacharacters
626 # things like old-tag which contain query metacharacters
627 try:
627 try:
628 if isinstance(spec, int):
628 if isinstance(spec, int):
629 seen.add(spec)
629 seen.add(spec)
630 l.append(spec)
630 l.append(spec)
631 continue
631 continue
632
632
633 if _revrangesep in spec:
633 if _revrangesep in spec:
634 start, end = spec.split(_revrangesep, 1)
634 start, end = spec.split(_revrangesep, 1)
635 start = revfix(repo, start, 0)
635 start = revfix(repo, start, 0)
636 end = revfix(repo, end, len(repo) - 1)
636 end = revfix(repo, end, len(repo) - 1)
637 step = start > end and -1 or 1
637 step = start > end and -1 or 1
638 if not seen and not l:
638 if not seen and not l:
639 # by far the most common case: revs = ["-1:0"]
639 # by far the most common case: revs = ["-1:0"]
640 l = range(start, end + step, step)
640 l = range(start, end + step, step)
641 # defer syncing seen until next iteration
641 # defer syncing seen until next iteration
642 continue
642 continue
643 newrevs = set(xrange(start, end + step, step))
643 newrevs = set(xrange(start, end + step, step))
644 if seen:
644 if seen:
645 newrevs.difference_update(seen)
645 newrevs.difference_update(seen)
646 seen.update(newrevs)
646 seen.update(newrevs)
647 else:
647 else:
648 seen = newrevs
648 seen = newrevs
649 l.extend(sorted(newrevs, reverse=start > end))
649 l.extend(sorted(newrevs, reverse=start > end))
650 continue
650 continue
651 elif spec and spec in repo: # single unquoted rev
651 elif spec and spec in repo: # single unquoted rev
652 rev = revfix(repo, spec, None)
652 rev = revfix(repo, spec, None)
653 if rev in seen:
653 if rev in seen:
654 continue
654 continue
655 seen.add(rev)
655 seen.add(rev)
656 l.append(rev)
656 l.append(rev)
657 continue
657 continue
658 except error.RepoLookupError:
658 except error.RepoLookupError:
659 pass
659 pass
660
660
661 # fall through to new-style queries if old-style fails
661 # fall through to new-style queries if old-style fails
662 m = revset.match(repo.ui, spec)
662 m = revset.match(repo.ui, spec)
663 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]
664 l.extend(dl)
664 l.extend(dl)
665 seen.update(dl)
665 seen.update(dl)
666
666
667 return l
667 return l
668
668
669 def expandpats(pats):
669 def expandpats(pats):
670 if not util.expandglobs:
670 if not util.expandglobs:
671 return list(pats)
671 return list(pats)
672 ret = []
672 ret = []
673 for p in pats:
673 for p in pats:
674 kind, name = matchmod._patsplit(p, None)
674 kind, name = matchmod._patsplit(p, None)
675 if kind is None:
675 if kind is None:
676 try:
676 try:
677 globbed = glob.glob(name)
677 globbed = glob.glob(name)
678 except re.error:
678 except re.error:
679 globbed = [name]
679 globbed = [name]
680 if globbed:
680 if globbed:
681 ret.extend(globbed)
681 ret.extend(globbed)
682 continue
682 continue
683 ret.append(p)
683 ret.append(p)
684 return ret
684 return ret
685
685
686 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
686 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
687 if pats == ("",):
687 if pats == ("",):
688 pats = []
688 pats = []
689 if not globbed and default == 'relpath':
689 if not globbed and default == 'relpath':
690 pats = expandpats(pats or [])
690 pats = expandpats(pats or [])
691
691
692 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
692 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
693 default)
693 default)
694 def badfn(f, msg):
694 def badfn(f, msg):
695 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
695 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
696 m.bad = badfn
696 m.bad = badfn
697 return m, pats
697 return m, pats
698
698
699 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
699 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
700 return matchandpats(ctx, pats, opts, globbed, default)[0]
700 return matchandpats(ctx, pats, opts, globbed, default)[0]
701
701
702 def matchall(repo):
702 def matchall(repo):
703 return matchmod.always(repo.root, repo.getcwd())
703 return matchmod.always(repo.root, repo.getcwd())
704
704
705 def matchfiles(repo, files):
705 def matchfiles(repo, files):
706 return matchmod.exact(repo.root, repo.getcwd(), files)
706 return matchmod.exact(repo.root, repo.getcwd(), files)
707
707
708 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
708 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
709 if dry_run is None:
709 if dry_run is None:
710 dry_run = opts.get('dry_run')
710 dry_run = opts.get('dry_run')
711 if similarity is None:
711 if similarity is None:
712 similarity = float(opts.get('similarity') or 0)
712 similarity = float(opts.get('similarity') or 0)
713 # 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
714 added, unknown, deleted, removed = [], [], [], []
714 added, unknown, deleted, removed = [], [], [], []
715 audit_path = pathauditor(repo.root)
715 audit_path = pathauditor(repo.root)
716 m = match(repo[None], pats, opts)
716 m = match(repo[None], pats, opts)
717 rejected = []
717 rejected = []
718 m.bad = lambda x, y: rejected.append(x)
718 m.bad = lambda x, y: rejected.append(x)
719
719
720 for abs in repo.walk(m):
720 for abs in repo.walk(m):
721 target = repo.wjoin(abs)
721 target = repo.wjoin(abs)
722 good = True
722 good = True
723 try:
723 try:
724 audit_path(abs)
724 audit_path(abs)
725 except (OSError, util.Abort):
725 except (OSError, util.Abort):
726 good = False
726 good = False
727 rel = m.rel(abs)
727 rel = m.rel(abs)
728 exact = m.exact(abs)
728 exact = m.exact(abs)
729 if good and abs not in repo.dirstate:
729 if good and abs not in repo.dirstate:
730 unknown.append(abs)
730 unknown.append(abs)
731 if repo.ui.verbose or not exact:
731 if repo.ui.verbose or not exact:
732 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
732 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
733 elif (repo.dirstate[abs] != 'r' and
733 elif (repo.dirstate[abs] != 'r' and
734 (not good or not os.path.lexists(target) or
734 (not good or not os.path.lexists(target) or
735 (os.path.isdir(target) and not os.path.islink(target)))):
735 (os.path.isdir(target) and not os.path.islink(target)))):
736 deleted.append(abs)
736 deleted.append(abs)
737 if repo.ui.verbose or not exact:
737 if repo.ui.verbose or not exact:
738 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
738 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
739 # for finding renames
739 # for finding renames
740 elif repo.dirstate[abs] == 'r':
740 elif repo.dirstate[abs] == 'r':
741 removed.append(abs)
741 removed.append(abs)
742 elif repo.dirstate[abs] == 'a':
742 elif repo.dirstate[abs] == 'a':
743 added.append(abs)
743 added.append(abs)
744 copies = {}
744 copies = {}
745 if similarity > 0:
745 if similarity > 0:
746 for old, new, score in similar.findrenames(repo,
746 for old, new, score in similar.findrenames(repo,
747 added + unknown, removed + deleted, similarity):
747 added + unknown, removed + deleted, similarity):
748 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):
749 repo.ui.status(_('recording removal of %s as rename to %s '
749 repo.ui.status(_('recording removal of %s as rename to %s '
750 '(%d%% similar)\n') %
750 '(%d%% similar)\n') %
751 (m.rel(old), m.rel(new), score * 100))
751 (m.rel(old), m.rel(new), score * 100))
752 copies[new] = old
752 copies[new] = old
753
753
754 if not dry_run:
754 if not dry_run:
755 wctx = repo[None]
755 wctx = repo[None]
756 wlock = repo.wlock()
756 wlock = repo.wlock()
757 try:
757 try:
758 wctx.forget(deleted)
758 wctx.forget(deleted)
759 wctx.add(unknown)
759 wctx.add(unknown)
760 for new, old in copies.iteritems():
760 for new, old in copies.iteritems():
761 wctx.copy(old, new)
761 wctx.copy(old, new)
762 finally:
762 finally:
763 wlock.release()
763 wlock.release()
764
764
765 for f in rejected:
765 for f in rejected:
766 if f in m.files():
766 if f in m.files():
767 return 1
767 return 1
768 return 0
768 return 0
769
769
770 def updatedir(ui, repo, patches, similarity=0):
770 def updatedir(ui, repo, patches, similarity=0):
771 '''Update dirstate after patch application according to metadata'''
771 '''Update dirstate after patch application according to metadata'''
772 if not patches:
772 if not patches:
773 return []
773 return []
774 copies = []
774 copies = []
775 removes = set()
775 removes = set()
776 cfiles = patches.keys()
776 cfiles = patches.keys()
777 cwd = repo.getcwd()
777 cwd = repo.getcwd()
778 if cwd:
778 if cwd:
779 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()]
780 for f in patches:
780 for f in patches:
781 gp = patches[f]
781 gp = patches[f]
782 if not gp:
782 if not gp:
783 continue
783 continue
784 if gp.op == 'RENAME':
784 if gp.op == 'RENAME':
785 copies.append((gp.oldpath, gp.path))
785 copies.append((gp.oldpath, gp.path))
786 removes.add(gp.oldpath)
786 removes.add(gp.oldpath)
787 elif gp.op == 'COPY':
787 elif gp.op == 'COPY':
788 copies.append((gp.oldpath, gp.path))
788 copies.append((gp.oldpath, gp.path))
789 elif gp.op == 'DELETE':
789 elif gp.op == 'DELETE':
790 removes.add(gp.path)
790 removes.add(gp.path)
791
791
792 wctx = repo[None]
792 wctx = repo[None]
793 for src, dst in copies:
793 for src, dst in copies:
794 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
794 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
795 if (not similarity) and removes:
795 if (not similarity) and removes:
796 wctx.remove(sorted(removes), True)
796 wctx.remove(sorted(removes), True)
797
797
798 for f in patches:
798 for f in patches:
799 gp = patches[f]
799 gp = patches[f]
800 if gp and gp.mode:
800 if gp and gp.mode:
801 islink, isexec = gp.mode
801 islink, isexec = gp.mode
802 dst = repo.wjoin(gp.path)
802 dst = repo.wjoin(gp.path)
803 # patch won't create empty files
803 # patch won't create empty files
804 if gp.op == 'ADD' and not os.path.lexists(dst):
804 if gp.op == 'ADD' and not os.path.lexists(dst):
805 flags = (isexec and 'x' or '') + (islink and 'l' or '')
805 flags = (isexec and 'x' or '') + (islink and 'l' or '')
806 repo.wwrite(gp.path, '', flags)
806 repo.wwrite(gp.path, '', flags)
807 util.setflags(dst, islink, isexec)
807 util.setflags(dst, islink, isexec)
808 addremove(repo, cfiles, similarity=similarity)
808 addremove(repo, cfiles, similarity=similarity)
809 files = patches.keys()
809 files = patches.keys()
810 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])
811 return sorted(files)
811 return sorted(files)
812
812
813 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
813 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
814 """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
815 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.
816 """
816 """
817 origsrc = repo.dirstate.copied(src) or src
817 origsrc = repo.dirstate.copied(src) or src
818 if dst == origsrc: # copying back a copy?
818 if dst == origsrc: # copying back a copy?
819 if repo.dirstate[dst] not in 'mn' and not dryrun:
819 if repo.dirstate[dst] not in 'mn' and not dryrun:
820 repo.dirstate.normallookup(dst)
820 repo.dirstate.normallookup(dst)
821 else:
821 else:
822 if repo.dirstate[origsrc] == 'a' and origsrc == src:
822 if repo.dirstate[origsrc] == 'a' and origsrc == src:
823 if not ui.quiet:
823 if not ui.quiet:
824 ui.warn(_("%s has not been committed yet, so no copy "
824 ui.warn(_("%s has not been committed yet, so no copy "
825 "data will be stored for %s.\n")
825 "data will be stored for %s.\n")
826 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
826 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
827 if repo.dirstate[dst] in '?r' and not dryrun:
827 if repo.dirstate[dst] in '?r' and not dryrun:
828 wctx.add([dst])
828 wctx.add([dst])
829 elif not dryrun:
829 elif not dryrun:
830 wctx.copy(origsrc, dst)
830 wctx.copy(origsrc, dst)
831
831
832 def readrequires(opener, supported):
832 def readrequires(opener, supported):
833 '''Reads and parses .hg/requires and checks if all entries found
833 '''Reads and parses .hg/requires and checks if all entries found
834 are in the list of supported features.'''
834 are in the list of supported features.'''
835 requirements = set(opener.read("requires").splitlines())
835 requirements = set(opener.read("requires").splitlines())
836 missings = []
836 missings = []
837 for r in requirements:
837 for r in requirements:
838 if r not in supported:
838 if r not in supported:
839 if not r or not r[0].isalnum():
839 if not r or not r[0].isalnum():
840 raise error.RequirementError(_(".hg/requires file is corrupt"))
840 raise error.RequirementError(_(".hg/requires file is corrupt"))
841 missings.append(r)
841 missings.append(r)
842 missings.sort()
842 missings.sort()
843 if missings:
843 if missings:
844 raise error.RequirementError(
844 raise error.RequirementError(
845 _("unknown repository format: requires features '%s' (upgrade "
845 _("unknown repository format: requires features '%s' (upgrade "
846 "Mercurial)") % "', '".join(missings))
846 "Mercurial)") % "', '".join(missings))
847 return requirements
847 return requirements
848
848
849 class filecacheentry(object):
849 class filecacheentry(object):
850 def __init__(self, path):
850 def __init__(self, path):
851 self.path = path
851 self.path = path
852 self.cachestat = filecacheentry.stat(self.path)
852 self.cachestat = filecacheentry.stat(self.path)
853
853
854 if self.cachestat:
854 if self.cachestat:
855 self._cacheable = self.cachestat.cacheable()
855 self._cacheable = self.cachestat.cacheable()
856 else:
856 else:
857 # None means we don't know yet
857 # None means we don't know yet
858 self._cacheable = None
858 self._cacheable = None
859
859
860 def refresh(self):
860 def refresh(self):
861 if self.cacheable():
861 if self.cacheable():
862 self.cachestat = filecacheentry.stat(self.path)
862 self.cachestat = filecacheentry.stat(self.path)
863
863
864 def cacheable(self):
864 def cacheable(self):
865 if self._cacheable is not None:
865 if self._cacheable is not None:
866 return self._cacheable
866 return self._cacheable
867
867
868 # we don't know yet, assume it is for now
868 # we don't know yet, assume it is for now
869 return True
869 return True
870
870
871 def changed(self):
871 def changed(self):
872 # no point in going further if we can't cache it
872 # no point in going further if we can't cache it
873 if not self.cacheable():
873 if not self.cacheable():
874 return True
874 return True
875
875
876 newstat = filecacheentry.stat(self.path)
876 newstat = filecacheentry.stat(self.path)
877
877
878 # 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
879 if newstat and self._cacheable is None:
879 if newstat and self._cacheable is None:
880 self._cacheable = newstat.cacheable()
880 self._cacheable = newstat.cacheable()
881
881
882 # check again
882 # check again
883 if not self._cacheable:
883 if not self._cacheable:
884 return True
884 return True
885
885
886 if self.cachestat != newstat:
886 if self.cachestat != newstat:
887 self.cachestat = newstat
887 self.cachestat = newstat
888 return True
888 return True
889 else:
889 else:
890 return False
890 return False
891
891
892 @staticmethod
892 @staticmethod
893 def stat(path):
893 def stat(path):
894 try:
894 try:
895 return util.cachestat(path)
895 return util.cachestat(path)
896 except OSError, e:
896 except OSError, e:
897 if e.errno != errno.ENOENT:
897 if e.errno != errno.ENOENT:
898 raise
898 raise
899
899
900 class filecache(object):
900 class filecache(object):
901 '''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.
902
902
903 Records stat info when called in _filecache.
903 Records stat info when called in _filecache.
904
904
905 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
906 the object when needed, updating the new stat info in _filecache.
906 the object when needed, updating the new stat info in _filecache.
907
907
908 Mercurial either atomic renames or appends for files under .hg,
908 Mercurial either atomic renames or appends for files under .hg,
909 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
910 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
911 recreating the object on every call (essentially the same behaviour as
911 recreating the object on every call (essentially the same behaviour as
912 propertycache).'''
912 propertycache).'''
913 def __init__(self, path):
913 def __init__(self, path):
914 self.path = path
914 self.path = path
915
915
916 def join(self, obj, fname):
916 def join(self, obj, fname):
917 """Used to compute the runtime path of the cached file.
917 """Used to compute the runtime path of the cached file.
918
918
919 Users should subclass filecache and provide their own version of this
919 Users should subclass filecache and provide their own version of this
920 function to call the appropriate join function on 'obj' (an instance
920 function to call the appropriate join function on 'obj' (an instance
921 of the class that its member function was decorated).
921 of the class that its member function was decorated).
922 """
922 """
923 return obj.join(fname)
923 return obj.join(fname)
924
924
925 def __call__(self, func):
925 def __call__(self, func):
926 self.func = func
926 self.func = func
927 self.name = func.__name__
927 self.name = func.__name__
928 return self
928 return self
929
929
930 def __get__(self, obj, type=None):
930 def __get__(self, obj, type=None):
931 # do we need to check if the file changed?
931 # do we need to check if the file changed?
932 if self.name in obj.__dict__:
932 if self.name in obj.__dict__:
933 return obj.__dict__[self.name]
933 return obj.__dict__[self.name]
934
934
935 entry = obj._filecache.get(self.name)
935 entry = obj._filecache.get(self.name)
936
936
937 if entry:
937 if entry:
938 if entry.changed():
938 if entry.changed():
939 entry.obj = self.func(obj)
939 entry.obj = self.func(obj)
940 else:
940 else:
941 path = self.join(obj, self.path)
941 path = self.join(obj, self.path)
942
942
943 # 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
944 # a writer modified between the time we read and stat
944 # a writer modified between the time we read and stat
945 entry = filecacheentry(path)
945 entry = filecacheentry(path)
946 entry.obj = self.func(obj)
946 entry.obj = self.func(obj)
947
947
948 obj._filecache[self.name] = entry
948 obj._filecache[self.name] = entry
949
949
950 obj.__dict__[self.name] = entry.obj
950 obj.__dict__[self.name] = entry.obj
951 return entry.obj
951 return entry.obj
952
952
953 def __set__(self, obj, value):
953 def __set__(self, obj, value):
954 if self.name in obj._filecache:
954 if self.name in obj._filecache:
955 obj._filecache[self.name].obj = value # update cached copy
955 obj._filecache[self.name].obj = value # update cached copy
956 obj.__dict__[self.name] = value # update copy returned by obj.x
956 obj.__dict__[self.name] = value # update copy returned by obj.x
957
957
958 def __delete__(self, obj):
958 def __delete__(self, obj):
959 try:
959 try:
960 del obj.__dict__[self.name]
960 del obj.__dict__[self.name]
961 except KeyError:
961 except KeyError:
962 raise AttributeError, self.name
962 raise AttributeError, self.name
General Comments 0
You need to be logged in to leave comments. Login now