--- a/mercurial/commit.py Sun Jan 30 06:01:42 2022 +0100
+++ b/mercurial/commit.py Thu Feb 03 18:14:11 2022 +0100
@@ -134,7 +134,13 @@
for s in salvaged:
files.mark_salvaged(s)
- if ctx.manifestnode():
+ narrow_files = {}
+ if not ctx.repo().narrowmatch().always():
+ for f, e in ms.allextras().items():
+ action = e.get(b'outside-narrow-merge-action')
+ if action is not None:
+ narrow_files[f] = action
+ if ctx.manifestnode() and not narrow_files:
# reuse an existing manifest revision
repo.ui.debug(b'reusing known manifest\n')
mn = ctx.manifestnode()
@@ -142,11 +148,11 @@
if writechangesetcopy:
files.update_added(ctx.filesadded())
files.update_removed(ctx.filesremoved())
- elif not ctx.files():
+ elif not ctx.files() and not narrow_files:
repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
mn = p1.manifestnode()
else:
- mn = _process_files(tr, ctx, ms, files, error=error)
+ mn = _process_files(tr, ctx, ms, files, narrow_files, error=error)
if origctx and origctx.manifestnode() == mn:
origfiles = origctx.files()
@@ -177,7 +183,7 @@
return salvaged
-def _process_files(tr, ctx, ms, files, error=False):
+def _process_files(tr, ctx, ms, files, narrow_files=None, error=False):
repo = ctx.repo()
p1 = ctx.p1()
p2 = ctx.p2()
@@ -198,8 +204,24 @@
linkrev = len(repo)
repo.ui.note(_(b"committing files:\n"))
uipathfn = scmutil.getuipathfn(repo)
- for f in sorted(ctx.modified() + ctx.added()):
+ all_files = ctx.modified() + ctx.added()
+ all_files.extend(narrow_files.keys())
+ all_files.sort()
+ for f in all_files:
repo.ui.note(uipathfn(f) + b"\n")
+ if f in narrow_files:
+ narrow_action = narrow_files.get(f)
+ if narrow_action == mergestate.CHANGE_MODIFIED:
+ files.mark_touched(f)
+ added.append(f)
+ m[f] = m2[f]
+ flags = m2ctx.find(f)[1] or b''
+ m.setflag(f, flags)
+ else:
+ msg = _(b"corrupted mergestate, unknown narrow action: %b")
+ hint = _(b"restart the merge")
+ raise error.Abort(msg, hint=hint)
+ continue
try:
fctx = ctx[f]
if fctx is None:
@@ -239,7 +261,17 @@
if not rf(f):
files.mark_removed(f)
- mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop)
+ mn = _commit_manifest(
+ tr,
+ linkrev,
+ ctx,
+ mctx,
+ m,
+ files.touched,
+ added,
+ drop,
+ bool(narrow_files),
+ )
return mn
@@ -409,7 +441,17 @@
return fnode, touched
-def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop):
+def _commit_manifest(
+ tr,
+ linkrev,
+ ctx,
+ mctx,
+ manifest,
+ files,
+ added,
+ drop,
+ has_some_narrow_action=False,
+):
"""make a new manifest entry (or reuse a new one)
given an initialised manifest context and precomputed list of
@@ -451,6 +493,10 @@
# at this point is merges, and we already error out in the
# case where the merge has files outside of the narrowspec,
# so this is safe.
+ if has_some_narrow_action:
+ match = None
+ else:
+ match = repo.narrowmatch()
mn = mctx.write(
tr,
linkrev,
@@ -458,7 +504,7 @@
p2.manifestnode(),
added,
drop,
- match=repo.narrowmatch(),
+ match=match,
)
else:
repo.ui.debug(
--- a/mercurial/merge.py Sun Jan 30 06:01:42 2022 +0100
+++ b/mercurial/merge.py Thu Feb 03 18:14:11 2022 +0100
@@ -518,13 +518,23 @@
mresult.removefile(f) # just updating, ignore changes outside clone
elif action[0].no_op:
mresult.removefile(f) # merge does not affect file
- elif action[0].narrow_safe: # TODO: handle these cases
- msg = _(
- b'merge affects file \'%s\' outside narrow, '
- b'which is not yet supported'
- )
- hint = _(b'merging in the other direction may work')
- raise error.Abort(msg % f, hint=hint)
+ elif action[0].narrow_safe:
+ if (
+ not f.endswith(b'/')
+ and action[0].changes == mergestatemod.CHANGE_MODIFIED
+ ):
+ mresult.removefile(f) # merge won't affect on-disk files
+
+ mresult.addcommitinfo(
+ f, b'outside-narrow-merge-action', action[0].changes
+ )
+ else: # TODO: handle the tree case
+ msg = _(
+ b'merge affects file \'%s\' outside narrow, '
+ b'which is not yet supported'
+ )
+ hint = _(b'merging in the other direction may work')
+ raise error.Abort(msg % f, hint=hint)
else:
msg = _(b'conflict in file \'%s\' is outside narrow clone')
raise error.StateError(msg % f)
--- a/tests/test-narrow-merge.t Sun Jan 30 06:01:42 2022 +0100
+++ b/tests/test-narrow-merge.t Thu Feb 03 18:14:11 2022 +0100
@@ -83,12 +83,67 @@
TODO: Can merge non-conflicting changes outside narrow spec
$ hg update -q 'desc("modify inside/f1")'
+
+#if flat
+
$ hg merge 'desc("modify outside/f1")'
- abort: merge affects file 'outside/f1' outside narrow, which is not yet supported (flat !)
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+
+status should be clean
+
+ $ hg status
+ ? inside/f1.orig
+
+file out of the spec should still not be in the dirstate at all
+
+ $ hg debugdirstate | grep outside/f1
+ [1]
+
+Commit that merge
+
+ $ hg ci -m 'merge from outside to inside'
+
+status should be clean
+
+ $ hg status
+ ? inside/f1.orig
+
+file out of the spec should not be in the mergestate anymore
+
+ $ hg debugmergestate | grep outside/f1
+ [1]
+
+file out of the spec should still not be in the dirstate at all
+
+ $ hg debugdirstate | grep outside/f1
+ [1]
+
+The filenode used should come from p2
+
+ $ hg manifest --debug --rev . | grep outside/f1
+ 83cd11431a3b2aff8a3995e5f27bcf33cdb5be98 644 outside/f1
+ $ hg manifest --debug --rev 'p1(.)' | grep outside/f1
+ c6b956c48be2cd4fa94be16002aba311143806fa 644 outside/f1
+ $ hg manifest --debug --rev 'p2(.)' | grep outside/f1
+ 83cd11431a3b2aff8a3995e5f27bcf33cdb5be98 644 outside/f1
+
+
+remove the commit to get in the previous situation again
+
+ $ hg debugstrip -r .
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/48eb25338b19-a1bb8350-backup.hg
+
+#else
+
+ $ hg merge 'desc("modify outside/f1")'
abort: merge affects file 'outside/' outside narrow, which is not yet supported (tree !)
(merging in the other direction may work)
[255]
+#endif
+
$ hg update -q 'desc("modify outside/f1")'
$ hg merge 'desc("modify inside/f1")'
1 files updated, 0 files merged, 0 files removed, 0 files unresolved