Mercurial > hg
annotate hgext/split.py @ 39141:c89e2fb207a1 stable
remotephase: fast path newheads computation in simple case (issue5964)
Changeset 88efb7d6bcb6 fixed the logic of `phases.newheads` but greatly
regressed its performance (up to many order of magnitude). The first step to
fix the regression is to exit early when there is no work to do. If there are
no heads to filter or not roots to filter them, we don't have to do any work.
This fixes the regression when talking to an all public changeset. The
performance is even better than before.
pypy, compared to an all public repo
------------------------------------
8eeed92475d5: 0.005758 seconds
88efb7d6bcb6: 0.602517 seconds (x104)
this code: 0.001508 seconds (-74% from base)
mercurial compared to an all public repo
----------------------------------------
8eeed92475d5: 0.000577 seconds
88efb7d6bcb6: 0.185316 seconds (x321)
this code: 0.000150 seconds (-74% from base)
The performance of newheads, when actual computations are required, is fixed
in the next changeset.
author | Boris Feld <boris.feld@octobus.net> |
---|---|
date | Fri, 17 Aug 2018 16:00:32 -0700 |
parents | 4f885770c4a2 |
children | c303d65d2e34 |
rev | line source |
---|---|
35455 | 1 # split.py - split a changeset into smaller ones |
2 # | |
3 # Copyright 2015 Laurent Charignon <lcharignon@fb.com> | |
4 # Copyright 2017 Facebook, Inc. | |
5 # | |
6 # This software may be used and distributed according to the terms of the | |
7 # GNU General Public License version 2 or any later version. | |
8 """command to split a changeset into smaller ones (EXPERIMENTAL)""" | |
9 | |
10 from __future__ import absolute_import | |
11 | |
12 from mercurial.i18n import _ | |
13 | |
14 from mercurial.node import ( | |
15 nullid, | |
16 short, | |
17 ) | |
18 | |
19 from mercurial import ( | |
20 bookmarks, | |
21 cmdutil, | |
22 commands, | |
23 error, | |
24 hg, | |
25 obsolete, | |
26 phases, | |
36400
7b86aa31b004
py3: fix handling of keyword arguments at more places
Pulkit Goyal <7895pulkit@gmail.com>
parents:
35455
diff
changeset
|
27 pycompat, |
35455 | 28 registrar, |
29 revsetlang, | |
30 scmutil, | |
31 ) | |
32 | |
33 # allow people to use split without explicitly enabling rebase extension | |
34 from . import ( | |
35 rebase, | |
36 ) | |
37 | |
38 cmdtable = {} | |
39 command = registrar.command(cmdtable) | |
40 | |
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | |
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | |
43 # be specifying the version(s) of Mercurial they are tested with, or | |
44 # leave the attribute unspecified. | |
45 testedwith = 'ships-with-hg-core' | |
46 | |
47 @command('^split', | |
48 [('r', 'rev', '', _("revision to split"), _('REV')), | |
49 ('', 'rebase', True, _('rebase descendants after split')), | |
50 ] + cmdutil.commitopts2, | |
51 _('hg split [--no-rebase] [[-r] REV]')) | |
52 def split(ui, repo, *revs, **opts): | |
53 """split a changeset into smaller ones | |
54 | |
55 Repeatedly prompt changes and commit message for new changesets until there | |
56 is nothing left in the original changeset. | |
57 | |
58 If --rev was not given, split the working directory parent. | |
59 | |
60 By default, rebase connected non-obsoleted descendants onto the new | |
61 changeset. Use --no-rebase to avoid the rebase. | |
62 """ | |
38069
5ba0cf22e4d0
py3: fix kwargs handling in hgext/split.py
Pulkit Goyal <7895pulkit@gmail.com>
parents:
36408
diff
changeset
|
63 opts = pycompat.byteskwargs(opts) |
35455 | 64 revlist = [] |
65 if opts.get('rev'): | |
66 revlist.append(opts.get('rev')) | |
67 revlist.extend(revs) | |
68 with repo.wlock(), repo.lock(), repo.transaction('split') as tr: | |
69 revs = scmutil.revrange(repo, revlist or ['.']) | |
70 if len(revs) > 1: | |
71 raise error.Abort(_('cannot split multiple revisions')) | |
72 | |
73 rev = revs.first() | |
74 ctx = repo[rev] | |
75 if rev is None or ctx.node() == nullid: | |
76 ui.status(_('nothing to split\n')) | |
77 return 1 | |
78 if ctx.node() is None: | |
79 raise error.Abort(_('cannot split working directory')) | |
80 | |
81 # rewriteutil.precheck is not very useful here because: | |
82 # 1. null check is done above and it's more friendly to return 1 | |
83 # instead of abort | |
84 # 2. mergestate check is done below by cmdutil.bailifchanged | |
85 # 3. unstable check is more complex here because of --rebase | |
86 # | |
87 # So only "public" check is useful and it's checked directly here. | |
88 if ctx.phase() == phases.public: | |
89 raise error.Abort(_('cannot split public changeset'), | |
90 hint=_("see 'hg help phases' for details")) | |
91 | |
92 descendants = list(repo.revs('(%d::) - (%d)', rev, rev)) | |
93 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt) | |
94 if opts.get('rebase'): | |
95 # Skip obsoleted descendants and their descendants so the rebase | |
96 # won't cause conflicts for sure. | |
97 torebase = list(repo.revs('%ld - (%ld & obsolete())::', | |
98 descendants, descendants)) | |
99 if not alloworphaned and len(torebase) != len(descendants): | |
100 raise error.Abort(_('split would leave orphaned changesets ' | |
101 'behind')) | |
102 else: | |
103 if not alloworphaned and descendants: | |
104 raise error.Abort( | |
105 _('cannot split changeset with children without rebase')) | |
106 torebase = () | |
107 | |
108 if len(ctx.parents()) > 1: | |
109 raise error.Abort(_('cannot split a merge changeset')) | |
110 | |
111 cmdutil.bailifchanged(repo) | |
112 | |
113 # Deactivate bookmark temporarily so it won't get moved unintentionally | |
114 bname = repo._activebookmark | |
115 if bname and repo._bookmarks[bname] != ctx.node(): | |
116 bookmarks.deactivate(repo) | |
117 | |
118 wnode = repo['.'].node() | |
119 top = None | |
120 try: | |
121 top = dosplit(ui, repo, tr, ctx, opts) | |
122 finally: | |
123 # top is None: split failed, need update --clean recovery. | |
124 # wnode == ctx.node(): wnode split, no need to update. | |
125 if top is None or wnode != ctx.node(): | |
126 hg.clean(repo, wnode, show_stats=False) | |
127 if bname: | |
128 bookmarks.activate(repo, bname) | |
129 if torebase and top: | |
130 dorebase(ui, repo, torebase, top) | |
131 | |
132 def dosplit(ui, repo, tr, ctx, opts): | |
133 committed = [] # [ctx] | |
134 | |
135 # Set working parent to ctx.p1(), and keep working copy as ctx's content | |
136 # NOTE: if we can have "update without touching working copy" API, the | |
137 # revert step could be cheaper. | |
138 hg.clean(repo, ctx.p1().node(), show_stats=False) | |
139 parents = repo.changelog.parents(ctx.node()) | |
140 ui.pushbuffer() | |
141 cmdutil.revert(ui, repo, ctx, parents) | |
142 ui.popbuffer() # discard "reverting ..." messages | |
143 | |
144 # Any modified, added, removed, deleted result means split is incomplete | |
145 incomplete = lambda repo: any(repo.status()[:4]) | |
146 | |
147 # Main split loop | |
148 while incomplete(repo): | |
149 if committed: | |
150 header = (_('HG: Splitting %s. So far it has been split into:\n') | |
151 % short(ctx.node())) | |
152 for c in committed: | |
153 firstline = c.description().split('\n', 1)[0] | |
154 header += _('HG: - %s: %s\n') % (short(c.node()), firstline) | |
155 header += _('HG: Write commit message for the next split ' | |
156 'changeset.\n') | |
157 else: | |
158 header = _('HG: Splitting %s. Write commit message for the ' | |
159 'first split changeset.\n') % short(ctx.node()) | |
160 opts.update({ | |
161 'edit': True, | |
162 'interactive': True, | |
163 'message': header + ctx.description(), | |
164 }) | |
36400
7b86aa31b004
py3: fix handling of keyword arguments at more places
Pulkit Goyal <7895pulkit@gmail.com>
parents:
35455
diff
changeset
|
165 commands.commit(ui, repo, **pycompat.strkwargs(opts)) |
35455 | 166 newctx = repo['.'] |
167 committed.append(newctx) | |
168 | |
169 if not committed: | |
170 raise error.Abort(_('cannot split an empty revision')) | |
171 | |
172 scmutil.cleanupnodes(repo, {ctx.node(): [c.node() for c in committed]}, | |
38424
4f885770c4a2
split: preserve phase of commit that is being split
Martin von Zweigbergk <martinvonz@google.com>
parents:
38069
diff
changeset
|
173 operation='split', fixphase=True) |
35455 | 174 |
175 return committed[-1] | |
176 | |
36408
83bade6206d4
split: use ctx.rev() instead of %d % ctx
Gregory Szorc <gregory.szorc@gmail.com>
parents:
36400
diff
changeset
|
177 def dorebase(ui, repo, src, destctx): |
35455 | 178 rebase.rebase(ui, repo, rev=[revsetlang.formatspec('%ld', src)], |
36408
83bade6206d4
split: use ctx.rev() instead of %d % ctx
Gregory Szorc <gregory.szorc@gmail.com>
parents:
36400
diff
changeset
|
179 dest=revsetlang.formatspec('%d', destctx.rev())) |