mercurial/cmdutil.py
changeset 50029 28dfb2df4ab9
parent 50028 a46dfc2b58a3
child 50038 6cdcab3ae3fa
equal deleted inserted replaced
50028:a46dfc2b58a3 50029: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():