##// 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 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.path.normcase os.makedirs
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(path, self._root))
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 if path in self.audited:
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 p2 = os.path.join(d, b.upper())
597 if path == p2:
598 p2 = os.path.join(d, b.lower())
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.lower().startswith(root.lower()):
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.normcase(os.path.normpath(root))
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.lower() == lpart:
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, lpart)
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