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