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 | 446 | os.chmod(abslfile, mode) |
|
447 | 447 | ret = 1 |
|
448 | 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 | 454 | os.unlink(abslfile) |
|
451 | 455 | ret = -1 |
|
452 | 456 | state = repo.dirstate[lfutil.standin(lfile)] |
@@ -242,6 +242,90 b' def override_update(orig, ui, repo, *pat' | |||
|
242 | 242 | wlock.release() |
|
243 | 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 | 329 | # Override filemerge to prompt the user about how they wish to merge |
|
246 | 330 | # largefiles. This will handle identical edits, and copy/rename + |
|
247 | 331 | # edit without prompting the user. |
@@ -215,9 +215,18 b' def reposetup(ui, repo):' | |||
|
215 | 215 | continue |
|
216 | 216 | if lfile not in lfdirstate: |
|
217 | 217 | removed.append(lfile) |
|
218 | # Handle unknown and ignored differently | |
|
219 | lfiles = (modified, added, removed, missing, [], [], clean) | |
|
218 | ||
|
219 | # Filter result lists | |
|
220 | 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 | 230 | # Unknown files |
|
222 | 231 | unknown = set(unknown).difference(ignored) |
|
223 | 232 | result[4] = [f for f in unknown |
@@ -230,6 +239,7 b' def reposetup(ui, repo):' | |||
|
230 | 239 | normals = [[fn for fn in filelist |
|
231 | 240 | if not lfutil.isstandin(fn)] |
|
232 | 241 | for filelist in result] |
|
242 | lfiles = (modified, added, removed, missing, [], [], clean) | |
|
233 | 243 | result = [sorted(list1 + list2) |
|
234 | 244 | for (list1, list2) in zip(normals, lfiles)] |
|
235 | 245 | else: |
@@ -9,7 +9,7 b'' | |||
|
9 | 9 | '''setup for largefiles extension: uisetup''' |
|
10 | 10 | |
|
11 | 11 | from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \ |
|
12 | httprepo, localrepo, sshrepo, sshserver, wireproto | |
|
12 | httprepo, localrepo, merge, sshrepo, sshserver, wireproto | |
|
13 | 13 | from mercurial.i18n import _ |
|
14 | 14 | from mercurial.hgweb import hgweb_mod, protocol |
|
15 | 15 | |
@@ -63,6 +63,10 b' def uisetup(ui):' | |||
|
63 | 63 | overrides.override_update) |
|
64 | 64 | entry = extensions.wrapcommand(commands.table, 'pull', |
|
65 | 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 | 70 | entry = extensions.wrapfunction(filemerge, 'filemerge', |
|
67 | 71 | overrides.override_filemerge) |
|
68 | 72 | entry = extensions.wrapfunction(cmdutil, 'copy', |
@@ -271,17 +271,21 b' def uisetup(ui):' | |||
|
271 | 271 | class progressui(ui.__class__): |
|
272 | 272 | _progbar = None |
|
273 | 273 | |
|
274 | def _quiet(self): | |
|
275 | return self.debugflag or self.quiet | |
|
276 | ||
|
274 | 277 | def progress(self, *args, **opts): |
|
275 | self._progbar.progress(*args, **opts) | |
|
278 | if not self._quiet(): | |
|
279 | self._progbar.progress(*args, **opts) | |
|
276 | 280 | return super(progressui, self).progress(*args, **opts) |
|
277 | 281 | |
|
278 | 282 | def write(self, *args, **opts): |
|
279 | if self._progbar.printed: | |
|
283 | if not self._quiet() and self._progbar.printed: | |
|
280 | 284 | self._progbar.clear() |
|
281 | 285 | return super(progressui, self).write(*args, **opts) |
|
282 | 286 | |
|
283 | 287 | def write_err(self, *args, **opts): |
|
284 | if self._progbar.printed: | |
|
288 | if not self._quiet() and self._progbar.printed: | |
|
285 | 289 | self._progbar.clear() |
|
286 | 290 | return super(progressui, self).write_err(*args, **opts) |
|
287 | 291 |
@@ -127,7 +127,7 b' def wrapname(name, wrapper):' | |||
|
127 | 127 | # NOTE: os.path.dirname() and os.path.basename() are safe because |
|
128 | 128 | # they use result of os.path.split() |
|
129 | 129 | funcs = '''os.path.join os.path.split os.path.splitext |
|
130 |
os.path.splitunc os.path.normpath os. |
|
|
130 | os.path.splitunc os.path.normpath os.makedirs | |
|
131 | 131 | mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase |
|
132 | 132 | mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath |
|
133 | 133 | mercurial.util.checkwinfilename mercurial.util.checkosfilename''' |
@@ -24,9 +24,20 b' def _string_escape(text):' | |||
|
24 | 24 | return text.replace('\0', '\\0') |
|
25 | 25 | |
|
26 | 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 | 33 | extra = {} |
|
28 | 34 | for l in text.split('\0'): |
|
29 | 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 | 41 | k, v = l.decode('string_escape').split(':', 1) |
|
31 | 42 | extra[k] = v |
|
32 | 43 | return extra |
@@ -65,10 +65,15 b' class dirstate(object):' | |||
|
65 | 65 | return self._copymap |
|
66 | 66 | |
|
67 | 67 | @propertycache |
|
68 | def _normroot(self): | |
|
69 | return util.normcase(self._root) | |
|
70 | ||
|
71 | @propertycache | |
|
68 | 72 | def _foldmap(self): |
|
69 | 73 | f = {} |
|
70 | 74 | for name in self._map: |
|
71 | 75 | f[util.normcase(name)] = name |
|
76 | f['.'] = '.' # prevents useless util.fspath() invocation | |
|
72 | 77 | return f |
|
73 | 78 | |
|
74 | 79 | @propertycache |
@@ -383,7 +388,7 b' class dirstate(object):' | |||
|
383 | 388 | folded = path |
|
384 | 389 | else: |
|
385 | 390 | folded = self._foldmap.setdefault(normed, |
|
386 |
util.fspath( |
|
|
391 | util.fspath(normed, self._normroot)) | |
|
387 | 392 | return folded |
|
388 | 393 | |
|
389 | 394 | def normalize(self, path, isknown=False): |
@@ -171,3 +171,22 b' def lower(s):' | |||
|
171 | 171 | return lu.encode(encoding) |
|
172 | 172 | except UnicodeError: |
|
173 | 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 | 96 | raise util.Abort(_("untracked file in working directory differs" |
|
97 | 97 | " from file in requested revision: '%s'") % fn) |
|
98 | 98 | |
|
99 | def _checkcollision(mctx): | |
|
99 | def _checkcollision(mctx, wctx): | |
|
100 | 100 | "check for case folding collisions in the destination context" |
|
101 | 101 | folded = {} |
|
102 | 102 | for fn in mctx: |
@@ -106,6 +106,14 b' def _checkcollision(mctx):' | |||
|
106 | 106 | % (fn, folded[fold])) |
|
107 | 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 | 117 | def _forgetremoved(wctx, mctx, branchmerge): |
|
110 | 118 | """ |
|
111 | 119 | Forget removed files |
@@ -551,7 +559,7 b' def update(repo, node, branchmerge, forc' | |||
|
551 | 559 | if not force: |
|
552 | 560 | _checkunknown(wc, p2, folding) |
|
553 | 561 | if folding: |
|
554 | _checkcollision(p2) | |
|
562 | _checkcollision(p2, branchmerge and p1) | |
|
555 | 563 | action += _forgetremoved(wc, p2, branchmerge) |
|
556 | 564 | action += manifestmerge(repo, wc, p2, pa, overwrite, partial) |
|
557 | 565 |
@@ -164,6 +164,9 b' def samedevice(fpath1, fpath2):' | |||
|
164 | 164 | st2 = os.lstat(fpath2) |
|
165 | 165 | return st1.st_dev == st2.st_dev |
|
166 | 166 | |
|
167 | encodinglower = None | |
|
168 | encodingupper = None | |
|
169 | ||
|
167 | 170 | # os.path.normcase is a no-op, which doesn't help us on non-native filesystems |
|
168 | 171 | def normcase(path): |
|
169 | 172 | return path.lower() |
@@ -76,18 +76,22 b' class pathauditor(object):' | |||
|
76 | 76 | self.auditeddir = set() |
|
77 | 77 | self.root = root |
|
78 | 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 | 84 | def __call__(self, path): |
|
81 | 85 | '''Check the relative path. |
|
82 | 86 | path may contain a pattern (e.g. foodir/**.txt)''' |
|
83 | 87 | |
|
84 |
|
|
|
88 | normpath = self.normcase(path) | |
|
89 | if normpath in self.audited: | |
|
85 | 90 | return |
|
86 | 91 | # AIX ignores "/" at end of path, others raise EISDIR. |
|
87 | 92 | if util.endswithsep(path): |
|
88 | 93 | raise util.Abort(_("path ends in directory separator: %s") % path) |
|
89 | normpath = os.path.normcase(path) | |
|
90 | parts = util.splitpath(normpath) | |
|
94 | parts = util.splitpath(path) | |
|
91 | 95 | if (os.path.splitdrive(path)[0] |
|
92 | 96 | or parts[0].lower() in ('.hg', '.hg.', '') |
|
93 | 97 | or os.pardir in parts): |
@@ -101,11 +105,16 b' class pathauditor(object):' | |||
|
101 | 105 | raise util.Abort(_("path '%s' is inside nested repo %r") |
|
102 | 106 | % (path, base)) |
|
103 | 107 | |
|
108 | normparts = util.splitpath(normpath) | |
|
109 | assert len(parts) == len(normparts) | |
|
110 | ||
|
104 | 111 | parts.pop() |
|
112 | normparts.pop() | |
|
105 | 113 | prefixes = [] |
|
106 | 114 | while parts: |
|
107 | 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 | 118 | break |
|
110 | 119 | curpath = os.path.join(self.root, prefix) |
|
111 | 120 | try: |
@@ -125,10 +134,11 b' class pathauditor(object):' | |||
|
125 | 134 | if not self.callback or not self.callback(curpath): |
|
126 | 135 | raise util.Abort(_("path '%s' is inside nested repo %r") % |
|
127 | 136 | (path, prefix)) |
|
128 | prefixes.append(prefix) | |
|
137 | prefixes.append(normprefix) | |
|
129 | 138 | parts.pop() |
|
139 | normparts.pop() | |
|
130 | 140 | |
|
131 | self.audited.add(path) | |
|
141 | self.audited.add(normpath) | |
|
132 | 142 | # only add prefixes to the cache after checking everything: we don't |
|
133 | 143 | # want to add "foo/bar/baz" before checking if there's a "foo/.hg" |
|
134 | 144 | self.auditeddir.update(prefixes) |
@@ -24,6 +24,9 b" if os.name == 'nt':" | |||
|
24 | 24 | else: |
|
25 | 25 | import posix as platform |
|
26 | 26 | |
|
27 | platform.encodinglower = encoding.lower | |
|
28 | platform.encodingupper = encoding.upper | |
|
29 | ||
|
27 | 30 | cachestat = platform.cachestat |
|
28 | 31 | checkexec = platform.checkexec |
|
29 | 32 | checklink = platform.checklink |
@@ -593,9 +596,12 b' def checkcase(path):' | |||
|
593 | 596 | """ |
|
594 | 597 | s1 = os.stat(path) |
|
595 | 598 | d, b = os.path.split(path) |
|
596 |
|
|
|
597 |
if |
|
|
598 |
|
|
|
599 | b2 = b.upper() | |
|
600 | if b == b2: | |
|
601 | b2 = b.lower() | |
|
602 | if b == b2: | |
|
603 | return True # no evidence against case sensitivity | |
|
604 | p2 = os.path.join(d, b2) | |
|
599 | 605 | try: |
|
600 | 606 | s2 = os.stat(p2) |
|
601 | 607 | if s2 == s1: |
@@ -611,9 +617,11 b' def fspath(name, root):' | |||
|
611 | 617 | The name is either relative to root, or it is an absolute path starting |
|
612 | 618 | with root. Note that this function is unnecessary, and should not be |
|
613 | 619 | called, for case-sensitive filesystems (simply because it's expensive). |
|
620 | ||
|
621 | Both name and root should be normcase-ed. | |
|
614 | 622 | ''' |
|
615 | 623 | # If name is absolute, make it relative |
|
616 |
if name. |
|
|
624 | if name.startswith(root): | |
|
617 | 625 | l = len(root) |
|
618 | 626 | if name[l] == os.sep or name[l] == os.altsep: |
|
619 | 627 | l = l + 1 |
@@ -628,7 +636,7 b' def fspath(name, root):' | |||
|
628 | 636 | # Protect backslashes. This gets silly very quickly. |
|
629 | 637 | seps.replace('\\','\\\\') |
|
630 | 638 | pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps)) |
|
631 |
dir = os.path. |
|
|
639 | dir = os.path.normpath(root) | |
|
632 | 640 | result = [] |
|
633 | 641 | for part, sep in pattern.findall(name): |
|
634 | 642 | if sep: |
@@ -639,16 +647,15 b' def fspath(name, root):' | |||
|
639 | 647 | _fspathcache[dir] = os.listdir(dir) |
|
640 | 648 | contents = _fspathcache[dir] |
|
641 | 649 | |
|
642 | lpart = part.lower() | |
|
643 | 650 | lenp = len(part) |
|
644 | 651 | for n in contents: |
|
645 |
if lenp == len(n) and n |
|
|
652 | if lenp == len(n) and normcase(n) == part: | |
|
646 | 653 | result.append(n) |
|
647 | 654 | break |
|
648 | 655 | else: |
|
649 | 656 | # Cannot happen, as the file exists! |
|
650 | 657 | result.append(part) |
|
651 |
dir = os.path.join(dir, |
|
|
658 | dir = os.path.join(dir, part) | |
|
652 | 659 | |
|
653 | 660 | return ''.join(result) |
|
654 | 661 |
@@ -131,7 +131,11 b' def localpath(path):' | |||
|
131 | 131 | def normpath(path): |
|
132 | 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 | 140 | def realpath(path): |
|
137 | 141 | ''' |
General Comments 0
You need to be logged in to leave comments.
Login now