##// END OF EJS Templates
scmutil: rename classes from "opener" to "vfs"...
FUJIWARA Katsunori -
r17649:f65c6a5f default
parent child Browse files
Show More
@@ -1,929 +1,933 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 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 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 vfs(abstractvfs):
223 '''Open files relative to a base directory
223 '''Operate 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.basesep = self.base + os.sep
233 self._setmustaudit(audit)
233 self._setmustaudit(audit)
234 self.createmode = None
234 self.createmode = None
235 self._trustnlink = None
235 self._trustnlink = None
236
236
237 def _getmustaudit(self):
237 def _getmustaudit(self):
238 return self._audit
238 return self._audit
239
239
240 def _setmustaudit(self, onoff):
240 def _setmustaudit(self, onoff):
241 self._audit = onoff
241 self._audit = onoff
242 if onoff:
242 if onoff:
243 self.auditor = pathauditor(self.base)
243 self.auditor = pathauditor(self.base)
244 else:
244 else:
245 self.auditor = util.always
245 self.auditor = util.always
246
246
247 mustaudit = property(_getmustaudit, _setmustaudit)
247 mustaudit = property(_getmustaudit, _setmustaudit)
248
248
249 @util.propertycache
249 @util.propertycache
250 def _cansymlink(self):
250 def _cansymlink(self):
251 return util.checklink(self.base)
251 return util.checklink(self.base)
252
252
253 def _fixfilemode(self, name):
253 def _fixfilemode(self, name):
254 if self.createmode is None:
254 if self.createmode is None:
255 return
255 return
256 os.chmod(name, self.createmode & 0666)
256 os.chmod(name, self.createmode & 0666)
257
257
258 def __call__(self, path, mode="r", text=False, atomictemp=False):
258 def __call__(self, path, mode="r", text=False, atomictemp=False):
259 if self._audit:
259 if self._audit:
260 r = util.checkosfilename(path)
260 r = util.checkosfilename(path)
261 if r:
261 if r:
262 raise util.Abort("%s: %r" % (r, path))
262 raise util.Abort("%s: %r" % (r, path))
263 self.auditor(path)
263 self.auditor(path)
264 f = self.join(path)
264 f = self.join(path)
265
265
266 if not text and "b" not in mode:
266 if not text and "b" not in mode:
267 mode += "b" # for that other OS
267 mode += "b" # for that other OS
268
268
269 nlink = -1
269 nlink = -1
270 dirname, basename = util.split(f)
270 dirname, basename = util.split(f)
271 # 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
272 # to a directory. Let the posixfile() call below raise IOError.
272 # to a directory. Let the posixfile() call below raise IOError.
273 if basename and mode not in ('r', 'rb'):
273 if basename and mode not in ('r', 'rb'):
274 if atomictemp:
274 if atomictemp:
275 if not os.path.isdir(dirname):
275 if not os.path.isdir(dirname):
276 util.makedirs(dirname, self.createmode)
276 util.makedirs(dirname, self.createmode)
277 return util.atomictempfile(f, mode, self.createmode)
277 return util.atomictempfile(f, mode, self.createmode)
278 try:
278 try:
279 if 'w' in mode:
279 if 'w' in mode:
280 util.unlink(f)
280 util.unlink(f)
281 nlink = 0
281 nlink = 0
282 else:
282 else:
283 # nlinks() may behave differently for files on Windows
283 # nlinks() may behave differently for files on Windows
284 # shares if the file is open.
284 # shares if the file is open.
285 fd = util.posixfile(f)
285 fd = util.posixfile(f)
286 nlink = util.nlinks(f)
286 nlink = util.nlinks(f)
287 if nlink < 1:
287 if nlink < 1:
288 nlink = 2 # force mktempcopy (issue1922)
288 nlink = 2 # force mktempcopy (issue1922)
289 fd.close()
289 fd.close()
290 except (OSError, IOError), e:
290 except (OSError, IOError), e:
291 if e.errno != errno.ENOENT:
291 if e.errno != errno.ENOENT:
292 raise
292 raise
293 nlink = 0
293 nlink = 0
294 if not os.path.isdir(dirname):
294 if not os.path.isdir(dirname):
295 util.makedirs(dirname, self.createmode)
295 util.makedirs(dirname, self.createmode)
296 if nlink > 0:
296 if nlink > 0:
297 if self._trustnlink is None:
297 if self._trustnlink is None:
298 self._trustnlink = nlink > 1 or util.checknlink(f)
298 self._trustnlink = nlink > 1 or util.checknlink(f)
299 if nlink > 1 or not self._trustnlink:
299 if nlink > 1 or not self._trustnlink:
300 util.rename(util.mktempcopy(f), f)
300 util.rename(util.mktempcopy(f), f)
301 fp = util.posixfile(f, mode)
301 fp = util.posixfile(f, mode)
302 if nlink == 0:
302 if nlink == 0:
303 self._fixfilemode(f)
303 self._fixfilemode(f)
304 return fp
304 return fp
305
305
306 def symlink(self, src, dst):
306 def symlink(self, src, dst):
307 self.auditor(dst)
307 self.auditor(dst)
308 linkname = self.join(dst)
308 linkname = self.join(dst)
309 try:
309 try:
310 os.unlink(linkname)
310 os.unlink(linkname)
311 except OSError:
311 except OSError:
312 pass
312 pass
313
313
314 dirname = os.path.dirname(linkname)
314 dirname = os.path.dirname(linkname)
315 if not os.path.exists(dirname):
315 if not os.path.exists(dirname):
316 util.makedirs(dirname, self.createmode)
316 util.makedirs(dirname, self.createmode)
317
317
318 if self._cansymlink:
318 if self._cansymlink:
319 try:
319 try:
320 os.symlink(src, linkname)
320 os.symlink(src, linkname)
321 except OSError, err:
321 except OSError, err:
322 raise OSError(err.errno, _('could not symlink to %r: %s') %
322 raise OSError(err.errno, _('could not symlink to %r: %s') %
323 (src, err.strerror), linkname)
323 (src, err.strerror), linkname)
324 else:
324 else:
325 f = self(dst, "w")
325 f = self(dst, "w")
326 f.write(src)
326 f.write(src)
327 f.close()
327 f.close()
328 self._fixfilemode(dst)
328 self._fixfilemode(dst)
329
329
330 def audit(self, path):
330 def audit(self, path):
331 self.auditor(path)
331 self.auditor(path)
332
332
333 def join(self, path):
333 def join(self, path):
334 if path:
334 if path:
335 return path.startswith('/') and path or (self.basesep + path)
335 return path.startswith('/') and path or (self.basesep + path)
336 return self.base
336 return self.base
337
337
338 class filteropener(abstractopener):
338 opener = vfs
339 '''Wrapper opener for filtering filenames with a function.'''
339
340 class filtervfs(abstractvfs):
341 '''Wrapper vfs for filtering filenames with a function.'''
340
342
341 def __init__(self, opener, filter):
343 def __init__(self, opener, filter):
342 self._filter = filter
344 self._filter = filter
343 self._orig = opener
345 self._orig = opener
344
346
345 def __call__(self, path, *args, **kwargs):
347 def __call__(self, path, *args, **kwargs):
346 return self._orig(self._filter(path), *args, **kwargs)
348 return self._orig(self._filter(path), *args, **kwargs)
347
349
350 filteropener = filtervfs
351
348 def canonpath(root, cwd, myname, auditor=None):
352 def canonpath(root, cwd, myname, auditor=None):
349 '''return the canonical path of myname, given cwd and root'''
353 '''return the canonical path of myname, given cwd and root'''
350 if util.endswithsep(root):
354 if util.endswithsep(root):
351 rootsep = root
355 rootsep = root
352 else:
356 else:
353 rootsep = root + os.sep
357 rootsep = root + os.sep
354 name = myname
358 name = myname
355 if not os.path.isabs(name):
359 if not os.path.isabs(name):
356 name = os.path.join(root, cwd, name)
360 name = os.path.join(root, cwd, name)
357 name = os.path.normpath(name)
361 name = os.path.normpath(name)
358 if auditor is None:
362 if auditor is None:
359 auditor = pathauditor(root)
363 auditor = pathauditor(root)
360 if name != rootsep and name.startswith(rootsep):
364 if name != rootsep and name.startswith(rootsep):
361 name = name[len(rootsep):]
365 name = name[len(rootsep):]
362 auditor(name)
366 auditor(name)
363 return util.pconvert(name)
367 return util.pconvert(name)
364 elif name == root:
368 elif name == root:
365 return ''
369 return ''
366 else:
370 else:
367 # Determine whether `name' is in the hierarchy at or beneath `root',
371 # Determine whether `name' is in the hierarchy at or beneath `root',
368 # by iterating name=dirname(name) until that causes no change (can't
372 # by iterating name=dirname(name) until that causes no change (can't
369 # check name == '/', because that doesn't work on windows). The list
373 # check name == '/', because that doesn't work on windows). The list
370 # `rel' holds the reversed list of components making up the relative
374 # `rel' holds the reversed list of components making up the relative
371 # file name we want.
375 # file name we want.
372 rel = []
376 rel = []
373 while True:
377 while True:
374 try:
378 try:
375 s = util.samefile(name, root)
379 s = util.samefile(name, root)
376 except OSError:
380 except OSError:
377 s = False
381 s = False
378 if s:
382 if s:
379 if not rel:
383 if not rel:
380 # name was actually the same as root (maybe a symlink)
384 # name was actually the same as root (maybe a symlink)
381 return ''
385 return ''
382 rel.reverse()
386 rel.reverse()
383 name = os.path.join(*rel)
387 name = os.path.join(*rel)
384 auditor(name)
388 auditor(name)
385 return util.pconvert(name)
389 return util.pconvert(name)
386 dirname, basename = util.split(name)
390 dirname, basename = util.split(name)
387 rel.append(basename)
391 rel.append(basename)
388 if dirname == name:
392 if dirname == name:
389 break
393 break
390 name = dirname
394 name = dirname
391
395
392 raise util.Abort('%s not under root' % myname)
396 raise util.Abort('%s not under root' % myname)
393
397
394 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
398 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
395 '''yield every hg repository under path, always recursively.
399 '''yield every hg repository under path, always recursively.
396 The recurse flag will only control recursion into repo working dirs'''
400 The recurse flag will only control recursion into repo working dirs'''
397 def errhandler(err):
401 def errhandler(err):
398 if err.filename == path:
402 if err.filename == path:
399 raise err
403 raise err
400 samestat = getattr(os.path, 'samestat', None)
404 samestat = getattr(os.path, 'samestat', None)
401 if followsym and samestat is not None:
405 if followsym and samestat is not None:
402 def adddir(dirlst, dirname):
406 def adddir(dirlst, dirname):
403 match = False
407 match = False
404 dirstat = os.stat(dirname)
408 dirstat = os.stat(dirname)
405 for lstdirstat in dirlst:
409 for lstdirstat in dirlst:
406 if samestat(dirstat, lstdirstat):
410 if samestat(dirstat, lstdirstat):
407 match = True
411 match = True
408 break
412 break
409 if not match:
413 if not match:
410 dirlst.append(dirstat)
414 dirlst.append(dirstat)
411 return not match
415 return not match
412 else:
416 else:
413 followsym = False
417 followsym = False
414
418
415 if (seen_dirs is None) and followsym:
419 if (seen_dirs is None) and followsym:
416 seen_dirs = []
420 seen_dirs = []
417 adddir(seen_dirs, path)
421 adddir(seen_dirs, path)
418 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
422 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
419 dirs.sort()
423 dirs.sort()
420 if '.hg' in dirs:
424 if '.hg' in dirs:
421 yield root # found a repository
425 yield root # found a repository
422 qroot = os.path.join(root, '.hg', 'patches')
426 qroot = os.path.join(root, '.hg', 'patches')
423 if os.path.isdir(os.path.join(qroot, '.hg')):
427 if os.path.isdir(os.path.join(qroot, '.hg')):
424 yield qroot # we have a patch queue repo here
428 yield qroot # we have a patch queue repo here
425 if recurse:
429 if recurse:
426 # avoid recursing inside the .hg directory
430 # avoid recursing inside the .hg directory
427 dirs.remove('.hg')
431 dirs.remove('.hg')
428 else:
432 else:
429 dirs[:] = [] # don't descend further
433 dirs[:] = [] # don't descend further
430 elif followsym:
434 elif followsym:
431 newdirs = []
435 newdirs = []
432 for d in dirs:
436 for d in dirs:
433 fname = os.path.join(root, d)
437 fname = os.path.join(root, d)
434 if adddir(seen_dirs, fname):
438 if adddir(seen_dirs, fname):
435 if os.path.islink(fname):
439 if os.path.islink(fname):
436 for hgname in walkrepos(fname, True, seen_dirs):
440 for hgname in walkrepos(fname, True, seen_dirs):
437 yield hgname
441 yield hgname
438 else:
442 else:
439 newdirs.append(d)
443 newdirs.append(d)
440 dirs[:] = newdirs
444 dirs[:] = newdirs
441
445
442 def osrcpath():
446 def osrcpath():
443 '''return default os-specific hgrc search path'''
447 '''return default os-specific hgrc search path'''
444 path = systemrcpath()
448 path = systemrcpath()
445 path.extend(userrcpath())
449 path.extend(userrcpath())
446 path = [os.path.normpath(f) for f in path]
450 path = [os.path.normpath(f) for f in path]
447 return path
451 return path
448
452
449 _rcpath = None
453 _rcpath = None
450
454
451 def rcpath():
455 def rcpath():
452 '''return hgrc search path. if env var HGRCPATH is set, use it.
456 '''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,
457 for each item in path, if directory, use files ending in .rc,
454 else use item.
458 else use item.
455 make HGRCPATH empty to only look in .hg/hgrc of current repo.
459 make HGRCPATH empty to only look in .hg/hgrc of current repo.
456 if no HGRCPATH, use default os-specific path.'''
460 if no HGRCPATH, use default os-specific path.'''
457 global _rcpath
461 global _rcpath
458 if _rcpath is None:
462 if _rcpath is None:
459 if 'HGRCPATH' in os.environ:
463 if 'HGRCPATH' in os.environ:
460 _rcpath = []
464 _rcpath = []
461 for p in os.environ['HGRCPATH'].split(os.pathsep):
465 for p in os.environ['HGRCPATH'].split(os.pathsep):
462 if not p:
466 if not p:
463 continue
467 continue
464 p = util.expandpath(p)
468 p = util.expandpath(p)
465 if os.path.isdir(p):
469 if os.path.isdir(p):
466 for f, kind in osutil.listdir(p):
470 for f, kind in osutil.listdir(p):
467 if f.endswith('.rc'):
471 if f.endswith('.rc'):
468 _rcpath.append(os.path.join(p, f))
472 _rcpath.append(os.path.join(p, f))
469 else:
473 else:
470 _rcpath.append(p)
474 _rcpath.append(p)
471 else:
475 else:
472 _rcpath = osrcpath()
476 _rcpath = osrcpath()
473 return _rcpath
477 return _rcpath
474
478
475 if os.name != 'nt':
479 if os.name != 'nt':
476
480
477 def rcfiles(path):
481 def rcfiles(path):
478 rcs = [os.path.join(path, 'hgrc')]
482 rcs = [os.path.join(path, 'hgrc')]
479 rcdir = os.path.join(path, 'hgrc.d')
483 rcdir = os.path.join(path, 'hgrc.d')
480 try:
484 try:
481 rcs.extend([os.path.join(rcdir, f)
485 rcs.extend([os.path.join(rcdir, f)
482 for f, kind in osutil.listdir(rcdir)
486 for f, kind in osutil.listdir(rcdir)
483 if f.endswith(".rc")])
487 if f.endswith(".rc")])
484 except OSError:
488 except OSError:
485 pass
489 pass
486 return rcs
490 return rcs
487
491
488 def systemrcpath():
492 def systemrcpath():
489 path = []
493 path = []
490 if sys.platform == 'plan9':
494 if sys.platform == 'plan9':
491 root = 'lib/mercurial'
495 root = 'lib/mercurial'
492 else:
496 else:
493 root = 'etc/mercurial'
497 root = 'etc/mercurial'
494 # old mod_python does not set sys.argv
498 # old mod_python does not set sys.argv
495 if len(getattr(sys, 'argv', [])) > 0:
499 if len(getattr(sys, 'argv', [])) > 0:
496 p = os.path.dirname(os.path.dirname(sys.argv[0]))
500 p = os.path.dirname(os.path.dirname(sys.argv[0]))
497 path.extend(rcfiles(os.path.join(p, root)))
501 path.extend(rcfiles(os.path.join(p, root)))
498 path.extend(rcfiles('/' + root))
502 path.extend(rcfiles('/' + root))
499 return path
503 return path
500
504
501 def userrcpath():
505 def userrcpath():
502 if sys.platform == 'plan9':
506 if sys.platform == 'plan9':
503 return [os.environ['home'] + '/lib/hgrc']
507 return [os.environ['home'] + '/lib/hgrc']
504 else:
508 else:
505 return [os.path.expanduser('~/.hgrc')]
509 return [os.path.expanduser('~/.hgrc')]
506
510
507 else:
511 else:
508
512
509 import _winreg
513 import _winreg
510
514
511 def systemrcpath():
515 def systemrcpath():
512 '''return default os-specific hgrc search path'''
516 '''return default os-specific hgrc search path'''
513 rcpath = []
517 rcpath = []
514 filename = util.executablepath()
518 filename = util.executablepath()
515 # Use mercurial.ini found in directory with hg.exe
519 # Use mercurial.ini found in directory with hg.exe
516 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
520 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
517 if os.path.isfile(progrc):
521 if os.path.isfile(progrc):
518 rcpath.append(progrc)
522 rcpath.append(progrc)
519 return rcpath
523 return rcpath
520 # Use hgrc.d found in directory with hg.exe
524 # Use hgrc.d found in directory with hg.exe
521 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
525 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
522 if os.path.isdir(progrcd):
526 if os.path.isdir(progrcd):
523 for f, kind in osutil.listdir(progrcd):
527 for f, kind in osutil.listdir(progrcd):
524 if f.endswith('.rc'):
528 if f.endswith('.rc'):
525 rcpath.append(os.path.join(progrcd, f))
529 rcpath.append(os.path.join(progrcd, f))
526 return rcpath
530 return rcpath
527 # else look for a system rcpath in the registry
531 # else look for a system rcpath in the registry
528 value = util.lookupreg('SOFTWARE\\Mercurial', None,
532 value = util.lookupreg('SOFTWARE\\Mercurial', None,
529 _winreg.HKEY_LOCAL_MACHINE)
533 _winreg.HKEY_LOCAL_MACHINE)
530 if not isinstance(value, str) or not value:
534 if not isinstance(value, str) or not value:
531 return rcpath
535 return rcpath
532 value = util.localpath(value)
536 value = util.localpath(value)
533 for p in value.split(os.pathsep):
537 for p in value.split(os.pathsep):
534 if p.lower().endswith('mercurial.ini'):
538 if p.lower().endswith('mercurial.ini'):
535 rcpath.append(p)
539 rcpath.append(p)
536 elif os.path.isdir(p):
540 elif os.path.isdir(p):
537 for f, kind in osutil.listdir(p):
541 for f, kind in osutil.listdir(p):
538 if f.endswith('.rc'):
542 if f.endswith('.rc'):
539 rcpath.append(os.path.join(p, f))
543 rcpath.append(os.path.join(p, f))
540 return rcpath
544 return rcpath
541
545
542 def userrcpath():
546 def userrcpath():
543 '''return os-specific hgrc search path to the user dir'''
547 '''return os-specific hgrc search path to the user dir'''
544 home = os.path.expanduser('~')
548 home = os.path.expanduser('~')
545 path = [os.path.join(home, 'mercurial.ini'),
549 path = [os.path.join(home, 'mercurial.ini'),
546 os.path.join(home, '.hgrc')]
550 os.path.join(home, '.hgrc')]
547 userprofile = os.environ.get('USERPROFILE')
551 userprofile = os.environ.get('USERPROFILE')
548 if userprofile:
552 if userprofile:
549 path.append(os.path.join(userprofile, 'mercurial.ini'))
553 path.append(os.path.join(userprofile, 'mercurial.ini'))
550 path.append(os.path.join(userprofile, '.hgrc'))
554 path.append(os.path.join(userprofile, '.hgrc'))
551 return path
555 return path
552
556
553 def revsingle(repo, revspec, default='.'):
557 def revsingle(repo, revspec, default='.'):
554 if not revspec:
558 if not revspec:
555 return repo[default]
559 return repo[default]
556
560
557 l = revrange(repo, [revspec])
561 l = revrange(repo, [revspec])
558 if len(l) < 1:
562 if len(l) < 1:
559 raise util.Abort(_('empty revision set'))
563 raise util.Abort(_('empty revision set'))
560 return repo[l[-1]]
564 return repo[l[-1]]
561
565
562 def revpair(repo, revs):
566 def revpair(repo, revs):
563 if not revs:
567 if not revs:
564 return repo.dirstate.p1(), None
568 return repo.dirstate.p1(), None
565
569
566 l = revrange(repo, revs)
570 l = revrange(repo, revs)
567
571
568 if len(l) == 0:
572 if len(l) == 0:
569 if revs:
573 if revs:
570 raise util.Abort(_('empty revision range'))
574 raise util.Abort(_('empty revision range'))
571 return repo.dirstate.p1(), None
575 return repo.dirstate.p1(), None
572
576
573 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
577 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
574 return repo.lookup(l[0]), None
578 return repo.lookup(l[0]), None
575
579
576 return repo.lookup(l[0]), repo.lookup(l[-1])
580 return repo.lookup(l[0]), repo.lookup(l[-1])
577
581
578 _revrangesep = ':'
582 _revrangesep = ':'
579
583
580 def revrange(repo, revs):
584 def revrange(repo, revs):
581 """Yield revision as strings from a list of revision specifications."""
585 """Yield revision as strings from a list of revision specifications."""
582
586
583 def revfix(repo, val, defval):
587 def revfix(repo, val, defval):
584 if not val and val != 0 and defval is not None:
588 if not val and val != 0 and defval is not None:
585 return defval
589 return defval
586 return repo[val].rev()
590 return repo[val].rev()
587
591
588 seen, l = set(), []
592 seen, l = set(), []
589 for spec in revs:
593 for spec in revs:
590 if l and not seen:
594 if l and not seen:
591 seen = set(l)
595 seen = set(l)
592 # attempt to parse old-style ranges first to deal with
596 # attempt to parse old-style ranges first to deal with
593 # things like old-tag which contain query metacharacters
597 # things like old-tag which contain query metacharacters
594 try:
598 try:
595 if isinstance(spec, int):
599 if isinstance(spec, int):
596 seen.add(spec)
600 seen.add(spec)
597 l.append(spec)
601 l.append(spec)
598 continue
602 continue
599
603
600 if _revrangesep in spec:
604 if _revrangesep in spec:
601 start, end = spec.split(_revrangesep, 1)
605 start, end = spec.split(_revrangesep, 1)
602 start = revfix(repo, start, 0)
606 start = revfix(repo, start, 0)
603 end = revfix(repo, end, len(repo) - 1)
607 end = revfix(repo, end, len(repo) - 1)
604 step = start > end and -1 or 1
608 step = start > end and -1 or 1
605 if not seen and not l:
609 if not seen and not l:
606 # by far the most common case: revs = ["-1:0"]
610 # by far the most common case: revs = ["-1:0"]
607 l = range(start, end + step, step)
611 l = range(start, end + step, step)
608 # defer syncing seen until next iteration
612 # defer syncing seen until next iteration
609 continue
613 continue
610 newrevs = set(xrange(start, end + step, step))
614 newrevs = set(xrange(start, end + step, step))
611 if seen:
615 if seen:
612 newrevs.difference_update(seen)
616 newrevs.difference_update(seen)
613 seen.update(newrevs)
617 seen.update(newrevs)
614 else:
618 else:
615 seen = newrevs
619 seen = newrevs
616 l.extend(sorted(newrevs, reverse=start > end))
620 l.extend(sorted(newrevs, reverse=start > end))
617 continue
621 continue
618 elif spec and spec in repo: # single unquoted rev
622 elif spec and spec in repo: # single unquoted rev
619 rev = revfix(repo, spec, None)
623 rev = revfix(repo, spec, None)
620 if rev in seen:
624 if rev in seen:
621 continue
625 continue
622 seen.add(rev)
626 seen.add(rev)
623 l.append(rev)
627 l.append(rev)
624 continue
628 continue
625 except error.RepoLookupError:
629 except error.RepoLookupError:
626 pass
630 pass
627
631
628 # fall through to new-style queries if old-style fails
632 # fall through to new-style queries if old-style fails
629 m = revset.match(repo.ui, spec)
633 m = revset.match(repo.ui, spec)
630 dl = [r for r in m(repo, xrange(len(repo))) if r not in seen]
634 dl = [r for r in m(repo, xrange(len(repo))) if r not in seen]
631 l.extend(dl)
635 l.extend(dl)
632 seen.update(dl)
636 seen.update(dl)
633
637
634 return l
638 return l
635
639
636 def expandpats(pats):
640 def expandpats(pats):
637 if not util.expandglobs:
641 if not util.expandglobs:
638 return list(pats)
642 return list(pats)
639 ret = []
643 ret = []
640 for p in pats:
644 for p in pats:
641 kind, name = matchmod._patsplit(p, None)
645 kind, name = matchmod._patsplit(p, None)
642 if kind is None:
646 if kind is None:
643 try:
647 try:
644 globbed = glob.glob(name)
648 globbed = glob.glob(name)
645 except re.error:
649 except re.error:
646 globbed = [name]
650 globbed = [name]
647 if globbed:
651 if globbed:
648 ret.extend(globbed)
652 ret.extend(globbed)
649 continue
653 continue
650 ret.append(p)
654 ret.append(p)
651 return ret
655 return ret
652
656
653 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
657 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
654 if pats == ("",):
658 if pats == ("",):
655 pats = []
659 pats = []
656 if not globbed and default == 'relpath':
660 if not globbed and default == 'relpath':
657 pats = expandpats(pats or [])
661 pats = expandpats(pats or [])
658
662
659 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
663 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
660 default)
664 default)
661 def badfn(f, msg):
665 def badfn(f, msg):
662 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
666 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
663 m.bad = badfn
667 m.bad = badfn
664 return m, pats
668 return m, pats
665
669
666 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
670 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
667 return matchandpats(ctx, pats, opts, globbed, default)[0]
671 return matchandpats(ctx, pats, opts, globbed, default)[0]
668
672
669 def matchall(repo):
673 def matchall(repo):
670 return matchmod.always(repo.root, repo.getcwd())
674 return matchmod.always(repo.root, repo.getcwd())
671
675
672 def matchfiles(repo, files):
676 def matchfiles(repo, files):
673 return matchmod.exact(repo.root, repo.getcwd(), files)
677 return matchmod.exact(repo.root, repo.getcwd(), files)
674
678
675 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
679 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
676 if dry_run is None:
680 if dry_run is None:
677 dry_run = opts.get('dry_run')
681 dry_run = opts.get('dry_run')
678 if similarity is None:
682 if similarity is None:
679 similarity = float(opts.get('similarity') or 0)
683 similarity = float(opts.get('similarity') or 0)
680 # we'd use status here, except handling of symlinks and ignore is tricky
684 # we'd use status here, except handling of symlinks and ignore is tricky
681 added, unknown, deleted, removed = [], [], [], []
685 added, unknown, deleted, removed = [], [], [], []
682 audit_path = pathauditor(repo.root)
686 audit_path = pathauditor(repo.root)
683 m = match(repo[None], pats, opts)
687 m = match(repo[None], pats, opts)
684 rejected = []
688 rejected = []
685 m.bad = lambda x, y: rejected.append(x)
689 m.bad = lambda x, y: rejected.append(x)
686
690
687 for abs in repo.walk(m):
691 for abs in repo.walk(m):
688 target = repo.wjoin(abs)
692 target = repo.wjoin(abs)
689 good = True
693 good = True
690 try:
694 try:
691 audit_path(abs)
695 audit_path(abs)
692 except (OSError, util.Abort):
696 except (OSError, util.Abort):
693 good = False
697 good = False
694 rel = m.rel(abs)
698 rel = m.rel(abs)
695 exact = m.exact(abs)
699 exact = m.exact(abs)
696 if good and abs not in repo.dirstate:
700 if good and abs not in repo.dirstate:
697 unknown.append(abs)
701 unknown.append(abs)
698 if repo.ui.verbose or not exact:
702 if repo.ui.verbose or not exact:
699 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
703 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
700 elif (repo.dirstate[abs] != 'r' and
704 elif (repo.dirstate[abs] != 'r' and
701 (not good or not os.path.lexists(target) or
705 (not good or not os.path.lexists(target) or
702 (os.path.isdir(target) and not os.path.islink(target)))):
706 (os.path.isdir(target) and not os.path.islink(target)))):
703 deleted.append(abs)
707 deleted.append(abs)
704 if repo.ui.verbose or not exact:
708 if repo.ui.verbose or not exact:
705 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
709 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
706 # for finding renames
710 # for finding renames
707 elif repo.dirstate[abs] == 'r':
711 elif repo.dirstate[abs] == 'r':
708 removed.append(abs)
712 removed.append(abs)
709 elif repo.dirstate[abs] == 'a':
713 elif repo.dirstate[abs] == 'a':
710 added.append(abs)
714 added.append(abs)
711 copies = {}
715 copies = {}
712 if similarity > 0:
716 if similarity > 0:
713 for old, new, score in similar.findrenames(repo,
717 for old, new, score in similar.findrenames(repo,
714 added + unknown, removed + deleted, similarity):
718 added + unknown, removed + deleted, similarity):
715 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
719 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 '
720 repo.ui.status(_('recording removal of %s as rename to %s '
717 '(%d%% similar)\n') %
721 '(%d%% similar)\n') %
718 (m.rel(old), m.rel(new), score * 100))
722 (m.rel(old), m.rel(new), score * 100))
719 copies[new] = old
723 copies[new] = old
720
724
721 if not dry_run:
725 if not dry_run:
722 wctx = repo[None]
726 wctx = repo[None]
723 wlock = repo.wlock()
727 wlock = repo.wlock()
724 try:
728 try:
725 wctx.forget(deleted)
729 wctx.forget(deleted)
726 wctx.add(unknown)
730 wctx.add(unknown)
727 for new, old in copies.iteritems():
731 for new, old in copies.iteritems():
728 wctx.copy(old, new)
732 wctx.copy(old, new)
729 finally:
733 finally:
730 wlock.release()
734 wlock.release()
731
735
732 for f in rejected:
736 for f in rejected:
733 if f in m.files():
737 if f in m.files():
734 return 1
738 return 1
735 return 0
739 return 0
736
740
737 def updatedir(ui, repo, patches, similarity=0):
741 def updatedir(ui, repo, patches, similarity=0):
738 '''Update dirstate after patch application according to metadata'''
742 '''Update dirstate after patch application according to metadata'''
739 if not patches:
743 if not patches:
740 return []
744 return []
741 copies = []
745 copies = []
742 removes = set()
746 removes = set()
743 cfiles = patches.keys()
747 cfiles = patches.keys()
744 cwd = repo.getcwd()
748 cwd = repo.getcwd()
745 if cwd:
749 if cwd:
746 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
750 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
747 for f in patches:
751 for f in patches:
748 gp = patches[f]
752 gp = patches[f]
749 if not gp:
753 if not gp:
750 continue
754 continue
751 if gp.op == 'RENAME':
755 if gp.op == 'RENAME':
752 copies.append((gp.oldpath, gp.path))
756 copies.append((gp.oldpath, gp.path))
753 removes.add(gp.oldpath)
757 removes.add(gp.oldpath)
754 elif gp.op == 'COPY':
758 elif gp.op == 'COPY':
755 copies.append((gp.oldpath, gp.path))
759 copies.append((gp.oldpath, gp.path))
756 elif gp.op == 'DELETE':
760 elif gp.op == 'DELETE':
757 removes.add(gp.path)
761 removes.add(gp.path)
758
762
759 wctx = repo[None]
763 wctx = repo[None]
760 for src, dst in copies:
764 for src, dst in copies:
761 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
765 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
762 if (not similarity) and removes:
766 if (not similarity) and removes:
763 wctx.remove(sorted(removes), True)
767 wctx.remove(sorted(removes), True)
764
768
765 for f in patches:
769 for f in patches:
766 gp = patches[f]
770 gp = patches[f]
767 if gp and gp.mode:
771 if gp and gp.mode:
768 islink, isexec = gp.mode
772 islink, isexec = gp.mode
769 dst = repo.wjoin(gp.path)
773 dst = repo.wjoin(gp.path)
770 # patch won't create empty files
774 # patch won't create empty files
771 if gp.op == 'ADD' and not os.path.lexists(dst):
775 if gp.op == 'ADD' and not os.path.lexists(dst):
772 flags = (isexec and 'x' or '') + (islink and 'l' or '')
776 flags = (isexec and 'x' or '') + (islink and 'l' or '')
773 repo.wwrite(gp.path, '', flags)
777 repo.wwrite(gp.path, '', flags)
774 util.setflags(dst, islink, isexec)
778 util.setflags(dst, islink, isexec)
775 addremove(repo, cfiles, similarity=similarity)
779 addremove(repo, cfiles, similarity=similarity)
776 files = patches.keys()
780 files = patches.keys()
777 files.extend([r for r in removes if r not in files])
781 files.extend([r for r in removes if r not in files])
778 return sorted(files)
782 return sorted(files)
779
783
780 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
784 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
785 """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.
786 different reasons it might not end with dst being marked as copied from src.
783 """
787 """
784 origsrc = repo.dirstate.copied(src) or src
788 origsrc = repo.dirstate.copied(src) or src
785 if dst == origsrc: # copying back a copy?
789 if dst == origsrc: # copying back a copy?
786 if repo.dirstate[dst] not in 'mn' and not dryrun:
790 if repo.dirstate[dst] not in 'mn' and not dryrun:
787 repo.dirstate.normallookup(dst)
791 repo.dirstate.normallookup(dst)
788 else:
792 else:
789 if repo.dirstate[origsrc] == 'a' and origsrc == src:
793 if repo.dirstate[origsrc] == 'a' and origsrc == src:
790 if not ui.quiet:
794 if not ui.quiet:
791 ui.warn(_("%s has not been committed yet, so no copy "
795 ui.warn(_("%s has not been committed yet, so no copy "
792 "data will be stored for %s.\n")
796 "data will be stored for %s.\n")
793 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
797 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
794 if repo.dirstate[dst] in '?r' and not dryrun:
798 if repo.dirstate[dst] in '?r' and not dryrun:
795 wctx.add([dst])
799 wctx.add([dst])
796 elif not dryrun:
800 elif not dryrun:
797 wctx.copy(origsrc, dst)
801 wctx.copy(origsrc, dst)
798
802
799 def readrequires(opener, supported):
803 def readrequires(opener, supported):
800 '''Reads and parses .hg/requires and checks if all entries found
804 '''Reads and parses .hg/requires and checks if all entries found
801 are in the list of supported features.'''
805 are in the list of supported features.'''
802 requirements = set(opener.read("requires").splitlines())
806 requirements = set(opener.read("requires").splitlines())
803 missings = []
807 missings = []
804 for r in requirements:
808 for r in requirements:
805 if r not in supported:
809 if r not in supported:
806 if not r or not r[0].isalnum():
810 if not r or not r[0].isalnum():
807 raise error.RequirementError(_(".hg/requires file is corrupt"))
811 raise error.RequirementError(_(".hg/requires file is corrupt"))
808 missings.append(r)
812 missings.append(r)
809 missings.sort()
813 missings.sort()
810 if missings:
814 if missings:
811 raise error.RequirementError(
815 raise error.RequirementError(
812 _("unknown repository format: requires features '%s' (upgrade "
816 _("unknown repository format: requires features '%s' (upgrade "
813 "Mercurial)") % "', '".join(missings))
817 "Mercurial)") % "', '".join(missings))
814 return requirements
818 return requirements
815
819
816 class filecacheentry(object):
820 class filecacheentry(object):
817 def __init__(self, path):
821 def __init__(self, path):
818 self.path = path
822 self.path = path
819 self.cachestat = filecacheentry.stat(self.path)
823 self.cachestat = filecacheentry.stat(self.path)
820
824
821 if self.cachestat:
825 if self.cachestat:
822 self._cacheable = self.cachestat.cacheable()
826 self._cacheable = self.cachestat.cacheable()
823 else:
827 else:
824 # None means we don't know yet
828 # None means we don't know yet
825 self._cacheable = None
829 self._cacheable = None
826
830
827 def refresh(self):
831 def refresh(self):
828 if self.cacheable():
832 if self.cacheable():
829 self.cachestat = filecacheentry.stat(self.path)
833 self.cachestat = filecacheentry.stat(self.path)
830
834
831 def cacheable(self):
835 def cacheable(self):
832 if self._cacheable is not None:
836 if self._cacheable is not None:
833 return self._cacheable
837 return self._cacheable
834
838
835 # we don't know yet, assume it is for now
839 # we don't know yet, assume it is for now
836 return True
840 return True
837
841
838 def changed(self):
842 def changed(self):
839 # no point in going further if we can't cache it
843 # no point in going further if we can't cache it
840 if not self.cacheable():
844 if not self.cacheable():
841 return True
845 return True
842
846
843 newstat = filecacheentry.stat(self.path)
847 newstat = filecacheentry.stat(self.path)
844
848
845 # we may not know if it's cacheable yet, check again now
849 # we may not know if it's cacheable yet, check again now
846 if newstat and self._cacheable is None:
850 if newstat and self._cacheable is None:
847 self._cacheable = newstat.cacheable()
851 self._cacheable = newstat.cacheable()
848
852
849 # check again
853 # check again
850 if not self._cacheable:
854 if not self._cacheable:
851 return True
855 return True
852
856
853 if self.cachestat != newstat:
857 if self.cachestat != newstat:
854 self.cachestat = newstat
858 self.cachestat = newstat
855 return True
859 return True
856 else:
860 else:
857 return False
861 return False
858
862
859 @staticmethod
863 @staticmethod
860 def stat(path):
864 def stat(path):
861 try:
865 try:
862 return util.cachestat(path)
866 return util.cachestat(path)
863 except OSError, e:
867 except OSError, e:
864 if e.errno != errno.ENOENT:
868 if e.errno != errno.ENOENT:
865 raise
869 raise
866
870
867 class filecache(object):
871 class filecache(object):
868 '''A property like decorator that tracks a file under .hg/ for updates.
872 '''A property like decorator that tracks a file under .hg/ for updates.
869
873
870 Records stat info when called in _filecache.
874 Records stat info when called in _filecache.
871
875
872 On subsequent calls, compares old stat info with new info, and recreates
876 On subsequent calls, compares old stat info with new info, and recreates
873 the object when needed, updating the new stat info in _filecache.
877 the object when needed, updating the new stat info in _filecache.
874
878
875 Mercurial either atomic renames or appends for files under .hg,
879 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
880 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
881 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
882 recreating the object on every call (essentially the same behaviour as
879 propertycache).'''
883 propertycache).'''
880 def __init__(self, path):
884 def __init__(self, path):
881 self.path = path
885 self.path = path
882
886
883 def join(self, obj, fname):
887 def join(self, obj, fname):
884 """Used to compute the runtime path of the cached file.
888 """Used to compute the runtime path of the cached file.
885
889
886 Users should subclass filecache and provide their own version of this
890 Users should subclass filecache and provide their own version of this
887 function to call the appropriate join function on 'obj' (an instance
891 function to call the appropriate join function on 'obj' (an instance
888 of the class that its member function was decorated).
892 of the class that its member function was decorated).
889 """
893 """
890 return obj.join(fname)
894 return obj.join(fname)
891
895
892 def __call__(self, func):
896 def __call__(self, func):
893 self.func = func
897 self.func = func
894 self.name = func.__name__
898 self.name = func.__name__
895 return self
899 return self
896
900
897 def __get__(self, obj, type=None):
901 def __get__(self, obj, type=None):
898 # do we need to check if the file changed?
902 # do we need to check if the file changed?
899 if self.name in obj.__dict__:
903 if self.name in obj.__dict__:
900 return obj.__dict__[self.name]
904 return obj.__dict__[self.name]
901
905
902 entry = obj._filecache.get(self.name)
906 entry = obj._filecache.get(self.name)
903
907
904 if entry:
908 if entry:
905 if entry.changed():
909 if entry.changed():
906 entry.obj = self.func(obj)
910 entry.obj = self.func(obj)
907 else:
911 else:
908 path = self.join(obj, self.path)
912 path = self.join(obj, self.path)
909
913
910 # We stat -before- creating the object so our cache doesn't lie if
914 # We stat -before- creating the object so our cache doesn't lie if
911 # a writer modified between the time we read and stat
915 # a writer modified between the time we read and stat
912 entry = filecacheentry(path)
916 entry = filecacheentry(path)
913 entry.obj = self.func(obj)
917 entry.obj = self.func(obj)
914
918
915 obj._filecache[self.name] = entry
919 obj._filecache[self.name] = entry
916
920
917 obj.__dict__[self.name] = entry.obj
921 obj.__dict__[self.name] = entry.obj
918 return entry.obj
922 return entry.obj
919
923
920 def __set__(self, obj, value):
924 def __set__(self, obj, value):
921 if self.name in obj._filecache:
925 if self.name in obj._filecache:
922 obj._filecache[self.name].obj = value # update cached copy
926 obj._filecache[self.name].obj = value # update cached copy
923 obj.__dict__[self.name] = value # update copy returned by obj.x
927 obj.__dict__[self.name] = value # update copy returned by obj.x
924
928
925 def __delete__(self, obj):
929 def __delete__(self, obj):
926 try:
930 try:
927 del obj.__dict__[self.name]
931 del obj.__dict__[self.name]
928 except KeyError:
932 except KeyError:
929 raise AttributeError, self.name
933 raise AttributeError, self.name
@@ -1,154 +1,154 b''
1 # statichttprepo.py - simple http repository class for mercurial
1 # statichttprepo.py - simple http repository class for mercurial
2 #
2 #
3 # This provides read-only repo access to repositories exported via static http
3 # This provides read-only repo access to repositories exported via static http
4 #
4 #
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from i18n import _
10 from i18n import _
11 import changelog, byterange, url, error
11 import changelog, byterange, url, error
12 import localrepo, manifest, util, scmutil, store
12 import localrepo, manifest, util, scmutil, store
13 import urllib, urllib2, errno
13 import urllib, urllib2, errno
14
14
15 class httprangereader(object):
15 class httprangereader(object):
16 def __init__(self, url, opener):
16 def __init__(self, url, opener):
17 # we assume opener has HTTPRangeHandler
17 # we assume opener has HTTPRangeHandler
18 self.url = url
18 self.url = url
19 self.pos = 0
19 self.pos = 0
20 self.opener = opener
20 self.opener = opener
21 self.name = url
21 self.name = url
22 def seek(self, pos):
22 def seek(self, pos):
23 self.pos = pos
23 self.pos = pos
24 def read(self, bytes=None):
24 def read(self, bytes=None):
25 req = urllib2.Request(self.url)
25 req = urllib2.Request(self.url)
26 end = ''
26 end = ''
27 if bytes:
27 if bytes:
28 end = self.pos + bytes - 1
28 end = self.pos + bytes - 1
29 if self.pos or end:
29 if self.pos or end:
30 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
30 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
31
31
32 try:
32 try:
33 f = self.opener.open(req)
33 f = self.opener.open(req)
34 data = f.read()
34 data = f.read()
35 # Python 2.6+ defines a getcode() function, and 2.4 and
35 # Python 2.6+ defines a getcode() function, and 2.4 and
36 # 2.5 appear to always have an undocumented code attribute
36 # 2.5 appear to always have an undocumented code attribute
37 # set. If we can't read either of those, fall back to 206
37 # set. If we can't read either of those, fall back to 206
38 # and hope for the best.
38 # and hope for the best.
39 code = getattr(f, 'getcode', lambda : getattr(f, 'code', 206))()
39 code = getattr(f, 'getcode', lambda : getattr(f, 'code', 206))()
40 except urllib2.HTTPError, inst:
40 except urllib2.HTTPError, inst:
41 num = inst.code == 404 and errno.ENOENT or None
41 num = inst.code == 404 and errno.ENOENT or None
42 raise IOError(num, inst)
42 raise IOError(num, inst)
43 except urllib2.URLError, inst:
43 except urllib2.URLError, inst:
44 raise IOError(None, inst.reason[1])
44 raise IOError(None, inst.reason[1])
45
45
46 if code == 200:
46 if code == 200:
47 # HTTPRangeHandler does nothing if remote does not support
47 # HTTPRangeHandler does nothing if remote does not support
48 # Range headers and returns the full entity. Let's slice it.
48 # Range headers and returns the full entity. Let's slice it.
49 if bytes:
49 if bytes:
50 data = data[self.pos:self.pos + bytes]
50 data = data[self.pos:self.pos + bytes]
51 else:
51 else:
52 data = data[self.pos:]
52 data = data[self.pos:]
53 elif bytes:
53 elif bytes:
54 data = data[:bytes]
54 data = data[:bytes]
55 self.pos += len(data)
55 self.pos += len(data)
56 return data
56 return data
57 def __iter__(self):
57 def __iter__(self):
58 return iter(self.read().splitlines(1))
58 return iter(self.read().splitlines(1))
59 def close(self):
59 def close(self):
60 pass
60 pass
61
61
62 def build_opener(ui, authinfo):
62 def build_opener(ui, authinfo):
63 # urllib cannot handle URLs with embedded user or passwd
63 # urllib cannot handle URLs with embedded user or passwd
64 urlopener = url.opener(ui, authinfo)
64 urlopener = url.opener(ui, authinfo)
65 urlopener.add_handler(byterange.HTTPRangeHandler())
65 urlopener.add_handler(byterange.HTTPRangeHandler())
66
66
67 class statichttpopener(scmutil.abstractopener):
67 class statichttpvfs(scmutil.abstractvfs):
68 def __init__(self, base):
68 def __init__(self, base):
69 self.base = base
69 self.base = base
70
70
71 def __call__(self, path, mode="r", atomictemp=None):
71 def __call__(self, path, mode="r", atomictemp=None):
72 if mode not in ('r', 'rb'):
72 if mode not in ('r', 'rb'):
73 raise IOError('Permission denied')
73 raise IOError('Permission denied')
74 f = "/".join((self.base, urllib.quote(path)))
74 f = "/".join((self.base, urllib.quote(path)))
75 return httprangereader(f, urlopener)
75 return httprangereader(f, urlopener)
76
76
77 return statichttpopener
77 return statichttpvfs
78
78
79 class statichttppeer(localrepo.localpeer):
79 class statichttppeer(localrepo.localpeer):
80 def local(self):
80 def local(self):
81 return None
81 return None
82 def canpush(self):
82 def canpush(self):
83 return False
83 return False
84
84
85 class statichttprepository(localrepo.localrepository):
85 class statichttprepository(localrepo.localrepository):
86 def __init__(self, ui, path):
86 def __init__(self, ui, path):
87 self._url = path
87 self._url = path
88 self.ui = ui
88 self.ui = ui
89
89
90 self.root = path
90 self.root = path
91 u = util.url(path.rstrip('/') + "/.hg")
91 u = util.url(path.rstrip('/') + "/.hg")
92 self.path, authinfo = u.authinfo()
92 self.path, authinfo = u.authinfo()
93
93
94 opener = build_opener(ui, authinfo)
94 opener = build_opener(ui, authinfo)
95 self.opener = opener(self.path)
95 self.opener = opener(self.path)
96 self.vfs = self.opener
96 self.vfs = self.opener
97 self._phasedefaults = []
97 self._phasedefaults = []
98
98
99 try:
99 try:
100 requirements = scmutil.readrequires(self.opener, self.supported)
100 requirements = scmutil.readrequires(self.opener, self.supported)
101 except IOError, inst:
101 except IOError, inst:
102 if inst.errno != errno.ENOENT:
102 if inst.errno != errno.ENOENT:
103 raise
103 raise
104 requirements = set()
104 requirements = set()
105
105
106 # check if it is a non-empty old-style repository
106 # check if it is a non-empty old-style repository
107 try:
107 try:
108 fp = self.opener("00changelog.i")
108 fp = self.opener("00changelog.i")
109 fp.read(1)
109 fp.read(1)
110 fp.close()
110 fp.close()
111 except IOError, inst:
111 except IOError, inst:
112 if inst.errno != errno.ENOENT:
112 if inst.errno != errno.ENOENT:
113 raise
113 raise
114 # we do not care about empty old-style repositories here
114 # we do not care about empty old-style repositories here
115 msg = _("'%s' does not appear to be an hg repository") % path
115 msg = _("'%s' does not appear to be an hg repository") % path
116 raise error.RepoError(msg)
116 raise error.RepoError(msg)
117
117
118 # setup store
118 # setup store
119 self.store = store.store(requirements, self.path, opener)
119 self.store = store.store(requirements, self.path, opener)
120 self.spath = self.store.path
120 self.spath = self.store.path
121 self.sopener = self.store.opener
121 self.sopener = self.store.opener
122 self.svfs = self.sopener
122 self.svfs = self.sopener
123 self.sjoin = self.store.join
123 self.sjoin = self.store.join
124 self._filecache = {}
124 self._filecache = {}
125 self.requirements = requirements
125 self.requirements = requirements
126
126
127 self.manifest = manifest.manifest(self.sopener)
127 self.manifest = manifest.manifest(self.sopener)
128 self.changelog = changelog.changelog(self.sopener)
128 self.changelog = changelog.changelog(self.sopener)
129 self._tags = None
129 self._tags = None
130 self.nodetagscache = None
130 self.nodetagscache = None
131 self._branchcache = None
131 self._branchcache = None
132 self._branchcachetip = None
132 self._branchcachetip = None
133 self.encodepats = None
133 self.encodepats = None
134 self.decodepats = None
134 self.decodepats = None
135
135
136 def _restrictcapabilities(self, caps):
136 def _restrictcapabilities(self, caps):
137 return caps.difference(["pushkey"])
137 return caps.difference(["pushkey"])
138
138
139 def url(self):
139 def url(self):
140 return self._url
140 return self._url
141
141
142 def local(self):
142 def local(self):
143 return False
143 return False
144
144
145 def peer(self):
145 def peer(self):
146 return statichttppeer(self)
146 return statichttppeer(self)
147
147
148 def lock(self, wait=True):
148 def lock(self, wait=True):
149 raise util.Abort(_('cannot lock static-http repository'))
149 raise util.Abort(_('cannot lock static-http repository'))
150
150
151 def instance(ui, path, create):
151 def instance(ui, path, create):
152 if create:
152 if create:
153 raise util.Abort(_('cannot create new static-http repository'))
153 raise util.Abort(_('cannot create new static-http repository'))
154 return statichttprepository(ui, path[7:])
154 return statichttprepository(ui, path[7:])
@@ -1,494 +1,494 b''
1 # store.py - repository store handling for Mercurial
1 # store.py - repository store handling for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 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 osutil, scmutil, util, parsers
9 import osutil, scmutil, util, parsers
10 import os, stat, errno
10 import os, stat, errno
11
11
12 _sha = util.sha1
12 _sha = util.sha1
13
13
14 # This avoids a collision between a file named foo and a dir named
14 # This avoids a collision between a file named foo and a dir named
15 # foo.i or foo.d
15 # foo.i or foo.d
16 def _encodedir(path):
16 def _encodedir(path):
17 '''
17 '''
18 >>> _encodedir('data/foo.i')
18 >>> _encodedir('data/foo.i')
19 'data/foo.i'
19 'data/foo.i'
20 >>> _encodedir('data/foo.i/bla.i')
20 >>> _encodedir('data/foo.i/bla.i')
21 'data/foo.i.hg/bla.i'
21 'data/foo.i.hg/bla.i'
22 >>> _encodedir('data/foo.i.hg/bla.i')
22 >>> _encodedir('data/foo.i.hg/bla.i')
23 'data/foo.i.hg.hg/bla.i'
23 'data/foo.i.hg.hg/bla.i'
24 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
24 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
25 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
25 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
26 '''
26 '''
27 return (path
27 return (path
28 .replace(".hg/", ".hg.hg/")
28 .replace(".hg/", ".hg.hg/")
29 .replace(".i/", ".i.hg/")
29 .replace(".i/", ".i.hg/")
30 .replace(".d/", ".d.hg/"))
30 .replace(".d/", ".d.hg/"))
31
31
32 encodedir = getattr(parsers, 'encodedir', _encodedir)
32 encodedir = getattr(parsers, 'encodedir', _encodedir)
33
33
34 def decodedir(path):
34 def decodedir(path):
35 '''
35 '''
36 >>> decodedir('data/foo.i')
36 >>> decodedir('data/foo.i')
37 'data/foo.i'
37 'data/foo.i'
38 >>> decodedir('data/foo.i.hg/bla.i')
38 >>> decodedir('data/foo.i.hg/bla.i')
39 'data/foo.i/bla.i'
39 'data/foo.i/bla.i'
40 >>> decodedir('data/foo.i.hg.hg/bla.i')
40 >>> decodedir('data/foo.i.hg.hg/bla.i')
41 'data/foo.i.hg/bla.i'
41 'data/foo.i.hg/bla.i'
42 '''
42 '''
43 if ".hg/" not in path:
43 if ".hg/" not in path:
44 return path
44 return path
45 return (path
45 return (path
46 .replace(".d.hg/", ".d/")
46 .replace(".d.hg/", ".d/")
47 .replace(".i.hg/", ".i/")
47 .replace(".i.hg/", ".i/")
48 .replace(".hg.hg/", ".hg/"))
48 .replace(".hg.hg/", ".hg/"))
49
49
50 def _buildencodefun():
50 def _buildencodefun():
51 '''
51 '''
52 >>> enc, dec = _buildencodefun()
52 >>> enc, dec = _buildencodefun()
53
53
54 >>> enc('nothing/special.txt')
54 >>> enc('nothing/special.txt')
55 'nothing/special.txt'
55 'nothing/special.txt'
56 >>> dec('nothing/special.txt')
56 >>> dec('nothing/special.txt')
57 'nothing/special.txt'
57 'nothing/special.txt'
58
58
59 >>> enc('HELLO')
59 >>> enc('HELLO')
60 '_h_e_l_l_o'
60 '_h_e_l_l_o'
61 >>> dec('_h_e_l_l_o')
61 >>> dec('_h_e_l_l_o')
62 'HELLO'
62 'HELLO'
63
63
64 >>> enc('hello:world?')
64 >>> enc('hello:world?')
65 'hello~3aworld~3f'
65 'hello~3aworld~3f'
66 >>> dec('hello~3aworld~3f')
66 >>> dec('hello~3aworld~3f')
67 'hello:world?'
67 'hello:world?'
68
68
69 >>> enc('the\x07quick\xADshot')
69 >>> enc('the\x07quick\xADshot')
70 'the~07quick~adshot'
70 'the~07quick~adshot'
71 >>> dec('the~07quick~adshot')
71 >>> dec('the~07quick~adshot')
72 'the\\x07quick\\xadshot'
72 'the\\x07quick\\xadshot'
73 '''
73 '''
74 e = '_'
74 e = '_'
75 winreserved = [ord(x) for x in '\\:*?"<>|']
75 winreserved = [ord(x) for x in '\\:*?"<>|']
76 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
76 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
77 for x in (range(32) + range(126, 256) + winreserved):
77 for x in (range(32) + range(126, 256) + winreserved):
78 cmap[chr(x)] = "~%02x" % x
78 cmap[chr(x)] = "~%02x" % x
79 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
79 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
80 cmap[chr(x)] = e + chr(x).lower()
80 cmap[chr(x)] = e + chr(x).lower()
81 dmap = {}
81 dmap = {}
82 for k, v in cmap.iteritems():
82 for k, v in cmap.iteritems():
83 dmap[v] = k
83 dmap[v] = k
84 def decode(s):
84 def decode(s):
85 i = 0
85 i = 0
86 while i < len(s):
86 while i < len(s):
87 for l in xrange(1, 4):
87 for l in xrange(1, 4):
88 try:
88 try:
89 yield dmap[s[i:i + l]]
89 yield dmap[s[i:i + l]]
90 i += l
90 i += l
91 break
91 break
92 except KeyError:
92 except KeyError:
93 pass
93 pass
94 else:
94 else:
95 raise KeyError
95 raise KeyError
96 return (lambda s: ''.join([cmap[c] for c in s]),
96 return (lambda s: ''.join([cmap[c] for c in s]),
97 lambda s: ''.join(list(decode(s))))
97 lambda s: ''.join(list(decode(s))))
98
98
99 _encodefname, _decodefname = _buildencodefun()
99 _encodefname, _decodefname = _buildencodefun()
100
100
101 def encodefilename(s):
101 def encodefilename(s):
102 '''
102 '''
103 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
103 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
104 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
104 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
105 '''
105 '''
106 return _encodefname(encodedir(s))
106 return _encodefname(encodedir(s))
107
107
108 def decodefilename(s):
108 def decodefilename(s):
109 '''
109 '''
110 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
110 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
111 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
111 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
112 '''
112 '''
113 return decodedir(_decodefname(s))
113 return decodedir(_decodefname(s))
114
114
115 def _buildlowerencodefun():
115 def _buildlowerencodefun():
116 '''
116 '''
117 >>> f = _buildlowerencodefun()
117 >>> f = _buildlowerencodefun()
118 >>> f('nothing/special.txt')
118 >>> f('nothing/special.txt')
119 'nothing/special.txt'
119 'nothing/special.txt'
120 >>> f('HELLO')
120 >>> f('HELLO')
121 'hello'
121 'hello'
122 >>> f('hello:world?')
122 >>> f('hello:world?')
123 'hello~3aworld~3f'
123 'hello~3aworld~3f'
124 >>> f('the\x07quick\xADshot')
124 >>> f('the\x07quick\xADshot')
125 'the~07quick~adshot'
125 'the~07quick~adshot'
126 '''
126 '''
127 winreserved = [ord(x) for x in '\\:*?"<>|']
127 winreserved = [ord(x) for x in '\\:*?"<>|']
128 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
128 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
129 for x in (range(32) + range(126, 256) + winreserved):
129 for x in (range(32) + range(126, 256) + winreserved):
130 cmap[chr(x)] = "~%02x" % x
130 cmap[chr(x)] = "~%02x" % x
131 for x in range(ord("A"), ord("Z")+1):
131 for x in range(ord("A"), ord("Z")+1):
132 cmap[chr(x)] = chr(x).lower()
132 cmap[chr(x)] = chr(x).lower()
133 return lambda s: "".join([cmap[c] for c in s])
133 return lambda s: "".join([cmap[c] for c in s])
134
134
135 lowerencode = _buildlowerencodefun()
135 lowerencode = _buildlowerencodefun()
136
136
137 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
137 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
138 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
138 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
139 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
139 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
140 def _auxencode(path, dotencode):
140 def _auxencode(path, dotencode):
141 '''
141 '''
142 Encodes filenames containing names reserved by Windows or which end in
142 Encodes filenames containing names reserved by Windows or which end in
143 period or space. Does not touch other single reserved characters c.
143 period or space. Does not touch other single reserved characters c.
144 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
144 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
145 Additionally encodes space or period at the beginning, if dotencode is
145 Additionally encodes space or period at the beginning, if dotencode is
146 True. Parameter path is assumed to be all lowercase.
146 True. Parameter path is assumed to be all lowercase.
147 A segment only needs encoding if a reserved name appears as a
147 A segment only needs encoding if a reserved name appears as a
148 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
148 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
149 doesn't need encoding.
149 doesn't need encoding.
150
150
151 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
151 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
152 >>> _auxencode(s.split('/'), True)
152 >>> _auxencode(s.split('/'), True)
153 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
153 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
154 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
154 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
155 >>> _auxencode(s.split('/'), False)
155 >>> _auxencode(s.split('/'), False)
156 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
156 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
157 >>> _auxencode(['foo. '], True)
157 >>> _auxencode(['foo. '], True)
158 ['foo.~20']
158 ['foo.~20']
159 >>> _auxencode([' .foo'], True)
159 >>> _auxencode([' .foo'], True)
160 ['~20.foo']
160 ['~20.foo']
161 '''
161 '''
162 for i, n in enumerate(path):
162 for i, n in enumerate(path):
163 if not n:
163 if not n:
164 continue
164 continue
165 if dotencode and n[0] in '. ':
165 if dotencode and n[0] in '. ':
166 n = "~%02x" % ord(n[0]) + n[1:]
166 n = "~%02x" % ord(n[0]) + n[1:]
167 path[i] = n
167 path[i] = n
168 else:
168 else:
169 l = n.find('.')
169 l = n.find('.')
170 if l == -1:
170 if l == -1:
171 l = len(n)
171 l = len(n)
172 if ((l == 3 and n[:3] in _winres3) or
172 if ((l == 3 and n[:3] in _winres3) or
173 (l == 4 and n[3] <= '9' and n[3] >= '1'
173 (l == 4 and n[3] <= '9' and n[3] >= '1'
174 and n[:3] in _winres4)):
174 and n[:3] in _winres4)):
175 # encode third letter ('aux' -> 'au~78')
175 # encode third letter ('aux' -> 'au~78')
176 ec = "~%02x" % ord(n[2])
176 ec = "~%02x" % ord(n[2])
177 n = n[0:2] + ec + n[3:]
177 n = n[0:2] + ec + n[3:]
178 path[i] = n
178 path[i] = n
179 if n[-1] in '. ':
179 if n[-1] in '. ':
180 # encode last period or space ('foo...' -> 'foo..~2e')
180 # encode last period or space ('foo...' -> 'foo..~2e')
181 path[i] = n[:-1] + "~%02x" % ord(n[-1])
181 path[i] = n[:-1] + "~%02x" % ord(n[-1])
182 return path
182 return path
183
183
184 _maxstorepathlen = 120
184 _maxstorepathlen = 120
185 _dirprefixlen = 8
185 _dirprefixlen = 8
186 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
186 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
187
187
188 def _hashencode(path, dotencode):
188 def _hashencode(path, dotencode):
189 digest = _sha(path).hexdigest()
189 digest = _sha(path).hexdigest()
190 le = lowerencode(path).split('/')[1:]
190 le = lowerencode(path).split('/')[1:]
191 parts = _auxencode(le, dotencode)
191 parts = _auxencode(le, dotencode)
192 basename = parts[-1]
192 basename = parts[-1]
193 _root, ext = os.path.splitext(basename)
193 _root, ext = os.path.splitext(basename)
194 sdirs = []
194 sdirs = []
195 sdirslen = 0
195 sdirslen = 0
196 for p in parts[:-1]:
196 for p in parts[:-1]:
197 d = p[:_dirprefixlen]
197 d = p[:_dirprefixlen]
198 if d[-1] in '. ':
198 if d[-1] in '. ':
199 # Windows can't access dirs ending in period or space
199 # Windows can't access dirs ending in period or space
200 d = d[:-1] + '_'
200 d = d[:-1] + '_'
201 if sdirslen == 0:
201 if sdirslen == 0:
202 t = len(d)
202 t = len(d)
203 else:
203 else:
204 t = sdirslen + 1 + len(d)
204 t = sdirslen + 1 + len(d)
205 if t > _maxshortdirslen:
205 if t > _maxshortdirslen:
206 break
206 break
207 sdirs.append(d)
207 sdirs.append(d)
208 sdirslen = t
208 sdirslen = t
209 dirs = '/'.join(sdirs)
209 dirs = '/'.join(sdirs)
210 if len(dirs) > 0:
210 if len(dirs) > 0:
211 dirs += '/'
211 dirs += '/'
212 res = 'dh/' + dirs + digest + ext
212 res = 'dh/' + dirs + digest + ext
213 spaceleft = _maxstorepathlen - len(res)
213 spaceleft = _maxstorepathlen - len(res)
214 if spaceleft > 0:
214 if spaceleft > 0:
215 filler = basename[:spaceleft]
215 filler = basename[:spaceleft]
216 res = 'dh/' + dirs + filler + digest + ext
216 res = 'dh/' + dirs + filler + digest + ext
217 return res
217 return res
218
218
219 def _hybridencode(path, dotencode):
219 def _hybridencode(path, dotencode):
220 '''encodes path with a length limit
220 '''encodes path with a length limit
221
221
222 Encodes all paths that begin with 'data/', according to the following.
222 Encodes all paths that begin with 'data/', according to the following.
223
223
224 Default encoding (reversible):
224 Default encoding (reversible):
225
225
226 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
226 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
227 characters are encoded as '~xx', where xx is the two digit hex code
227 characters are encoded as '~xx', where xx is the two digit hex code
228 of the character (see encodefilename).
228 of the character (see encodefilename).
229 Relevant path components consisting of Windows reserved filenames are
229 Relevant path components consisting of Windows reserved filenames are
230 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
230 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
231
231
232 Hashed encoding (not reversible):
232 Hashed encoding (not reversible):
233
233
234 If the default-encoded path is longer than _maxstorepathlen, a
234 If the default-encoded path is longer than _maxstorepathlen, a
235 non-reversible hybrid hashing of the path is done instead.
235 non-reversible hybrid hashing of the path is done instead.
236 This encoding uses up to _dirprefixlen characters of all directory
236 This encoding uses up to _dirprefixlen characters of all directory
237 levels of the lowerencoded path, but not more levels than can fit into
237 levels of the lowerencoded path, but not more levels than can fit into
238 _maxshortdirslen.
238 _maxshortdirslen.
239 Then follows the filler followed by the sha digest of the full path.
239 Then follows the filler followed by the sha digest of the full path.
240 The filler is the beginning of the basename of the lowerencoded path
240 The filler is the beginning of the basename of the lowerencoded path
241 (the basename is everything after the last path separator). The filler
241 (the basename is everything after the last path separator). The filler
242 is as long as possible, filling in characters from the basename until
242 is as long as possible, filling in characters from the basename until
243 the encoded path has _maxstorepathlen characters (or all chars of the
243 the encoded path has _maxstorepathlen characters (or all chars of the
244 basename have been taken).
244 basename have been taken).
245 The extension (e.g. '.i' or '.d') is preserved.
245 The extension (e.g. '.i' or '.d') is preserved.
246
246
247 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
247 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
248 encoding was used.
248 encoding was used.
249 '''
249 '''
250 path = encodedir(path)
250 path = encodedir(path)
251 ef = _encodefname(path).split('/')
251 ef = _encodefname(path).split('/')
252 res = '/'.join(_auxencode(ef, dotencode))
252 res = '/'.join(_auxencode(ef, dotencode))
253 if len(res) > _maxstorepathlen:
253 if len(res) > _maxstorepathlen:
254 res = _hashencode(path, dotencode)
254 res = _hashencode(path, dotencode)
255 return res
255 return res
256
256
257 def _pathencode(path):
257 def _pathencode(path):
258 ef = _encodefname(encodedir(path)).split('/')
258 ef = _encodefname(encodedir(path)).split('/')
259 res = '/'.join(_auxencode(ef, True))
259 res = '/'.join(_auxencode(ef, True))
260 if len(res) > _maxstorepathlen:
260 if len(res) > _maxstorepathlen:
261 return None
261 return None
262 return res
262 return res
263
263
264 _pathencode = getattr(parsers, 'pathencode', _pathencode)
264 _pathencode = getattr(parsers, 'pathencode', _pathencode)
265
265
266 def _dothybridencode(f):
266 def _dothybridencode(f):
267 ef = _pathencode(f)
267 ef = _pathencode(f)
268 if ef is None:
268 if ef is None:
269 return _hashencode(encodedir(f), True)
269 return _hashencode(encodedir(f), True)
270 return ef
270 return ef
271
271
272 def _plainhybridencode(f):
272 def _plainhybridencode(f):
273 return _hybridencode(f, False)
273 return _hybridencode(f, False)
274
274
275 def _calcmode(path):
275 def _calcmode(path):
276 try:
276 try:
277 # files in .hg/ will be created using this mode
277 # files in .hg/ will be created using this mode
278 mode = os.stat(path).st_mode
278 mode = os.stat(path).st_mode
279 # avoid some useless chmods
279 # avoid some useless chmods
280 if (0777 & ~util.umask) == (0777 & mode):
280 if (0777 & ~util.umask) == (0777 & mode):
281 mode = None
281 mode = None
282 except OSError:
282 except OSError:
283 mode = None
283 mode = None
284 return mode
284 return mode
285
285
286 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
286 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
287 ' phaseroots obsstore')
287 ' phaseroots obsstore')
288
288
289 class basicstore(object):
289 class basicstore(object):
290 '''base class for local repository stores'''
290 '''base class for local repository stores'''
291 def __init__(self, path, openertype):
291 def __init__(self, path, openertype):
292 self.path = path
292 self.path = path
293 self.createmode = _calcmode(path)
293 self.createmode = _calcmode(path)
294 op = openertype(self.path)
294 op = openertype(self.path)
295 op.createmode = self.createmode
295 op.createmode = self.createmode
296 self.opener = scmutil.filteropener(op, encodedir)
296 self.opener = scmutil.filteropener(op, encodedir)
297
297
298 def join(self, f):
298 def join(self, f):
299 return self.path + '/' + encodedir(f)
299 return self.path + '/' + encodedir(f)
300
300
301 def _walk(self, relpath, recurse):
301 def _walk(self, relpath, recurse):
302 '''yields (unencoded, encoded, size)'''
302 '''yields (unencoded, encoded, size)'''
303 path = self.path
303 path = self.path
304 if relpath:
304 if relpath:
305 path += '/' + relpath
305 path += '/' + relpath
306 striplen = len(self.path) + 1
306 striplen = len(self.path) + 1
307 l = []
307 l = []
308 if os.path.isdir(path):
308 if os.path.isdir(path):
309 visit = [path]
309 visit = [path]
310 while visit:
310 while visit:
311 p = visit.pop()
311 p = visit.pop()
312 for f, kind, st in osutil.listdir(p, stat=True):
312 for f, kind, st in osutil.listdir(p, stat=True):
313 fp = p + '/' + f
313 fp = p + '/' + f
314 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
314 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
315 n = util.pconvert(fp[striplen:])
315 n = util.pconvert(fp[striplen:])
316 l.append((decodedir(n), n, st.st_size))
316 l.append((decodedir(n), n, st.st_size))
317 elif kind == stat.S_IFDIR and recurse:
317 elif kind == stat.S_IFDIR and recurse:
318 visit.append(fp)
318 visit.append(fp)
319 l.sort()
319 l.sort()
320 return l
320 return l
321
321
322 def datafiles(self):
322 def datafiles(self):
323 return self._walk('data', True)
323 return self._walk('data', True)
324
324
325 def walk(self):
325 def walk(self):
326 '''yields (unencoded, encoded, size)'''
326 '''yields (unencoded, encoded, size)'''
327 # yield data files first
327 # yield data files first
328 for x in self.datafiles():
328 for x in self.datafiles():
329 yield x
329 yield x
330 # yield manifest before changelog
330 # yield manifest before changelog
331 for x in reversed(self._walk('', False)):
331 for x in reversed(self._walk('', False)):
332 yield x
332 yield x
333
333
334 def copylist(self):
334 def copylist(self):
335 return ['requires'] + _data.split()
335 return ['requires'] + _data.split()
336
336
337 def write(self):
337 def write(self):
338 pass
338 pass
339
339
340 class encodedstore(basicstore):
340 class encodedstore(basicstore):
341 def __init__(self, path, openertype):
341 def __init__(self, path, openertype):
342 self.path = path + '/store'
342 self.path = path + '/store'
343 self.createmode = _calcmode(self.path)
343 self.createmode = _calcmode(self.path)
344 op = openertype(self.path)
344 op = openertype(self.path)
345 op.createmode = self.createmode
345 op.createmode = self.createmode
346 self.opener = scmutil.filteropener(op, encodefilename)
346 self.opener = scmutil.filteropener(op, encodefilename)
347
347
348 def datafiles(self):
348 def datafiles(self):
349 for a, b, size in self._walk('data', True):
349 for a, b, size in self._walk('data', True):
350 try:
350 try:
351 a = decodefilename(a)
351 a = decodefilename(a)
352 except KeyError:
352 except KeyError:
353 a = None
353 a = None
354 yield a, b, size
354 yield a, b, size
355
355
356 def join(self, f):
356 def join(self, f):
357 return self.path + '/' + encodefilename(f)
357 return self.path + '/' + encodefilename(f)
358
358
359 def copylist(self):
359 def copylist(self):
360 return (['requires', '00changelog.i'] +
360 return (['requires', '00changelog.i'] +
361 ['store/' + f for f in _data.split()])
361 ['store/' + f for f in _data.split()])
362
362
363 class fncache(object):
363 class fncache(object):
364 # the filename used to be partially encoded
364 # the filename used to be partially encoded
365 # hence the encodedir/decodedir dance
365 # hence the encodedir/decodedir dance
366 def __init__(self, opener):
366 def __init__(self, opener):
367 self.opener = opener
367 self.opener = opener
368 self.entries = None
368 self.entries = None
369 self._dirty = False
369 self._dirty = False
370
370
371 def _load(self):
371 def _load(self):
372 '''fill the entries from the fncache file'''
372 '''fill the entries from the fncache file'''
373 self._dirty = False
373 self._dirty = False
374 try:
374 try:
375 fp = self.opener('fncache', mode='rb')
375 fp = self.opener('fncache', mode='rb')
376 except IOError:
376 except IOError:
377 # skip nonexistent file
377 # skip nonexistent file
378 self.entries = set()
378 self.entries = set()
379 return
379 return
380 self.entries = set(decodedir(fp.read()).splitlines())
380 self.entries = set(decodedir(fp.read()).splitlines())
381 if '' in self.entries:
381 if '' in self.entries:
382 fp.seek(0)
382 fp.seek(0)
383 for n, line in enumerate(fp):
383 for n, line in enumerate(fp):
384 if not line.rstrip('\n'):
384 if not line.rstrip('\n'):
385 t = _('invalid entry in fncache, line %s') % (n + 1)
385 t = _('invalid entry in fncache, line %s') % (n + 1)
386 raise util.Abort(t)
386 raise util.Abort(t)
387 fp.close()
387 fp.close()
388
388
389 def _write(self, files, atomictemp):
389 def _write(self, files, atomictemp):
390 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
390 fp = self.opener('fncache', mode='wb', atomictemp=atomictemp)
391 if files:
391 if files:
392 fp.write(encodedir('\n'.join(files) + '\n'))
392 fp.write(encodedir('\n'.join(files) + '\n'))
393 fp.close()
393 fp.close()
394 self._dirty = False
394 self._dirty = False
395
395
396 def rewrite(self, files):
396 def rewrite(self, files):
397 self._write(files, False)
397 self._write(files, False)
398 self.entries = set(files)
398 self.entries = set(files)
399
399
400 def write(self):
400 def write(self):
401 if self._dirty:
401 if self._dirty:
402 self._write(self.entries, True)
402 self._write(self.entries, True)
403
403
404 def add(self, fn):
404 def add(self, fn):
405 if self.entries is None:
405 if self.entries is None:
406 self._load()
406 self._load()
407 if fn not in self.entries:
407 if fn not in self.entries:
408 self._dirty = True
408 self._dirty = True
409 self.entries.add(fn)
409 self.entries.add(fn)
410
410
411 def __contains__(self, fn):
411 def __contains__(self, fn):
412 if self.entries is None:
412 if self.entries is None:
413 self._load()
413 self._load()
414 return fn in self.entries
414 return fn in self.entries
415
415
416 def __iter__(self):
416 def __iter__(self):
417 if self.entries is None:
417 if self.entries is None:
418 self._load()
418 self._load()
419 return iter(self.entries)
419 return iter(self.entries)
420
420
421 class _fncacheopener(scmutil.abstractopener):
421 class _fncachevfs(scmutil.abstractvfs):
422 def __init__(self, op, fnc, encode):
422 def __init__(self, op, fnc, encode):
423 self.opener = op
423 self.opener = op
424 self.fncache = fnc
424 self.fncache = fnc
425 self.encode = encode
425 self.encode = encode
426
426
427 def _getmustaudit(self):
427 def _getmustaudit(self):
428 return self.opener.mustaudit
428 return self.opener.mustaudit
429
429
430 def _setmustaudit(self, onoff):
430 def _setmustaudit(self, onoff):
431 self.opener.mustaudit = onoff
431 self.opener.mustaudit = onoff
432
432
433 mustaudit = property(_getmustaudit, _setmustaudit)
433 mustaudit = property(_getmustaudit, _setmustaudit)
434
434
435 def __call__(self, path, mode='r', *args, **kw):
435 def __call__(self, path, mode='r', *args, **kw):
436 if mode not in ('r', 'rb') and path.startswith('data/'):
436 if mode not in ('r', 'rb') and path.startswith('data/'):
437 self.fncache.add(path)
437 self.fncache.add(path)
438 return self.opener(self.encode(path), mode, *args, **kw)
438 return self.opener(self.encode(path), mode, *args, **kw)
439
439
440 class fncachestore(basicstore):
440 class fncachestore(basicstore):
441 def __init__(self, path, openertype, dotencode):
441 def __init__(self, path, openertype, dotencode):
442 if dotencode:
442 if dotencode:
443 encode = _dothybridencode
443 encode = _dothybridencode
444 else:
444 else:
445 encode = _plainhybridencode
445 encode = _plainhybridencode
446 self.encode = encode
446 self.encode = encode
447 self.path = path + '/store'
447 self.path = path + '/store'
448 self.pathsep = self.path + '/'
448 self.pathsep = self.path + '/'
449 self.createmode = _calcmode(self.path)
449 self.createmode = _calcmode(self.path)
450 op = openertype(self.path)
450 op = openertype(self.path)
451 op.createmode = self.createmode
451 op.createmode = self.createmode
452 fnc = fncache(op)
452 fnc = fncache(op)
453 self.fncache = fnc
453 self.fncache = fnc
454 self.opener = _fncacheopener(op, fnc, encode)
454 self.opener = _fncachevfs(op, fnc, encode)
455
455
456 def join(self, f):
456 def join(self, f):
457 return self.pathsep + self.encode(f)
457 return self.pathsep + self.encode(f)
458
458
459 def getsize(self, path):
459 def getsize(self, path):
460 return os.stat(self.pathsep + path).st_size
460 return os.stat(self.pathsep + path).st_size
461
461
462 def datafiles(self):
462 def datafiles(self):
463 rewrite = False
463 rewrite = False
464 existing = []
464 existing = []
465 for f in sorted(self.fncache):
465 for f in sorted(self.fncache):
466 ef = self.encode(f)
466 ef = self.encode(f)
467 try:
467 try:
468 yield f, ef, self.getsize(ef)
468 yield f, ef, self.getsize(ef)
469 existing.append(f)
469 existing.append(f)
470 except OSError, err:
470 except OSError, err:
471 if err.errno != errno.ENOENT:
471 if err.errno != errno.ENOENT:
472 raise
472 raise
473 # nonexistent entry
473 # nonexistent entry
474 rewrite = True
474 rewrite = True
475 if rewrite:
475 if rewrite:
476 # rewrite fncache to remove nonexistent entries
476 # rewrite fncache to remove nonexistent entries
477 # (may be caused by rollback / strip)
477 # (may be caused by rollback / strip)
478 self.fncache.rewrite(existing)
478 self.fncache.rewrite(existing)
479
479
480 def copylist(self):
480 def copylist(self):
481 d = ('data dh fncache phaseroots obsstore'
481 d = ('data dh fncache phaseroots obsstore'
482 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
482 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
483 return (['requires', '00changelog.i'] +
483 return (['requires', '00changelog.i'] +
484 ['store/' + f for f in d.split()])
484 ['store/' + f for f in d.split()])
485
485
486 def write(self):
486 def write(self):
487 self.fncache.write()
487 self.fncache.write()
488
488
489 def store(requirements, path, openertype):
489 def store(requirements, path, openertype):
490 if 'store' in requirements:
490 if 'store' in requirements:
491 if 'fncache' in requirements:
491 if 'fncache' in requirements:
492 return fncachestore(path, openertype, 'dotencode' in requirements)
492 return fncachestore(path, openertype, 'dotencode' in requirements)
493 return encodedstore(path, openertype)
493 return encodedstore(path, openertype)
494 return basicstore(path, openertype)
494 return basicstore(path, openertype)
General Comments 0
You need to be logged in to leave comments. Login now