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