Mercurial > hg
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 ) |