merge with stable
authorMatt Mackall <mpm@selenic.com>
Fri, 16 Dec 2011 19:05:59 -0600
changeset 15674 7b7f03502b5a
parent 15660 c7b0bedbb07a (current diff)
parent 15673 d550168f11ce (diff)
child 15692 65d1c54ca705
merge with stable
hgext/largefiles/overrides.py
hgext/largefiles/reposetup.py
hgext/largefiles/uisetup.py
mercurial/merge.py
mercurial/scmutil.py
mercurial/util.py
mercurial/windows.py
--- a/hgext/largefiles/lfcommands.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/hgext/largefiles/lfcommands.py	Fri Dec 16 19:05:59 2011 -0600
@@ -446,7 +446,11 @@
             os.chmod(abslfile, mode)
             ret = 1
     else:
-        if os.path.exists(abslfile):
+        # Remove lfiles for which the standin is deleted, unless the
+        # lfile is added to the repository again. This happens when a
+        # largefile is converted back to a normal file: the standin
+        # disappears, but a new (normal) file appears as the lfile.
+        if os.path.exists(abslfile) and lfile not in repo[None]:
             os.unlink(abslfile)
             ret = -1
     state = repo.dirstate[lfutil.standin(lfile)]
--- a/hgext/largefiles/overrides.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/hgext/largefiles/overrides.py	Fri Dec 16 19:05:59 2011 -0600
@@ -242,6 +242,90 @@
         wlock.release()
     return orig(ui, repo, *pats, **opts)
 
+# Before starting the manifest merge, merge.updates will call
+# _checkunknown to check if there are any files in the merged-in
+# changeset that collide with unknown files in the working copy.
+#
+# The largefiles are seen as unknown, so this prevents us from merging
+# in a file 'foo' if we already have a largefile with the same name.
+#
+# The overridden function filters the unknown files by removing any
+# largefiles. This makes the merge proceed and we can then handle this
+# case further in the overridden manifestmerge function below.
+def override_checkunknown(origfn, wctx, mctx, folding):
+    origunknown = wctx.unknown()
+    wctx._unknown = filter(lambda f: lfutil.standin(f) not in wctx, origunknown)
+    try:
+        return origfn(wctx, mctx, folding)
+    finally:
+        wctx._unknown = origunknown
+
+# The manifest merge handles conflicts on the manifest level. We want
+# to handle changes in largefile-ness of files at this level too.
+#
+# The strategy is to run the original manifestmerge and then process
+# the action list it outputs. There are two cases we need to deal with:
+#
+# 1. Normal file in p1, largefile in p2. Here the largefile is
+#    detected via its standin file, which will enter the working copy
+#    with a "get" action. It is not "merge" since the standin is all
+#    Mercurial is concerned with at this level -- the link to the
+#    existing normal file is not relevant here.
+#
+# 2. Largefile in p1, normal file in p2. Here we get a "merge" action
+#    since the largefile will be present in the working copy and
+#    different from the normal file in p2. Mercurial therefore
+#    triggers a merge action.
+#
+# In both cases, we prompt the user and emit new actions to either
+# remove the standin (if the normal file was kept) or to remove the
+# normal file and get the standin (if the largefile was kept). The
+# default prompt answer is to use the largefile version since it was
+# presumably changed on purpose.
+#
+# Finally, the merge.applyupdates function will then take care of
+# writing the files into the working copy and lfcommands.updatelfiles
+# will update the largefiles.
+def override_manifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
+    actions = origfn(repo, p1, p2, pa, overwrite, partial)
+    processed = []
+
+    for action in actions:
+        if overwrite:
+            processed.append(action)
+            continue
+        f, m = action[:2]
+
+        choices = (_('&Largefile'), _('&Normal file'))
+        if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
+            # Case 1: normal file in the working copy, largefile in
+            # the second parent
+            lfile = lfutil.splitstandin(f)
+            standin = f
+            msg = _('%s has been turned into a largefile\n'
+                    'use (l)argefile or keep as (n)ormal file?') % lfile
+            if repo.ui.promptchoice(msg, choices, 0) == 0:
+                processed.append((lfile, "r"))
+                processed.append((standin, "g", p2.flags(standin)))
+            else:
+                processed.append((standin, "r"))
+        elif m == "m" and lfutil.standin(f) in p1 and f in p2:
+            # Case 2: largefile in the working copy, normal file in
+            # the second parent
+            standin = lfutil.standin(f)
+            lfile = f
+            msg = _('%s has been turned into a normal file\n'
+                    'keep as (l)argefile or use (n)ormal file?') % lfile
+            if repo.ui.promptchoice(msg, choices, 0) == 0:
+                processed.append((lfile, "r"))
+            else:
+                processed.append((standin, "r"))
+                processed.append((lfile, "g", p2.flags(lfile)))
+        else:
+            processed.append(action)
+
+    return processed
+
 # Override filemerge to prompt the user about how they wish to merge
 # largefiles. This will handle identical edits, and copy/rename +
 # edit without prompting the user.
--- a/hgext/largefiles/reposetup.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/hgext/largefiles/reposetup.py	Fri Dec 16 19:05:59 2011 -0600
@@ -215,9 +215,18 @@
                             continue
                         if lfile not in lfdirstate:
                             removed.append(lfile)
-                    # Handle unknown and ignored differently
-                    lfiles = (modified, added, removed, missing, [], [], clean)
+
+                    # Filter result lists
                     result = list(result)
+
+                    # Largefiles are not really removed when they're
+                    # still in the normal dirstate. Likewise, normal
+                    # files are not really removed if it's still in
+                    # lfdirstate. This happens in merges where files
+                    # change type.
+                    removed = [f for f in removed if f not in repo.dirstate]
+                    result[2] = [f for f in result[2] if f not in lfdirstate]
+
                     # Unknown files
                     unknown = set(unknown).difference(ignored)
                     result[4] = [f for f in unknown
@@ -230,6 +239,7 @@
                     normals = [[fn for fn in filelist
                                 if not lfutil.isstandin(fn)]
                                for filelist in result]
+                    lfiles = (modified, added, removed, missing, [], [], clean)
                     result = [sorted(list1 + list2)
                               for (list1, list2) in zip(normals, lfiles)]
                 else:
--- a/hgext/largefiles/uisetup.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/hgext/largefiles/uisetup.py	Fri Dec 16 19:05:59 2011 -0600
@@ -9,7 +9,7 @@
 '''setup for largefiles extension: uisetup'''
 
 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
-    httprepo, localrepo, sshrepo, sshserver, wireproto
+    httprepo, localrepo, merge, sshrepo, sshserver, wireproto
 from mercurial.i18n import _
 from mercurial.hgweb import hgweb_mod, protocol
 
@@ -63,6 +63,10 @@
                                    overrides.override_update)
     entry = extensions.wrapcommand(commands.table, 'pull',
                                    overrides.override_pull)
+    entry = extensions.wrapfunction(merge, '_checkunknown',
+                                    overrides.override_checkunknown)
+    entry = extensions.wrapfunction(merge, 'manifestmerge',
+                                    overrides.override_manifestmerge)
     entry = extensions.wrapfunction(filemerge, 'filemerge',
                                     overrides.override_filemerge)
     entry = extensions.wrapfunction(cmdutil, 'copy',
--- a/hgext/progress.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/hgext/progress.py	Fri Dec 16 19:05:59 2011 -0600
@@ -271,17 +271,21 @@
     class progressui(ui.__class__):
         _progbar = None
 
+        def _quiet(self):
+            return self.debugflag or self.quiet
+
         def progress(self, *args, **opts):
-            self._progbar.progress(*args, **opts)
+            if not self._quiet():
+                self._progbar.progress(*args, **opts)
             return super(progressui, self).progress(*args, **opts)
 
         def write(self, *args, **opts):
-            if self._progbar.printed:
+            if not self._quiet() and self._progbar.printed:
                 self._progbar.clear()
             return super(progressui, self).write(*args, **opts)
 
         def write_err(self, *args, **opts):
-            if self._progbar.printed:
+            if not self._quiet() and self._progbar.printed:
                 self._progbar.clear()
             return super(progressui, self).write_err(*args, **opts)
 
--- a/hgext/win32mbcs.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/hgext/win32mbcs.py	Fri Dec 16 19:05:59 2011 -0600
@@ -127,7 +127,7 @@
 # NOTE: os.path.dirname() and os.path.basename() are safe because
 #       they use result of os.path.split()
 funcs = '''os.path.join os.path.split os.path.splitext
- os.path.splitunc os.path.normpath os.path.normcase os.makedirs
+ os.path.splitunc os.path.normpath os.makedirs
  mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
  mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
  mercurial.util.checkwinfilename mercurial.util.checkosfilename'''
--- a/mercurial/changelog.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/changelog.py	Fri Dec 16 19:05:59 2011 -0600
@@ -24,9 +24,20 @@
     return text.replace('\0', '\\0')
 
 def decodeextra(text):
+    """
+    >>> decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'}))
+    {'foo': 'bar', 'baz': '\\x002'}
+    >>> decodeextra(encodeextra({'foo': 'bar', 'baz': chr(92) + chr(0) + '2'}))
+    {'foo': 'bar', 'baz': '\\\\\\x002'}
+    """
     extra = {}
     for l in text.split('\0'):
         if l:
+            if '\\0' in l:
+                # fix up \0 without getting into trouble with \\0
+                l = l.replace('\\\\', '\\\\\n')
+                l = l.replace('\\0', '\0')
+                l = l.replace('\n', '')
             k, v = l.decode('string_escape').split(':', 1)
             extra[k] = v
     return extra
--- a/mercurial/dirstate.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/dirstate.py	Fri Dec 16 19:05:59 2011 -0600
@@ -65,10 +65,15 @@
         return self._copymap
 
     @propertycache
+    def _normroot(self):
+        return util.normcase(self._root)
+
+    @propertycache
     def _foldmap(self):
         f = {}
         for name in self._map:
             f[util.normcase(name)] = name
+        f['.'] = '.' # prevents useless util.fspath() invocation
         return f
 
     @propertycache
@@ -383,7 +388,7 @@
                 folded = path
             else:
                 folded = self._foldmap.setdefault(normed,
-                                util.fspath(path, self._root))
+                                util.fspath(normed, self._normroot))
         return folded
 
     def normalize(self, path, isknown=False):
--- a/mercurial/encoding.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/encoding.py	Fri Dec 16 19:05:59 2011 -0600
@@ -171,3 +171,22 @@
         return lu.encode(encoding)
     except UnicodeError:
         return s.lower() # we don't know how to fold this except in ASCII
+    except LookupError, k:
+        raise error.Abort(k, hint="please check your locale settings")
+
+def upper(s):
+    "best-effort encoding-aware case-folding of local string s"
+    try:
+        if isinstance(s, localstr):
+            u = s._utf8.decode("utf-8")
+        else:
+            u = s.decode(encoding, encodingmode)
+
+        uu = u.upper()
+        if u == uu:
+            return s # preserve localstring
+        return uu.encode(encoding)
+    except UnicodeError:
+        return s.upper() # we don't know how to fold this except in ASCII
+    except LookupError, k:
+        raise error.Abort(k, hint="please check your locale settings")
--- a/mercurial/merge.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/merge.py	Fri Dec 16 19:05:59 2011 -0600
@@ -96,7 +96,7 @@
             raise util.Abort(_("untracked file in working directory differs"
                                " from file in requested revision: '%s'") % fn)
 
-def _checkcollision(mctx):
+def _checkcollision(mctx, wctx):
     "check for case folding collisions in the destination context"
     folded = {}
     for fn in mctx:
@@ -106,6 +106,14 @@
                              % (fn, folded[fold]))
         folded[fold] = fn
 
+    if wctx:
+        for fn in wctx:
+            fold = util.normcase(fn)
+            mfn = folded.get(fold, None)
+            if mfn and (mfn != fn):
+                raise util.Abort(_("case-folding collision between %s and %s")
+                                 % (mfn, fn))
+
 def _forgetremoved(wctx, mctx, branchmerge):
     """
     Forget removed files
@@ -551,7 +559,7 @@
         if not force:
             _checkunknown(wc, p2, folding)
         if folding:
-            _checkcollision(p2)
+            _checkcollision(p2, branchmerge and p1)
         action += _forgetremoved(wc, p2, branchmerge)
         action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
 
--- a/mercurial/posix.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/posix.py	Fri Dec 16 19:05:59 2011 -0600
@@ -164,6 +164,9 @@
     st2 = os.lstat(fpath2)
     return st1.st_dev == st2.st_dev
 
+encodinglower = None
+encodingupper = None
+
 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
 def normcase(path):
     return path.lower()
--- a/mercurial/scmutil.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/scmutil.py	Fri Dec 16 19:05:59 2011 -0600
@@ -76,18 +76,22 @@
         self.auditeddir = set()
         self.root = root
         self.callback = callback
+        if os.path.lexists(root) and not util.checkcase(root):
+            self.normcase = util.normcase
+        else:
+            self.normcase = lambda x: x
 
     def __call__(self, path):
         '''Check the relative path.
         path may contain a pattern (e.g. foodir/**.txt)'''
 
-        if path in self.audited:
+        normpath = self.normcase(path)
+        if normpath in self.audited:
             return
         # AIX ignores "/" at end of path, others raise EISDIR.
         if util.endswithsep(path):
             raise util.Abort(_("path ends in directory separator: %s") % path)
-        normpath = os.path.normcase(path)
-        parts = util.splitpath(normpath)
+        parts = util.splitpath(path)
         if (os.path.splitdrive(path)[0]
             or parts[0].lower() in ('.hg', '.hg.', '')
             or os.pardir in parts):
@@ -101,11 +105,16 @@
                     raise util.Abort(_("path '%s' is inside nested repo %r")
                                      % (path, base))
 
+        normparts = util.splitpath(normpath)
+        assert len(parts) == len(normparts)
+
         parts.pop()
+        normparts.pop()
         prefixes = []
         while parts:
             prefix = os.sep.join(parts)
-            if prefix in self.auditeddir:
+            normprefix = os.sep.join(normparts)
+            if normprefix in self.auditeddir:
                 break
             curpath = os.path.join(self.root, prefix)
             try:
@@ -125,10 +134,11 @@
                     if not self.callback or not self.callback(curpath):
                         raise util.Abort(_("path '%s' is inside nested repo %r") %
                                          (path, prefix))
-            prefixes.append(prefix)
+            prefixes.append(normprefix)
             parts.pop()
+            normparts.pop()
 
-        self.audited.add(path)
+        self.audited.add(normpath)
         # only add prefixes to the cache after checking everything: we don't
         # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
         self.auditeddir.update(prefixes)
--- a/mercurial/util.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/util.py	Fri Dec 16 19:05:59 2011 -0600
@@ -24,6 +24,9 @@
 else:
     import posix as platform
 
+platform.encodinglower = encoding.lower
+platform.encodingupper = encoding.upper
+
 cachestat = platform.cachestat
 checkexec = platform.checkexec
 checklink = platform.checklink
@@ -593,9 +596,12 @@
     """
     s1 = os.stat(path)
     d, b = os.path.split(path)
-    p2 = os.path.join(d, b.upper())
-    if path == p2:
-        p2 = os.path.join(d, b.lower())
+    b2 = b.upper()
+    if b == b2:
+        b2 = b.lower()
+        if b == b2:
+            return True # no evidence against case sensitivity
+    p2 = os.path.join(d, b2)
     try:
         s2 = os.stat(p2)
         if s2 == s1:
@@ -611,9 +617,11 @@
     The name is either relative to root, or it is an absolute path starting
     with root. Note that this function is unnecessary, and should not be
     called, for case-sensitive filesystems (simply because it's expensive).
+
+    Both name and root should be normcase-ed.
     '''
     # If name is absolute, make it relative
-    if name.lower().startswith(root.lower()):
+    if name.startswith(root):
         l = len(root)
         if name[l] == os.sep or name[l] == os.altsep:
             l = l + 1
@@ -628,7 +636,7 @@
     # Protect backslashes. This gets silly very quickly.
     seps.replace('\\','\\\\')
     pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
-    dir = os.path.normcase(os.path.normpath(root))
+    dir = os.path.normpath(root)
     result = []
     for part, sep in pattern.findall(name):
         if sep:
@@ -639,16 +647,15 @@
             _fspathcache[dir] = os.listdir(dir)
         contents = _fspathcache[dir]
 
-        lpart = part.lower()
         lenp = len(part)
         for n in contents:
-            if lenp == len(n) and n.lower() == lpart:
+            if lenp == len(n) and normcase(n) == part:
                 result.append(n)
                 break
         else:
             # Cannot happen, as the file exists!
             result.append(part)
-        dir = os.path.join(dir, lpart)
+        dir = os.path.join(dir, part)
 
     return ''.join(result)
 
--- a/mercurial/windows.py	Thu Dec 15 16:50:21 2011 -0600
+++ b/mercurial/windows.py	Fri Dec 16 19:05:59 2011 -0600
@@ -131,7 +131,11 @@
 def normpath(path):
     return pconvert(os.path.normpath(path))
 
-normcase = os.path.normcase
+encodinglower = None
+encodingupper = None
+
+def normcase(path):
+    return encodingupper(path)
 
 def realpath(path):
     '''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-casecollision-merge.t	Fri Dec 16 19:05:59 2011 -0600
@@ -0,0 +1,109 @@
+run only on case-insensitive filesystems
+
+  $ "$TESTDIR/hghave" icasefs || exit 80
+
+################################
+test for branch merging
+################################
+
+  $ hg init repo1
+  $ cd repo1
+
+create base revision
+
+  $ echo base > base.txt
+  $ hg add base.txt
+  $ hg commit -m 'base'
+
+add same file in different case on both heads
+
+  $ echo a > a.txt
+  $ hg add a.txt
+  $ hg commit -m 'add a.txt'
+
+  $ hg update 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+  $ echo A > A.TXT
+  $ hg add A.TXT
+  $ hg commit -m 'add A.TXT'
+  created new head
+
+merge another, and fail with case-folding collision
+
+  $ hg merge
+  abort: case-folding collision between a.txt and A.TXT
+  [255]
+
+check clean-ness of working directory
+
+  $ hg status
+  $ hg parents --template '{rev}\n'
+  2
+  $ cd ..
+
+################################
+test for linear updates
+################################
+
+  $ hg init repo2
+  $ cd repo2
+
+create base revision (rev:0)
+
+  $ hg import --bypass --exact - <<EOF
+  > # HG changeset patch
+  > # User null
+  > # Date 1 0
+  > # Node ID e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
+  > # Parent  0000000000000000000000000000000000000000
+  > add a
+  > 
+  > diff --git a/a b/a
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/a
+  > @@ -0,0 +1,3 @@
+  > +this is line 1
+  > +this is line 2
+  > +this is line 3
+  > EOF
+  applying patch from stdin
+
+create rename revision (rev:1)
+
+  $ hg import --bypass --exact - <<EOF
+  > # HG changeset patch
+  > # User null
+  > # Date 1 0
+  > # Node ID 9dca9f19bb91851bc693544b598b0740629edfad
+  > # Parent  e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
+  > rename a to A
+  > 
+  > diff --git a/a b/A
+  > rename from a
+  > rename to A
+  > EOF
+  applying patch from stdin
+
+update to base revision, and modify 'a'
+
+  $ hg update 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 'this is added line' >> a
+
+update to current tip linearly
+
+  $ hg update 1
+  merging a and A to A
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+
+check status and contents of file
+
+  $ hg status -A
+  M A
+  $ cat A
+  this is line 1
+  this is line 2
+  this is line 3
+  this is added line
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-issue3084.t	Fri Dec 16 19:05:59 2011 -0600
@@ -0,0 +1,108 @@
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "largefiles =" >> $HGRCPATH
+
+Create the repository outside $HOME since largefiles write to
+$HOME/.cache/largefiles.
+
+  $ hg init test
+  $ cd test
+  $ echo "root" > root
+  $ hg add root
+  $ hg commit -m "Root commit"
+
+  $ echo "large" > foo
+  $ hg add --large foo
+  $ hg commit -m "Add foo as a largefile"
+
+  $ hg update -r 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  getting changed largefiles
+  0 largefiles updated, 1 removed
+
+  $ echo "normal" > foo
+  $ hg add foo
+  $ hg commit -m "Add foo as normal file"
+  created new head
+
+Normal file in the working copy, keeping the normal version:
+
+  $ echo "n" | hg merge --config ui.interactive=Yes
+  foo has been turned into a largefile
+  use (l)argefile or keep as (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg status
+  $ cat foo
+  normal
+
+Normal file in the working copy, keeping the largefile version:
+
+  $ hg update -q -C
+  $ echo "l" | hg merge --config ui.interactive=Yes
+  foo has been turned into a largefile
+  use (l)argefile or keep as (n)ormal file? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  getting changed largefiles
+  1 largefiles updated, 0 removed
+
+  $ hg status
+  M foo
+
+  $ hg diff --nodates
+  diff -r fa129ab6b5a7 .hglf/foo
+  --- /dev/null
+  +++ b/.hglf/foo
+  @@ -0,0 +1,1 @@
+  +7f7097b041ccf68cc5561e9600da4655d21c6d18
+  diff -r fa129ab6b5a7 foo
+  --- a/foo
+  +++ /dev/null
+  @@ -1,1 +0,0 @@
+  -normal
+
+  $ cat foo
+  large
+
+Largefile in the working copy, keeping the normal version:
+
+  $ hg update -q -C -r 1
+  $ echo "n" | hg merge --config ui.interactive=Yes
+  foo has been turned into a normal file
+  keep as (l)argefile or use (n)ormal file? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  getting changed largefiles
+  0 largefiles updated, 0 removed
+
+  $ hg status
+  M foo
+
+  $ hg diff --nodates
+  diff -r ff521236428a .hglf/foo
+  --- a/.hglf/foo
+  +++ /dev/null
+  @@ -1,1 +0,0 @@
+  -7f7097b041ccf68cc5561e9600da4655d21c6d18
+  diff -r ff521236428a foo
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,1 @@
+  +normal
+
+  $ cat foo
+  normal
+
+Largefile in the working copy, keeping the largefile version:
+
+  $ hg update -q -C -r 1
+  $ echo "l" | hg merge --config ui.interactive=Yes
+  foo has been turned into a normal file
+  keep as (l)argefile or use (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  getting changed largefiles
+  1 largefiles updated, 0 removed
+
+  $ hg status
+
+  $ cat foo
+  large