comparison mercurial/commit.py @ 48716:f1eb77dceb36

narrow: allow merging non-conflicting change outside of the narrow spec We use the mergestate to carry information about these merge action and reprocess them at commit time to apply the necessary update. The dirstate itself is never affected and remains "pure", with content only in the narrow-spec. This file involved in such merge are therefor not listed in `hg status`. The current testing is based on a modification of the previous testing, that refused to do such merges. As a result it is a bit simple and more extensive code and testing testing will have to be introduced later. I am planning to do this extra testing, soon. In addition, this only works for flat manifest. Support for tree manifest will need more work. I am not currently planning to do this work. Differential Revision: https://phab.mercurial-scm.org/D12119
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 03 Feb 2022 18:14:11 +0100
parents 5b9de38a0356
children 18e69f224e4b
comparison
equal deleted inserted replaced
48715:7d073df49a54 48716:f1eb77dceb36
132 ms = mergestate.mergestate.read(repo) 132 ms = mergestate.mergestate.read(repo)
133 salvaged = _get_salvaged(repo, ms, ctx) 133 salvaged = _get_salvaged(repo, ms, ctx)
134 for s in salvaged: 134 for s in salvaged:
135 files.mark_salvaged(s) 135 files.mark_salvaged(s)
136 136
137 if ctx.manifestnode(): 137 narrow_files = {}
138 if not ctx.repo().narrowmatch().always():
139 for f, e in ms.allextras().items():
140 action = e.get(b'outside-narrow-merge-action')
141 if action is not None:
142 narrow_files[f] = action
143 if ctx.manifestnode() and not narrow_files:
138 # reuse an existing manifest revision 144 # reuse an existing manifest revision
139 repo.ui.debug(b'reusing known manifest\n') 145 repo.ui.debug(b'reusing known manifest\n')
140 mn = ctx.manifestnode() 146 mn = ctx.manifestnode()
141 files.update_touched(ctx.files()) 147 files.update_touched(ctx.files())
142 if writechangesetcopy: 148 if writechangesetcopy:
143 files.update_added(ctx.filesadded()) 149 files.update_added(ctx.filesadded())
144 files.update_removed(ctx.filesremoved()) 150 files.update_removed(ctx.filesremoved())
145 elif not ctx.files(): 151 elif not ctx.files() and not narrow_files:
146 repo.ui.debug(b'reusing manifest from p1 (no file change)\n') 152 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
147 mn = p1.manifestnode() 153 mn = p1.manifestnode()
148 else: 154 else:
149 mn = _process_files(tr, ctx, ms, files, error=error) 155 mn = _process_files(tr, ctx, ms, files, narrow_files, error=error)
150 156
151 if origctx and origctx.manifestnode() == mn: 157 if origctx and origctx.manifestnode() == mn:
152 origfiles = origctx.files() 158 origfiles = origctx.files()
153 assert files.touched.issubset(origfiles) 159 assert files.touched.issubset(origfiles)
154 files.update_touched(origfiles) 160 files.update_touched(origfiles)
175 if fname in ctx: 181 if fname in ctx:
176 salvaged.append(fname) 182 salvaged.append(fname)
177 return salvaged 183 return salvaged
178 184
179 185
180 def _process_files(tr, ctx, ms, files, error=False): 186 def _process_files(tr, ctx, ms, files, narrow_files=None, error=False):
181 repo = ctx.repo() 187 repo = ctx.repo()
182 p1 = ctx.p1() 188 p1 = ctx.p1()
183 p2 = ctx.p2() 189 p2 = ctx.p2()
184 190
185 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo) 191 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
196 added = [] 202 added = []
197 removed = list(ctx.removed()) 203 removed = list(ctx.removed())
198 linkrev = len(repo) 204 linkrev = len(repo)
199 repo.ui.note(_(b"committing files:\n")) 205 repo.ui.note(_(b"committing files:\n"))
200 uipathfn = scmutil.getuipathfn(repo) 206 uipathfn = scmutil.getuipathfn(repo)
201 for f in sorted(ctx.modified() + ctx.added()): 207 all_files = ctx.modified() + ctx.added()
208 all_files.extend(narrow_files.keys())
209 all_files.sort()
210 for f in all_files:
202 repo.ui.note(uipathfn(f) + b"\n") 211 repo.ui.note(uipathfn(f) + b"\n")
212 if f in narrow_files:
213 narrow_action = narrow_files.get(f)
214 if narrow_action == mergestate.CHANGE_MODIFIED:
215 files.mark_touched(f)
216 added.append(f)
217 m[f] = m2[f]
218 flags = m2ctx.find(f)[1] or b''
219 m.setflag(f, flags)
220 else:
221 msg = _(b"corrupted mergestate, unknown narrow action: %b")
222 hint = _(b"restart the merge")
223 raise error.Abort(msg, hint=hint)
224 continue
203 try: 225 try:
204 fctx = ctx[f] 226 fctx = ctx[f]
205 if fctx is None: 227 if fctx is None:
206 removed.append(f) 228 removed.append(f)
207 else: 229 else:
237 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2)) 259 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
238 for f in removed: 260 for f in removed:
239 if not rf(f): 261 if not rf(f):
240 files.mark_removed(f) 262 files.mark_removed(f)
241 263
242 mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop) 264 mn = _commit_manifest(
265 tr,
266 linkrev,
267 ctx,
268 mctx,
269 m,
270 files.touched,
271 added,
272 drop,
273 bool(narrow_files),
274 )
243 275
244 return mn 276 return mn
245 277
246 278
247 def _filecommit( 279 def _filecommit(
407 else: 439 else:
408 fnode = fparent1 440 fnode = fparent1
409 return fnode, touched 441 return fnode, touched
410 442
411 443
412 def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop): 444 def _commit_manifest(
445 tr,
446 linkrev,
447 ctx,
448 mctx,
449 manifest,
450 files,
451 added,
452 drop,
453 has_some_narrow_action=False,
454 ):
413 """make a new manifest entry (or reuse a new one) 455 """make a new manifest entry (or reuse a new one)
414 456
415 given an initialised manifest context and precomputed list of 457 given an initialised manifest context and precomputed list of
416 - files: files affected by the commit 458 - files: files affected by the commit
417 - added: new entries in the manifest 459 - added: new entries in the manifest
449 # ignoring things outside of narrowspec in most cases. The 491 # ignoring things outside of narrowspec in most cases. The
450 # one case where we might have files outside the narrowspec 492 # one case where we might have files outside the narrowspec
451 # at this point is merges, and we already error out in the 493 # at this point is merges, and we already error out in the
452 # case where the merge has files outside of the narrowspec, 494 # case where the merge has files outside of the narrowspec,
453 # so this is safe. 495 # so this is safe.
496 if has_some_narrow_action:
497 match = None
498 else:
499 match = repo.narrowmatch()
454 mn = mctx.write( 500 mn = mctx.write(
455 tr, 501 tr,
456 linkrev, 502 linkrev,
457 p1.manifestnode(), 503 p1.manifestnode(),
458 p2.manifestnode(), 504 p2.manifestnode(),
459 added, 505 added,
460 drop, 506 drop,
461 match=repo.narrowmatch(), 507 match=match,
462 ) 508 )
463 else: 509 else:
464 repo.ui.debug( 510 repo.ui.debug(
465 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n' 511 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
466 ) 512 )