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. |
|
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( |
|
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 |
|
|
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 |
|
|
599 | b2 = b.upper() | |
597 |
if |
|
600 | if b == b2: | |
598 |
|
|
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. |
|
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. |
|
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 |
|
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, |
|
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