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