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