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