##// END OF EJS Templates
merge with stable
Matt Mackall -
r23602:a4679a74 merge default
parent child Browse files
Show More
@@ -98,3 +98,4 b' 7f8d16af8cae246fa5a48e723d48d58b015aed94'
98 ced632394371a36953ce4d394f86278ae51a2aae 0 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
@@ -111,3 +111,4 b' 7f8d16af8cae246fa5a48e723d48d58b015aed94'
111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
114 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 3.2.3
@@ -191,8 +191,15 b' def removelargefiles(ui, repo, isaddremo'
191 # are removing the file.
191 # are removing the file.
192 if isaddremove:
192 if isaddremove:
193 ui.status(_('removing %s\n') % f)
193 ui.status(_('removing %s\n') % f)
194 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
194
195 lfdirstate.remove(f)
195 if not opts.get('dry_run'):
196 if not after:
197 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
198 lfdirstate.remove(f)
199
200 if opts.get('dry_run'):
201 return result
202
196 lfdirstate.write()
203 lfdirstate.write()
197 remove = [lfutil.standin(f) for f in remove]
204 remove = [lfutil.standin(f) for f in remove]
198 # If this is being called by addremove, let the original addremove
205 # If this is being called by addremove, let the original addremove
@@ -17,6 +17,11 b' import revlog'
17
17
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 # Phony node value to stand-in for new files in some uses of
21 # manifests. Manifests support 21-byte hashes for nodes which are
22 # dirty in the working copy.
23 _newnode = '!' * 21
24
20 class basectx(object):
25 class basectx(object):
21 """A basectx object represents the common logic for its children:
26 """A basectx object represents the common logic for its children:
22 changectx: read-only context that is already present in the repo,
27 changectx: read-only context that is already present in the repo,
@@ -104,7 +109,7 b' class basectx(object):'
104 if (fn not in deletedset and
109 if (fn not in deletedset and
105 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
110 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
106 (mf1[fn] != mf2node and
111 (mf1[fn] != mf2node and
107 (mf2node or self[fn].cmp(other[fn]))))):
112 (mf2node != _newnode or self[fn].cmp(other[fn]))))):
108 modified.append(fn)
113 modified.append(fn)
109 elif listclean:
114 elif listclean:
110 clean.append(fn)
115 clean.append(fn)
@@ -1382,7 +1387,7 b' class workingctx(committablectx):'
1382 """
1387 """
1383 mf = self._repo['.']._manifestmatches(match, s)
1388 mf = self._repo['.']._manifestmatches(match, s)
1384 for f in s.modified + s.added:
1389 for f in s.modified + s.added:
1385 mf[f] = None
1390 mf[f] = _newnode
1386 mf.setflag(f, self.flags(f))
1391 mf.setflag(f, self.flags(f))
1387 for f in s.removed:
1392 for f in s.removed:
1388 if f in mf:
1393 if f in mf:
@@ -8,6 +8,28 b''
8 import error
8 import error
9 import unicodedata, locale, os
9 import unicodedata, locale, os
10
10
11 # These unicode characters are ignored by HFS+ (Apple Technote 1150,
12 # "Unicode Subtleties"), so we need to ignore them in some places for
13 # sanity.
14 _ignore = [unichr(int(x, 16)).encode("utf-8") for x in
15 "200c 200d 200e 200f 202a 202b 202c 202d 202e "
16 "206a 206b 206c 206d 206e 206f feff".split()]
17 # verify the next function will work
18 assert set([i[0] for i in _ignore]) == set(["\xe2", "\xef"])
19
20 def hfsignoreclean(s):
21 """Remove codepoints ignored by HFS+ from s.
22
23 >>> hfsignoreclean(u'.h\u200cg'.encode('utf-8'))
24 '.hg'
25 >>> hfsignoreclean(u'.h\ufeffg'.encode('utf-8'))
26 '.hg'
27 """
28 if "\xe2" in s or "\xef" in s:
29 for c in _ignore:
30 s = s.replace(c, '')
31 return s
32
11 def _getpreferredencoding():
33 def _getpreferredencoding():
12 '''
34 '''
13 On darwin, getpreferredencoding ignores the locale environment and
35 On darwin, getpreferredencoding ignores the locale environment and
@@ -17,6 +17,9 b' class manifestdict(dict):'
17 flags = {}
17 flags = {}
18 dict.__init__(self, mapping)
18 dict.__init__(self, mapping)
19 self._flags = flags
19 self._flags = flags
20 def __setitem__(self, k, v):
21 assert v is not None
22 dict.__setitem__(self, k, v)
20 def flags(self, f):
23 def flags(self, f):
21 return self._flags.get(f, "")
24 return self._flags.get(f, "")
22 def withflags(self):
25 def withflags(self):
@@ -1,8 +1,12 b''
1 import os, errno, stat
1 import os, errno, stat
2
2
3 import encoding
3 import util
4 import util
4 from i18n import _
5 from i18n import _
5
6
7 def _lowerclean(s):
8 return encoding.hfsignoreclean(s.lower())
9
6 class pathauditor(object):
10 class pathauditor(object):
7 '''ensure that a filesystem path contains no banned components.
11 '''ensure that a filesystem path contains no banned components.
8 the following properties of a path are checked:
12 the following properties of a path are checked:
@@ -39,11 +43,18 b' class pathauditor(object):'
39 raise util.Abort(_("path ends in directory separator: %s") % path)
43 raise util.Abort(_("path ends in directory separator: %s") % path)
40 parts = util.splitpath(path)
44 parts = util.splitpath(path)
41 if (os.path.splitdrive(path)[0]
45 if (os.path.splitdrive(path)[0]
42 or parts[0].lower() in ('.hg', '.hg.', '')
46 or _lowerclean(parts[0]) in ('.hg', '.hg.', '')
43 or os.pardir in parts):
47 or os.pardir in parts):
44 raise util.Abort(_("path contains illegal component: %s") % path)
48 raise util.Abort(_("path contains illegal component: %s") % path)
45 if '.hg' in path.lower():
49 # Windows shortname aliases
46 lparts = [p.lower() for p in parts]
50 for p in parts:
51 if "~" in p:
52 first, last = p.split("~", 1)
53 if last.isdigit() and first.upper() in ["HG", "HG8B6C"]:
54 raise util.Abort(_("path contains illegal component: %s")
55 % path)
56 if '.hg' in _lowerclean(path):
57 lparts = [_lowerclean(p.lower()) for p in parts]
47 for p in '.hg', '.hg.':
58 for p in '.hg', '.hg.':
48 if p in lparts[1:]:
59 if p in lparts[1:]:
49 pos = lparts.index(p)
60 pos = lparts.index(p)
@@ -208,6 +208,7 b" if sys.platform == 'darwin':"
208 - escape-encode invalid characters
208 - escape-encode invalid characters
209 - decompose to NFD
209 - decompose to NFD
210 - lowercase
210 - lowercase
211 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
211
212
212 >>> normcase('UPPER')
213 >>> normcase('UPPER')
213 'upper'
214 'upper'
@@ -265,7 +266,9 b" if sys.platform == 'darwin':"
265 u = s.decode('utf-8')
266 u = s.decode('utf-8')
266
267
267 # Decompose then lowercase (HFS+ technote specifies lower)
268 # Decompose then lowercase (HFS+ technote specifies lower)
268 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
269 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
270 # drop HFS+ ignored characters
271 return encoding.hfsignoreclean(enc)
269
272
270 if sys.platform == 'cygwin':
273 if sys.platform == 'cygwin':
271 # workaround for cygwin, in which mount point part of path is
274 # workaround for cygwin, in which mount point part of path is
@@ -195,4 +195,16 b' case changes.'
195 $ hg qrefresh a # issue 3271, qrefresh with file handled case wrong
195 $ hg qrefresh a # issue 3271, qrefresh with file handled case wrong
196 $ hg status # empty status means the qrefresh worked
196 $ hg status # empty status means the qrefresh worked
197
197
198 #if osx
199
200 We assume anyone running the tests on a case-insensitive volume on OS
201 X will be using HFS+. If that's not true, this test will fail.
202
203 $ rm A
204 >>> open(u'a\u200c'.encode('utf-8'), 'w').write('unicode is fun')
205 $ hg status
206 M A
207
208 #endif
209
198 $ cd ..
210 $ cd ..
@@ -518,4 +518,55 b' commit copy'
518 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
518 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
519 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
519 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
520
520
521 $ cd ..
521 verify pathauditor blocks evil filepaths
522 $ cat > evil-commit.py <<EOF
523 > from mercurial import ui, hg, context, node
524 > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
525 > u = ui.ui()
526 > r = hg.repository(u, '.')
527 > def filectxfn(repo, memctx, path):
528 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
529 > c = context.memctx(r, [r['tip'].node(), node.nullid],
530 > 'evil', [notrc], filectxfn, 0)
531 > r.commitctx(c)
532 > EOF
533 $ $PYTHON evil-commit.py
534 $ hg co --clean tip
535 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
536 [255]
537
538 $ hg rollback -f
539 repository tip rolled back to revision 1 (undo commit)
540 $ cat > evil-commit.py <<EOF
541 > from mercurial import ui, hg, context, node
542 > notrc = "HG~1/hgrc"
543 > u = ui.ui()
544 > r = hg.repository(u, '.')
545 > def filectxfn(repo, memctx, path):
546 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
547 > c = context.memctx(r, [r['tip'].node(), node.nullid],
548 > 'evil', [notrc], filectxfn, 0)
549 > r.commitctx(c)
550 > EOF
551 $ $PYTHON evil-commit.py
552 $ hg co --clean tip
553 abort: path contains illegal component: HG~1/hgrc
554 [255]
555
556 $ hg rollback -f
557 repository tip rolled back to revision 1 (undo commit)
558 $ cat > evil-commit.py <<EOF
559 > from mercurial import ui, hg, context, node
560 > notrc = "HG8B6C~2/hgrc"
561 > u = ui.ui()
562 > r = hg.repository(u, '.')
563 > def filectxfn(repo, memctx, path):
564 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
565 > c = context.memctx(r, [r['tip'].node(), node.nullid],
566 > 'evil', [notrc], filectxfn, 0)
567 > r.commitctx(c)
568 > EOF
569 $ $PYTHON evil-commit.py
570 $ hg co --clean tip
571 abort: path contains illegal component: HG8B6C~2/hgrc
572 [255]
@@ -255,6 +255,16 b' verify that large files in subrepos hand'
255 Add a normal file to the subrepo, then test archiving
255 Add a normal file to the subrepo, then test archiving
256
256
257 $ echo 'normal file' > subrepo/normal.txt
257 $ echo 'normal file' > subrepo/normal.txt
258 $ mv subrepo/large.txt subrepo/renamed-large.txt
259 $ hg -R subrepo addremove --dry-run
260 removing large.txt
261 adding normal.txt
262 adding renamed-large.txt
263 $ hg status -S
264 ! subrepo/large.txt
265 ? subrepo/normal.txt
266 ? subrepo/renamed-large.txt
267 $ mv subrepo/renamed-large.txt subrepo/large.txt
258 $ hg -R subrepo add subrepo/normal.txt
268 $ hg -R subrepo add subrepo/normal.txt
259
269
260 Lock in subrepo, otherwise the change isn't archived
270 Lock in subrepo, otherwise the change isn't archived
General Comments 0
You need to be logged in to leave comments. Login now