Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/cmdutil.py @ 50085:28dfb2df4ab9
commit: use `dirstate.change_files` to scope the associated `addremove`
This was significantly more complicated than I expected, because multiple
extensions get in the way.
I introduced a context that lazily open the transaction and associated context
to work around these complication. See the inline documentation for details.
Introducing the wrapping transaction remove the need for dirstate-guard (one of
the ultimate goal of all this), and slightly affect the result of a `hg
rollback` after a `hg commit --addremove`. That last part is deemed fine. It
aligns the behavior with what happens after a failed `hg commit --addremove` and
nobody should be using `hg rollback` anyway.
The small output change in the test come from the different transaction timing
and fact the transaction now backup the dirstate before the addremove, which
might mean "no file to backup" when the repository starts from an empty state.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Wed, 15 Feb 2023 11:51:58 +0100 |
parents | a46dfc2b58a3 |
children | 6cdcab3ae3fa |
comparison
equal
deleted
inserted
replaced
50084:a46dfc2b58a3 | 50085:28dfb2df4ab9 |
---|---|
27 from . import ( | 27 from . import ( |
28 bookmarks, | 28 bookmarks, |
29 changelog, | 29 changelog, |
30 copies, | 30 copies, |
31 crecord as crecordmod, | 31 crecord as crecordmod, |
32 dirstateguard, | |
33 encoding, | 32 encoding, |
34 error, | 33 error, |
35 formatter, | 34 formatter, |
36 logcmdutil, | 35 logcmdutil, |
37 match as matchmod, | 36 match as matchmod, |
2787 ) | 2786 ) |
2788 | 2787 |
2789 return err | 2788 return err |
2790 | 2789 |
2791 | 2790 |
2791 class _AddRemoveContext: | |
2792 """a small (hacky) context to deal with lazy opening of context | |
2793 | |
2794 This is to be used in the `commit` function right below. This deals with | |
2795 lazily open a `changing_files` context inside a `transaction` that span the | |
2796 full commit operation. | |
2797 | |
2798 We need : | |
2799 - a `changing_files` context to wrap the dirstate change within the | |
2800 "addremove" operation, | |
2801 - a transaction to make sure these change are not written right after the | |
2802 addremove, but when the commit operation succeed. | |
2803 | |
2804 However it get complicated because: | |
2805 - opening a transaction "this early" shuffle hooks order, especially the | |
2806 `precommit` one happening after the `pretxtopen` one which I am not too | |
2807 enthusiastic about. | |
2808 - the `mq` extensions + the `record` extension stacks many layers of call | |
2809 to implement `qrefresh --interactive` and this result with `mq` calling a | |
2810 `strip` in the middle of this function. Which prevent the existence of | |
2811 transaction wrapping all of its function code. (however, `qrefresh` never | |
2812 call the `addremove` bits. | |
2813 - the largefile extensions (and maybe other extensions?) wraps `addremove` | |
2814 so slicing `addremove` in smaller bits is a complex endeavour. | |
2815 | |
2816 So I eventually took a this shortcut that open the transaction if we | |
2817 actually needs it, not disturbing much of the rest of the code. | |
2818 | |
2819 It will result in some hooks order change for `hg commit --addremove`, | |
2820 however it seems a corner case enough to ignore that for now (hopefully). | |
2821 | |
2822 Notes that None of the above problems seems insurmountable, however I have | |
2823 been fighting with this specific piece of code for a couple of day already | |
2824 and I need a solution to keep moving forward on the bigger work around | |
2825 `changing_files` context that is being introduced at the same time as this | |
2826 hack. | |
2827 | |
2828 Each problem seems to have a solution: | |
2829 - the hook order issue could be solved by refactoring the many-layer stack | |
2830 that currently composes a commit and calling them earlier, | |
2831 - the mq issue could be solved by refactoring `mq` so that the final strip | |
2832 is done after transaction closure. Be warned that the mq code is quite | |
2833 antic however. | |
2834 - large-file could be reworked in parallel of the `addremove` to be | |
2835 friendlier to this. | |
2836 | |
2837 However each of these tasks are too much a diversion right now. In addition | |
2838 they will be much easier to undertake when the `changing_files` dust has | |
2839 settled.""" | |
2840 | |
2841 def __init__(self, repo): | |
2842 self._repo = repo | |
2843 self._transaction = None | |
2844 self._dirstate_context = None | |
2845 self._state = None | |
2846 | |
2847 def __enter__(self): | |
2848 assert self._state is None | |
2849 self._state = True | |
2850 return self | |
2851 | |
2852 def open_transaction(self): | |
2853 """open a `transaction` and `changing_files` context | |
2854 | |
2855 Call this when you know that change to the dirstate will be needed and | |
2856 we need to open the transaction early | |
2857 | |
2858 This will also open the dirstate `changing_files` context, so you should | |
2859 call `close_dirstate_context` when the distate changes are done. | |
2860 """ | |
2861 assert self._state is not None | |
2862 if self._transaction is None: | |
2863 self._transaction = self._repo.transaction(b'commit') | |
2864 self._transaction.__enter__() | |
2865 if self._dirstate_context is None: | |
2866 self._dirstate_context = self._repo.dirstate.changing_files( | |
2867 self._repo | |
2868 ) | |
2869 self._dirstate_context.__enter__() | |
2870 | |
2871 def close_dirstate_context(self): | |
2872 """close the change_files if any | |
2873 | |
2874 Call this after the (potential) `open_transaction` call to close the | |
2875 (potential) changing_files context. | |
2876 """ | |
2877 if self._dirstate_context is not None: | |
2878 self._dirstate_context.__exit__(None, None, None) | |
2879 self._dirstate_context = None | |
2880 | |
2881 def __exit__(self, *args): | |
2882 if self._dirstate_context is not None: | |
2883 self._dirstate_context.__exit__(*args) | |
2884 if self._transaction is not None: | |
2885 self._transaction.__exit__(*args) | |
2886 | |
2887 | |
2792 def commit(ui, repo, commitfunc, pats, opts): | 2888 def commit(ui, repo, commitfunc, pats, opts): |
2793 '''commit the specified files or all outstanding changes''' | 2889 '''commit the specified files or all outstanding changes''' |
2794 date = opts.get(b'date') | 2890 date = opts.get(b'date') |
2795 if date: | 2891 if date: |
2796 opts[b'date'] = dateutil.parsedate(date) | 2892 opts[b'date'] = dateutil.parsedate(date) |
2797 | 2893 |
2798 dsguard = None | 2894 with repo.wlock(), repo.lock(): |
2799 # extract addremove carefully -- this function can be called from a command | |
2800 # that doesn't support addremove | |
2801 if opts.get(b'addremove'): | |
2802 dsguard = dirstateguard.dirstateguard(repo, b'commit') | |
2803 with dsguard or util.nullcontextmanager(): | |
2804 message = logmessage(ui, opts) | 2895 message = logmessage(ui, opts) |
2805 matcher = scmutil.match(repo[None], pats, opts) | 2896 matcher = scmutil.match(repo[None], pats, opts) |
2806 if True: | 2897 |
2898 with _AddRemoveContext(repo) as c: | |
2807 # extract addremove carefully -- this function can be called from a | 2899 # extract addremove carefully -- this function can be called from a |
2808 # command that doesn't support addremove | 2900 # command that doesn't support addremove |
2809 if opts.get(b'addremove'): | 2901 if opts.get(b'addremove'): |
2810 relative = scmutil.anypats(pats, opts) | 2902 relative = scmutil.anypats(pats, opts) |
2811 uipathfn = scmutil.getuipathfn( | 2903 uipathfn = scmutil.getuipathfn( |
2816 repo, | 2908 repo, |
2817 matcher, | 2909 matcher, |
2818 b"", | 2910 b"", |
2819 uipathfn, | 2911 uipathfn, |
2820 opts, | 2912 opts, |
2913 open_tr=c.open_transaction, | |
2821 ) | 2914 ) |
2822 m = _(b"failed to mark all new/missing files as added/removed") | 2915 m = _(b"failed to mark all new/missing files as added/removed") |
2823 if r != 0: | 2916 if r != 0: |
2824 raise error.Abort(m) | 2917 raise error.Abort(m) |
2825 | 2918 c.close_dirstate_context() |
2826 return commitfunc(ui, repo, message, matcher, opts) | 2919 return commitfunc(ui, repo, message, matcher, opts) |
2827 | 2920 |
2828 | 2921 |
2829 def samefile(f, ctx1, ctx2): | 2922 def samefile(f, ctx1, ctx2): |
2830 if f in ctx1.manifest(): | 2923 if f in ctx1.manifest(): |