##// END OF EJS Templates
merge with stable
Matt Mackall -
r15674:7b7f0350 merge default
parent child Browse files
Show More
@@ -0,0 +1,109 b''
1 run only on case-insensitive filesystems
2
3 $ "$TESTDIR/hghave" icasefs || exit 80
4
5 ################################
6 test for branch merging
7 ################################
8
9 $ hg init repo1
10 $ cd repo1
11
12 create base revision
13
14 $ echo base > base.txt
15 $ hg add base.txt
16 $ hg commit -m 'base'
17
18 add same file in different case on both heads
19
20 $ echo a > a.txt
21 $ hg add a.txt
22 $ hg commit -m 'add a.txt'
23
24 $ hg update 0
25 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
26
27 $ echo A > A.TXT
28 $ hg add A.TXT
29 $ hg commit -m 'add A.TXT'
30 created new head
31
32 merge another, and fail with case-folding collision
33
34 $ hg merge
35 abort: case-folding collision between a.txt and A.TXT
36 [255]
37
38 check clean-ness of working directory
39
40 $ hg status
41 $ hg parents --template '{rev}\n'
42 2
43 $ cd ..
44
45 ################################
46 test for linear updates
47 ################################
48
49 $ hg init repo2
50 $ cd repo2
51
52 create base revision (rev:0)
53
54 $ hg import --bypass --exact - <<EOF
55 > # HG changeset patch
56 > # User null
57 > # Date 1 0
58 > # Node ID e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
59 > # Parent 0000000000000000000000000000000000000000
60 > add a
61 >
62 > diff --git a/a b/a
63 > new file mode 100644
64 > --- /dev/null
65 > +++ b/a
66 > @@ -0,0 +1,3 @@
67 > +this is line 1
68 > +this is line 2
69 > +this is line 3
70 > EOF
71 applying patch from stdin
72
73 create rename revision (rev:1)
74
75 $ hg import --bypass --exact - <<EOF
76 > # HG changeset patch
77 > # User null
78 > # Date 1 0
79 > # Node ID 9dca9f19bb91851bc693544b598b0740629edfad
80 > # Parent e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
81 > rename a to A
82 >
83 > diff --git a/a b/A
84 > rename from a
85 > rename to A
86 > EOF
87 applying patch from stdin
88
89 update to base revision, and modify 'a'
90
91 $ hg update 0
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 $ echo 'this is added line' >> a
94
95 update to current tip linearly
96
97 $ hg update 1
98 merging a and A to A
99 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
100
101 check status and contents of file
102
103 $ hg status -A
104 M A
105 $ cat A
106 this is line 1
107 this is line 2
108 this is line 3
109 this is added line
@@ -0,0 +1,108 b''
1
2 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "largefiles =" >> $HGRCPATH
4
5 Create the repository outside $HOME since largefiles write to
6 $HOME/.cache/largefiles.
7
8 $ hg init test
9 $ cd test
10 $ echo "root" > root
11 $ hg add root
12 $ hg commit -m "Root commit"
13
14 $ echo "large" > foo
15 $ hg add --large foo
16 $ hg commit -m "Add foo as a largefile"
17
18 $ hg update -r 0
19 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
20 getting changed largefiles
21 0 largefiles updated, 1 removed
22
23 $ echo "normal" > foo
24 $ hg add foo
25 $ hg commit -m "Add foo as normal file"
26 created new head
27
28 Normal file in the working copy, keeping the normal version:
29
30 $ echo "n" | hg merge --config ui.interactive=Yes
31 foo has been turned into a largefile
32 use (l)argefile or keep as (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
33 (branch merge, don't forget to commit)
34
35 $ hg status
36 $ cat foo
37 normal
38
39 Normal file in the working copy, keeping the largefile version:
40
41 $ hg update -q -C
42 $ echo "l" | hg merge --config ui.interactive=Yes
43 foo has been turned into a largefile
44 use (l)argefile or keep as (n)ormal file? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
45 (branch merge, don't forget to commit)
46 getting changed largefiles
47 1 largefiles updated, 0 removed
48
49 $ hg status
50 M foo
51
52 $ hg diff --nodates
53 diff -r fa129ab6b5a7 .hglf/foo
54 --- /dev/null
55 +++ b/.hglf/foo
56 @@ -0,0 +1,1 @@
57 +7f7097b041ccf68cc5561e9600da4655d21c6d18
58 diff -r fa129ab6b5a7 foo
59 --- a/foo
60 +++ /dev/null
61 @@ -1,1 +0,0 @@
62 -normal
63
64 $ cat foo
65 large
66
67 Largefile in the working copy, keeping the normal version:
68
69 $ hg update -q -C -r 1
70 $ echo "n" | hg merge --config ui.interactive=Yes
71 foo has been turned into a normal file
72 keep as (l)argefile or use (n)ormal file? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 (branch merge, don't forget to commit)
74 getting changed largefiles
75 0 largefiles updated, 0 removed
76
77 $ hg status
78 M foo
79
80 $ hg diff --nodates
81 diff -r ff521236428a .hglf/foo
82 --- a/.hglf/foo
83 +++ /dev/null
84 @@ -1,1 +0,0 @@
85 -7f7097b041ccf68cc5561e9600da4655d21c6d18
86 diff -r ff521236428a foo
87 --- /dev/null
88 +++ b/foo
89 @@ -0,0 +1,1 @@
90 +normal
91
92 $ cat foo
93 normal
94
95 Largefile in the working copy, keeping the largefile version:
96
97 $ hg update -q -C -r 1
98 $ echo "l" | hg merge --config ui.interactive=Yes
99 foo has been turned into a normal file
100 keep as (l)argefile or use (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
101 (branch merge, don't forget to commit)
102 getting changed largefiles
103 1 largefiles updated, 0 removed
104
105 $ hg status
106
107 $ cat foo
108 large
@@ -446,7 +446,11 b' def _updatelfile(repo, lfdirstate, lfile'
446 os.chmod(abslfile, mode)
446 os.chmod(abslfile, mode)
447 ret = 1
447 ret = 1
448 else:
448 else:
449 if os.path.exists(abslfile):
449 # Remove lfiles for which the standin is deleted, unless the
450 # lfile is added to the repository again. This happens when a
451 # largefile is converted back to a normal file: the standin
452 # disappears, but a new (normal) file appears as the lfile.
453 if os.path.exists(abslfile) and lfile not in repo[None]:
450 os.unlink(abslfile)
454 os.unlink(abslfile)
451 ret = -1
455 ret = -1
452 state = repo.dirstate[lfutil.standin(lfile)]
456 state = repo.dirstate[lfutil.standin(lfile)]
@@ -242,6 +242,90 b' def override_update(orig, ui, repo, *pat'
242 wlock.release()
242 wlock.release()
243 return orig(ui, repo, *pats, **opts)
243 return orig(ui, repo, *pats, **opts)
244
244
245 # Before starting the manifest merge, merge.updates will call
246 # _checkunknown to check if there are any files in the merged-in
247 # changeset that collide with unknown files in the working copy.
248 #
249 # The largefiles are seen as unknown, so this prevents us from merging
250 # in a file 'foo' if we already have a largefile with the same name.
251 #
252 # The overridden function filters the unknown files by removing any
253 # largefiles. This makes the merge proceed and we can then handle this
254 # case further in the overridden manifestmerge function below.
255 def override_checkunknown(origfn, wctx, mctx, folding):
256 origunknown = wctx.unknown()
257 wctx._unknown = filter(lambda f: lfutil.standin(f) not in wctx, origunknown)
258 try:
259 return origfn(wctx, mctx, folding)
260 finally:
261 wctx._unknown = origunknown
262
263 # The manifest merge handles conflicts on the manifest level. We want
264 # to handle changes in largefile-ness of files at this level too.
265 #
266 # The strategy is to run the original manifestmerge and then process
267 # the action list it outputs. There are two cases we need to deal with:
268 #
269 # 1. Normal file in p1, largefile in p2. Here the largefile is
270 # detected via its standin file, which will enter the working copy
271 # with a "get" action. It is not "merge" since the standin is all
272 # Mercurial is concerned with at this level -- the link to the
273 # existing normal file is not relevant here.
274 #
275 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
276 # since the largefile will be present in the working copy and
277 # different from the normal file in p2. Mercurial therefore
278 # triggers a merge action.
279 #
280 # In both cases, we prompt the user and emit new actions to either
281 # remove the standin (if the normal file was kept) or to remove the
282 # normal file and get the standin (if the largefile was kept). The
283 # default prompt answer is to use the largefile version since it was
284 # presumably changed on purpose.
285 #
286 # Finally, the merge.applyupdates function will then take care of
287 # writing the files into the working copy and lfcommands.updatelfiles
288 # will update the largefiles.
289 def override_manifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
290 actions = origfn(repo, p1, p2, pa, overwrite, partial)
291 processed = []
292
293 for action in actions:
294 if overwrite:
295 processed.append(action)
296 continue
297 f, m = action[:2]
298
299 choices = (_('&Largefile'), _('&Normal file'))
300 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
301 # Case 1: normal file in the working copy, largefile in
302 # the second parent
303 lfile = lfutil.splitstandin(f)
304 standin = f
305 msg = _('%s has been turned into a largefile\n'
306 'use (l)argefile or keep as (n)ormal file?') % lfile
307 if repo.ui.promptchoice(msg, choices, 0) == 0:
308 processed.append((lfile, "r"))
309 processed.append((standin, "g", p2.flags(standin)))
310 else:
311 processed.append((standin, "r"))
312 elif m == "m" and lfutil.standin(f) in p1 and f in p2:
313 # Case 2: largefile in the working copy, normal file in
314 # the second parent
315 standin = lfutil.standin(f)
316 lfile = f
317 msg = _('%s has been turned into a normal file\n'
318 'keep as (l)argefile or use (n)ormal file?') % lfile
319 if repo.ui.promptchoice(msg, choices, 0) == 0:
320 processed.append((lfile, "r"))
321 else:
322 processed.append((standin, "r"))
323 processed.append((lfile, "g", p2.flags(lfile)))
324 else:
325 processed.append(action)
326
327 return processed
328
245 # Override filemerge to prompt the user about how they wish to merge
329 # Override filemerge to prompt the user about how they wish to merge
246 # largefiles. This will handle identical edits, and copy/rename +
330 # largefiles. This will handle identical edits, and copy/rename +
247 # edit without prompting the user.
331 # edit without prompting the user.
@@ -215,9 +215,18 b' def reposetup(ui, repo):'
215 continue
215 continue
216 if lfile not in lfdirstate:
216 if lfile not in lfdirstate:
217 removed.append(lfile)
217 removed.append(lfile)
218 # Handle unknown and ignored differently
218
219 lfiles = (modified, added, removed, missing, [], [], clean)
219 # Filter result lists
220 result = list(result)
220 result = list(result)
221
222 # Largefiles are not really removed when they're
223 # still in the normal dirstate. Likewise, normal
224 # files are not really removed if it's still in
225 # lfdirstate. This happens in merges where files
226 # change type.
227 removed = [f for f in removed if f not in repo.dirstate]
228 result[2] = [f for f in result[2] if f not in lfdirstate]
229
221 # Unknown files
230 # Unknown files
222 unknown = set(unknown).difference(ignored)
231 unknown = set(unknown).difference(ignored)
223 result[4] = [f for f in unknown
232 result[4] = [f for f in unknown
@@ -230,6 +239,7 b' def reposetup(ui, repo):'
230 normals = [[fn for fn in filelist
239 normals = [[fn for fn in filelist
231 if not lfutil.isstandin(fn)]
240 if not lfutil.isstandin(fn)]
232 for filelist in result]
241 for filelist in result]
242 lfiles = (modified, added, removed, missing, [], [], clean)
233 result = [sorted(list1 + list2)
243 result = [sorted(list1 + list2)
234 for (list1, list2) in zip(normals, lfiles)]
244 for (list1, list2) in zip(normals, lfiles)]
235 else:
245 else:
@@ -9,7 +9,7 b''
9 '''setup for largefiles extension: uisetup'''
9 '''setup for largefiles extension: uisetup'''
10
10
11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
12 httprepo, localrepo, sshrepo, sshserver, wireproto
12 httprepo, localrepo, merge, sshrepo, sshserver, wireproto
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial.hgweb import hgweb_mod, protocol
14 from mercurial.hgweb import hgweb_mod, protocol
15
15
@@ -63,6 +63,10 b' def uisetup(ui):'
63 overrides.override_update)
63 overrides.override_update)
64 entry = extensions.wrapcommand(commands.table, 'pull',
64 entry = extensions.wrapcommand(commands.table, 'pull',
65 overrides.override_pull)
65 overrides.override_pull)
66 entry = extensions.wrapfunction(merge, '_checkunknown',
67 overrides.override_checkunknown)
68 entry = extensions.wrapfunction(merge, 'manifestmerge',
69 overrides.override_manifestmerge)
66 entry = extensions.wrapfunction(filemerge, 'filemerge',
70 entry = extensions.wrapfunction(filemerge, 'filemerge',
67 overrides.override_filemerge)
71 overrides.override_filemerge)
68 entry = extensions.wrapfunction(cmdutil, 'copy',
72 entry = extensions.wrapfunction(cmdutil, 'copy',
@@ -271,17 +271,21 b' def uisetup(ui):'
271 class progressui(ui.__class__):
271 class progressui(ui.__class__):
272 _progbar = None
272 _progbar = None
273
273
274 def _quiet(self):
275 return self.debugflag or self.quiet
276
274 def progress(self, *args, **opts):
277 def progress(self, *args, **opts):
275 self._progbar.progress(*args, **opts)
278 if not self._quiet():
279 self._progbar.progress(*args, **opts)
276 return super(progressui, self).progress(*args, **opts)
280 return super(progressui, self).progress(*args, **opts)
277
281
278 def write(self, *args, **opts):
282 def write(self, *args, **opts):
279 if self._progbar.printed:
283 if not self._quiet() and self._progbar.printed:
280 self._progbar.clear()
284 self._progbar.clear()
281 return super(progressui, self).write(*args, **opts)
285 return super(progressui, self).write(*args, **opts)
282
286
283 def write_err(self, *args, **opts):
287 def write_err(self, *args, **opts):
284 if self._progbar.printed:
288 if not self._quiet() and self._progbar.printed:
285 self._progbar.clear()
289 self._progbar.clear()
286 return super(progressui, self).write_err(*args, **opts)
290 return super(progressui, self).write_err(*args, **opts)
287
291
@@ -127,7 +127,7 b' def wrapname(name, wrapper):'
127 # NOTE: os.path.dirname() and os.path.basename() are safe because
127 # NOTE: os.path.dirname() and os.path.basename() are safe because
128 # they use result of os.path.split()
128 # they use result of os.path.split()
129 funcs = '''os.path.join os.path.split os.path.splitext
129 funcs = '''os.path.join os.path.split os.path.splitext
130 os.path.splitunc os.path.normpath os.path.normcase os.makedirs
130 os.path.splitunc os.path.normpath os.makedirs
131 mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
131 mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
132 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
132 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
133 mercurial.util.checkwinfilename mercurial.util.checkosfilename'''
133 mercurial.util.checkwinfilename mercurial.util.checkosfilename'''
@@ -24,9 +24,20 b' def _string_escape(text):'
24 return text.replace('\0', '\\0')
24 return text.replace('\0', '\\0')
25
25
26 def decodeextra(text):
26 def decodeextra(text):
27 """
28 >>> decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'}))
29 {'foo': 'bar', 'baz': '\\x002'}
30 >>> decodeextra(encodeextra({'foo': 'bar', 'baz': chr(92) + chr(0) + '2'}))
31 {'foo': 'bar', 'baz': '\\\\\\x002'}
32 """
27 extra = {}
33 extra = {}
28 for l in text.split('\0'):
34 for l in text.split('\0'):
29 if l:
35 if l:
36 if '\\0' in l:
37 # fix up \0 without getting into trouble with \\0
38 l = l.replace('\\\\', '\\\\\n')
39 l = l.replace('\\0', '\0')
40 l = l.replace('\n', '')
30 k, v = l.decode('string_escape').split(':', 1)
41 k, v = l.decode('string_escape').split(':', 1)
31 extra[k] = v
42 extra[k] = v
32 return extra
43 return extra
@@ -65,10 +65,15 b' class dirstate(object):'
65 return self._copymap
65 return self._copymap
66
66
67 @propertycache
67 @propertycache
68 def _normroot(self):
69 return util.normcase(self._root)
70
71 @propertycache
68 def _foldmap(self):
72 def _foldmap(self):
69 f = {}
73 f = {}
70 for name in self._map:
74 for name in self._map:
71 f[util.normcase(name)] = name
75 f[util.normcase(name)] = name
76 f['.'] = '.' # prevents useless util.fspath() invocation
72 return f
77 return f
73
78
74 @propertycache
79 @propertycache
@@ -383,7 +388,7 b' class dirstate(object):'
383 folded = path
388 folded = path
384 else:
389 else:
385 folded = self._foldmap.setdefault(normed,
390 folded = self._foldmap.setdefault(normed,
386 util.fspath(path, self._root))
391 util.fspath(normed, self._normroot))
387 return folded
392 return folded
388
393
389 def normalize(self, path, isknown=False):
394 def normalize(self, path, isknown=False):
@@ -171,3 +171,22 b' def lower(s):'
171 return lu.encode(encoding)
171 return lu.encode(encoding)
172 except UnicodeError:
172 except UnicodeError:
173 return s.lower() # we don't know how to fold this except in ASCII
173 return s.lower() # we don't know how to fold this except in ASCII
174 except LookupError, k:
175 raise error.Abort(k, hint="please check your locale settings")
176
177 def upper(s):
178 "best-effort encoding-aware case-folding of local string s"
179 try:
180 if isinstance(s, localstr):
181 u = s._utf8.decode("utf-8")
182 else:
183 u = s.decode(encoding, encodingmode)
184
185 uu = u.upper()
186 if u == uu:
187 return s # preserve localstring
188 return uu.encode(encoding)
189 except UnicodeError:
190 return s.upper() # we don't know how to fold this except in ASCII
191 except LookupError, k:
192 raise error.Abort(k, hint="please check your locale settings")
@@ -96,7 +96,7 b' def _checkunknown(wctx, mctx, folding):'
96 raise util.Abort(_("untracked file in working directory differs"
96 raise util.Abort(_("untracked file in working directory differs"
97 " from file in requested revision: '%s'") % fn)
97 " from file in requested revision: '%s'") % fn)
98
98
99 def _checkcollision(mctx):
99 def _checkcollision(mctx, wctx):
100 "check for case folding collisions in the destination context"
100 "check for case folding collisions in the destination context"
101 folded = {}
101 folded = {}
102 for fn in mctx:
102 for fn in mctx:
@@ -106,6 +106,14 b' def _checkcollision(mctx):'
106 % (fn, folded[fold]))
106 % (fn, folded[fold]))
107 folded[fold] = fn
107 folded[fold] = fn
108
108
109 if wctx:
110 for fn in wctx:
111 fold = util.normcase(fn)
112 mfn = folded.get(fold, None)
113 if mfn and (mfn != fn):
114 raise util.Abort(_("case-folding collision between %s and %s")
115 % (mfn, fn))
116
109 def _forgetremoved(wctx, mctx, branchmerge):
117 def _forgetremoved(wctx, mctx, branchmerge):
110 """
118 """
111 Forget removed files
119 Forget removed files
@@ -551,7 +559,7 b' def update(repo, node, branchmerge, forc'
551 if not force:
559 if not force:
552 _checkunknown(wc, p2, folding)
560 _checkunknown(wc, p2, folding)
553 if folding:
561 if folding:
554 _checkcollision(p2)
562 _checkcollision(p2, branchmerge and p1)
555 action += _forgetremoved(wc, p2, branchmerge)
563 action += _forgetremoved(wc, p2, branchmerge)
556 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
564 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
557
565
@@ -164,6 +164,9 b' def samedevice(fpath1, fpath2):'
164 st2 = os.lstat(fpath2)
164 st2 = os.lstat(fpath2)
165 return st1.st_dev == st2.st_dev
165 return st1.st_dev == st2.st_dev
166
166
167 encodinglower = None
168 encodingupper = None
169
167 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
170 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
168 def normcase(path):
171 def normcase(path):
169 return path.lower()
172 return path.lower()
@@ -76,18 +76,22 b' class pathauditor(object):'
76 self.auditeddir = set()
76 self.auditeddir = set()
77 self.root = root
77 self.root = root
78 self.callback = callback
78 self.callback = callback
79 if os.path.lexists(root) and not util.checkcase(root):
80 self.normcase = util.normcase
81 else:
82 self.normcase = lambda x: x
79
83
80 def __call__(self, path):
84 def __call__(self, path):
81 '''Check the relative path.
85 '''Check the relative path.
82 path may contain a pattern (e.g. foodir/**.txt)'''
86 path may contain a pattern (e.g. foodir/**.txt)'''
83
87
84 if path in self.audited:
88 normpath = self.normcase(path)
89 if normpath in self.audited:
85 return
90 return
86 # AIX ignores "/" at end of path, others raise EISDIR.
91 # AIX ignores "/" at end of path, others raise EISDIR.
87 if util.endswithsep(path):
92 if util.endswithsep(path):
88 raise util.Abort(_("path ends in directory separator: %s") % path)
93 raise util.Abort(_("path ends in directory separator: %s") % path)
89 normpath = os.path.normcase(path)
94 parts = util.splitpath(path)
90 parts = util.splitpath(normpath)
91 if (os.path.splitdrive(path)[0]
95 if (os.path.splitdrive(path)[0]
92 or parts[0].lower() in ('.hg', '.hg.', '')
96 or parts[0].lower() in ('.hg', '.hg.', '')
93 or os.pardir in parts):
97 or os.pardir in parts):
@@ -101,11 +105,16 b' class pathauditor(object):'
101 raise util.Abort(_("path '%s' is inside nested repo %r")
105 raise util.Abort(_("path '%s' is inside nested repo %r")
102 % (path, base))
106 % (path, base))
103
107
108 normparts = util.splitpath(normpath)
109 assert len(parts) == len(normparts)
110
104 parts.pop()
111 parts.pop()
112 normparts.pop()
105 prefixes = []
113 prefixes = []
106 while parts:
114 while parts:
107 prefix = os.sep.join(parts)
115 prefix = os.sep.join(parts)
108 if prefix in self.auditeddir:
116 normprefix = os.sep.join(normparts)
117 if normprefix in self.auditeddir:
109 break
118 break
110 curpath = os.path.join(self.root, prefix)
119 curpath = os.path.join(self.root, prefix)
111 try:
120 try:
@@ -125,10 +134,11 b' class pathauditor(object):'
125 if not self.callback or not self.callback(curpath):
134 if not self.callback or not self.callback(curpath):
126 raise util.Abort(_("path '%s' is inside nested repo %r") %
135 raise util.Abort(_("path '%s' is inside nested repo %r") %
127 (path, prefix))
136 (path, prefix))
128 prefixes.append(prefix)
137 prefixes.append(normprefix)
129 parts.pop()
138 parts.pop()
139 normparts.pop()
130
140
131 self.audited.add(path)
141 self.audited.add(normpath)
132 # only add prefixes to the cache after checking everything: we don't
142 # only add prefixes to the cache after checking everything: we don't
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
143 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 self.auditeddir.update(prefixes)
144 self.auditeddir.update(prefixes)
@@ -24,6 +24,9 b" if os.name == 'nt':"
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 platform.encodinglower = encoding.lower
28 platform.encodingupper = encoding.upper
29
27 cachestat = platform.cachestat
30 cachestat = platform.cachestat
28 checkexec = platform.checkexec
31 checkexec = platform.checkexec
29 checklink = platform.checklink
32 checklink = platform.checklink
@@ -593,9 +596,12 b' def checkcase(path):'
593 """
596 """
594 s1 = os.stat(path)
597 s1 = os.stat(path)
595 d, b = os.path.split(path)
598 d, b = os.path.split(path)
596 p2 = os.path.join(d, b.upper())
599 b2 = b.upper()
597 if path == p2:
600 if b == b2:
598 p2 = os.path.join(d, b.lower())
601 b2 = b.lower()
602 if b == b2:
603 return True # no evidence against case sensitivity
604 p2 = os.path.join(d, b2)
599 try:
605 try:
600 s2 = os.stat(p2)
606 s2 = os.stat(p2)
601 if s2 == s1:
607 if s2 == s1:
@@ -611,9 +617,11 b' def fspath(name, root):'
611 The name is either relative to root, or it is an absolute path starting
617 The name is either relative to root, or it is an absolute path starting
612 with root. Note that this function is unnecessary, and should not be
618 with root. Note that this function is unnecessary, and should not be
613 called, for case-sensitive filesystems (simply because it's expensive).
619 called, for case-sensitive filesystems (simply because it's expensive).
620
621 Both name and root should be normcase-ed.
614 '''
622 '''
615 # If name is absolute, make it relative
623 # If name is absolute, make it relative
616 if name.lower().startswith(root.lower()):
624 if name.startswith(root):
617 l = len(root)
625 l = len(root)
618 if name[l] == os.sep or name[l] == os.altsep:
626 if name[l] == os.sep or name[l] == os.altsep:
619 l = l + 1
627 l = l + 1
@@ -628,7 +636,7 b' def fspath(name, root):'
628 # Protect backslashes. This gets silly very quickly.
636 # Protect backslashes. This gets silly very quickly.
629 seps.replace('\\','\\\\')
637 seps.replace('\\','\\\\')
630 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
638 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
631 dir = os.path.normcase(os.path.normpath(root))
639 dir = os.path.normpath(root)
632 result = []
640 result = []
633 for part, sep in pattern.findall(name):
641 for part, sep in pattern.findall(name):
634 if sep:
642 if sep:
@@ -639,16 +647,15 b' def fspath(name, root):'
639 _fspathcache[dir] = os.listdir(dir)
647 _fspathcache[dir] = os.listdir(dir)
640 contents = _fspathcache[dir]
648 contents = _fspathcache[dir]
641
649
642 lpart = part.lower()
643 lenp = len(part)
650 lenp = len(part)
644 for n in contents:
651 for n in contents:
645 if lenp == len(n) and n.lower() == lpart:
652 if lenp == len(n) and normcase(n) == part:
646 result.append(n)
653 result.append(n)
647 break
654 break
648 else:
655 else:
649 # Cannot happen, as the file exists!
656 # Cannot happen, as the file exists!
650 result.append(part)
657 result.append(part)
651 dir = os.path.join(dir, lpart)
658 dir = os.path.join(dir, part)
652
659
653 return ''.join(result)
660 return ''.join(result)
654
661
@@ -131,7 +131,11 b' def localpath(path):'
131 def normpath(path):
131 def normpath(path):
132 return pconvert(os.path.normpath(path))
132 return pconvert(os.path.normpath(path))
133
133
134 normcase = os.path.normcase
134 encodinglower = None
135 encodingupper = None
136
137 def normcase(path):
138 return encodingupper(path)
135
139
136 def realpath(path):
140 def realpath(path):
137 '''
141 '''
General Comments 0
You need to be logged in to leave comments. Login now