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