Mercurial > hg
annotate hgext/split.py @ 42001:624d6683c705
branchmap: remove the dict interface from the branchcache class (API)
The current branchmap computation involves reading the whole branchmap from
disk, validating all the nodes even if they are not required. This leads to a
lot of time on repos which have large branchmap or a lot of branches. On large
repos, this can validate around 1000's of nodes.
On some operations, like finding whether a branch exists or not, we don't need
to validate all the nodes. Or updating heads for a single branch.
Before this patch, branchcache class was having dict interface and it was hard
to keep track of reads.
This patch removes the dict interface. Upcoming patches will implement lazy
loading and validation of data and implement better API's.
Differential Revision: https://phab.mercurial-scm.org/D6151
author | Pulkit Goyal <pulkit@yandex-team.ru> |
---|---|
date | Mon, 18 Mar 2019 18:59:38 +0300 |
parents | 42e2c7c52e1b |
children | 2372284d9457 |
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 | |
40295
fa88170c10bb
help: adding a proper declaration for shortlist/basic commands (API)
Rodrigo Damazio <rdamazio@google.com>
parents:
40293
diff
changeset
|
47 @command('split', |
35455 | 48 [('r', 'rev', '', _("revision to split"), _('REV')), |
49 ('', 'rebase', True, _('rebase descendants after split')), | |
50 ] + cmdutil.commitopts2, | |
40293
c303d65d2e34
help: assigning categories to existing commands
rdamazio@google.com
parents:
38424
diff
changeset
|
51 _('hg split [--no-rebase] [[-r] REV]'), |
40295
fa88170c10bb
help: adding a proper declaration for shortlist/basic commands (API)
Rodrigo Damazio <rdamazio@google.com>
parents:
40293
diff
changeset
|
52 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True) |
35455 | 53 def split(ui, repo, *revs, **opts): |
54 """split a changeset into smaller ones | |
55 | |
56 Repeatedly prompt changes and commit message for new changesets until there | |
57 is nothing left in the original changeset. | |
58 | |
59 If --rev was not given, split the working directory parent. | |
60 | |
61 By default, rebase connected non-obsoleted descendants onto the new | |
62 changeset. Use --no-rebase to avoid the rebase. | |
63 """ | |
38069
5ba0cf22e4d0
py3: fix kwargs handling in hgext/split.py
Pulkit Goyal <7895pulkit@gmail.com>
parents:
36408
diff
changeset
|
64 opts = pycompat.byteskwargs(opts) |
35455 | 65 revlist = [] |
66 if opts.get('rev'): | |
67 revlist.append(opts.get('rev')) | |
68 revlist.extend(revs) | |
69 with repo.wlock(), repo.lock(), repo.transaction('split') as tr: | |
70 revs = scmutil.revrange(repo, revlist or ['.']) | |
71 if len(revs) > 1: | |
72 raise error.Abort(_('cannot split multiple revisions')) | |
73 | |
74 rev = revs.first() | |
75 ctx = repo[rev] | |
76 if rev is None or ctx.node() == nullid: | |
77 ui.status(_('nothing to split\n')) | |
78 return 1 | |
79 if ctx.node() is None: | |
80 raise error.Abort(_('cannot split working directory')) | |
81 | |
82 # rewriteutil.precheck is not very useful here because: | |
83 # 1. null check is done above and it's more friendly to return 1 | |
84 # instead of abort | |
85 # 2. mergestate check is done below by cmdutil.bailifchanged | |
86 # 3. unstable check is more complex here because of --rebase | |
87 # | |
88 # So only "public" check is useful and it's checked directly here. | |
89 if ctx.phase() == phases.public: | |
90 raise error.Abort(_('cannot split public changeset'), | |
91 hint=_("see 'hg help phases' for details")) | |
92 | |
93 descendants = list(repo.revs('(%d::) - (%d)', rev, rev)) | |
94 alloworphaned = obsolete.isenabled(repo, obsolete.allowunstableopt) | |
95 if opts.get('rebase'): | |
96 # Skip obsoleted descendants and their descendants so the rebase | |
97 # won't cause conflicts for sure. | |
98 torebase = list(repo.revs('%ld - (%ld & obsolete())::', | |
99 descendants, descendants)) | |
100 if not alloworphaned and len(torebase) != len(descendants): | |
101 raise error.Abort(_('split would leave orphaned changesets ' | |
102 'behind')) | |
103 else: | |
104 if not alloworphaned and descendants: | |
105 raise error.Abort( | |
106 _('cannot split changeset with children without rebase')) | |
107 torebase = () | |
108 | |
109 if len(ctx.parents()) > 1: | |
110 raise error.Abort(_('cannot split a merge changeset')) | |
111 | |
112 cmdutil.bailifchanged(repo) | |
113 | |
114 # Deactivate bookmark temporarily so it won't get moved unintentionally | |
115 bname = repo._activebookmark | |
116 if bname and repo._bookmarks[bname] != ctx.node(): | |
117 bookmarks.deactivate(repo) | |
118 | |
119 wnode = repo['.'].node() | |
120 top = None | |
121 try: | |
122 top = dosplit(ui, repo, tr, ctx, opts) | |
123 finally: | |
124 # top is None: split failed, need update --clean recovery. | |
125 # wnode == ctx.node(): wnode split, no need to update. | |
126 if top is None or wnode != ctx.node(): | |
127 hg.clean(repo, wnode, show_stats=False) | |
128 if bname: | |
129 bookmarks.activate(repo, bname) | |
130 if torebase and top: | |
131 dorebase(ui, repo, torebase, top) | |
132 | |
133 def dosplit(ui, repo, tr, ctx, opts): | |
134 committed = [] # [ctx] | |
135 | |
136 # Set working parent to ctx.p1(), and keep working copy as ctx's content | |
41966
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
137 if ctx.node() != repo.dirstate.p1(): |
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
138 hg.clean(repo, ctx.node(), show_stats=False) |
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
139 with repo.dirstate.parentchange(): |
42e2c7c52e1b
split: use the new movedirstate() we now have in scmutil
Martin von Zweigbergk <martinvonz@google.com>
parents:
40295
diff
changeset
|
140 scmutil.movedirstate(repo, ctx.p1()) |
35455 | 141 |
142 # Any modified, added, removed, deleted result means split is incomplete | |
143 incomplete = lambda repo: any(repo.status()[:4]) | |
144 | |
145 # Main split loop | |
146 while incomplete(repo): | |
147 if committed: | |
148 header = (_('HG: Splitting %s. So far it has been split into:\n') | |
149 % short(ctx.node())) | |
150 for c in committed: | |
151 firstline = c.description().split('\n', 1)[0] | |
152 header += _('HG: - %s: %s\n') % (short(c.node()), firstline) | |
153 header += _('HG: Write commit message for the next split ' | |
154 'changeset.\n') | |
155 else: | |
156 header = _('HG: Splitting %s. Write commit message for the ' | |
157 'first split changeset.\n') % short(ctx.node()) | |
158 opts.update({ | |
159 'edit': True, | |
160 'interactive': True, | |
161 'message': header + ctx.description(), | |
162 }) | |
36400
7b86aa31b004
py3: fix handling of keyword arguments at more places
Pulkit Goyal <7895pulkit@gmail.com>
parents:
35455
diff
changeset
|
163 commands.commit(ui, repo, **pycompat.strkwargs(opts)) |
35455 | 164 newctx = repo['.'] |
165 committed.append(newctx) | |
166 | |
167 if not committed: | |
168 raise error.Abort(_('cannot split an empty revision')) | |
169 | |
170 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
|
171 operation='split', fixphase=True) |
35455 | 172 |
173 return committed[-1] | |
174 | |
36408
83bade6206d4
split: use ctx.rev() instead of %d % ctx
Gregory Szorc <gregory.szorc@gmail.com>
parents:
36400
diff
changeset
|
175 def dorebase(ui, repo, src, destctx): |
35455 | 176 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
|
177 dest=revsetlang.formatspec('%d', destctx.rev())) |