##// END OF EJS Templates
precheck: fix false warning about content-divergence creation...
Sushil khanchi -
r49848:d4752aeb stable
parent child Browse files
Show More
@@ -1,252 +1,258 b''
1 1 # rewriteutil.py - utility functions for rewriting changesets
2 2 #
3 3 # Copyright 2017 Octobus <contact@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import re
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 hex,
15 15 nullrev,
16 16 )
17 17
18 18 from . import (
19 19 error,
20 20 node,
21 21 obsolete,
22 22 obsutil,
23 23 revset,
24 24 scmutil,
25 25 util,
26 26 )
27 27
28 28
29 29 NODE_RE = re.compile(br'\b[0-9a-f]{6,64}\b')
30 30
31 31
32 32 def _formatrevs(repo, revs, maxrevs=4):
33 33 """returns a string summarizing revisions in a decent size
34 34
35 35 If there are few enough revisions, we list them all. Otherwise we display a
36 36 summary of the form:
37 37
38 38 1ea73414a91b and 5 others
39 39 """
40 40 tonode = repo.changelog.node
41 41 numrevs = len(revs)
42 42 if numrevs < maxrevs:
43 43 shorts = [node.short(tonode(r)) for r in revs]
44 44 summary = b', '.join(shorts)
45 45 else:
46 46 first = revs.first()
47 47 summary = _(b'%s and %d others')
48 48 summary %= (node.short(tonode(first)), numrevs - 1)
49 49 return summary
50 50
51 51
52 def precheck(repo, revs, action=b'rewrite'):
52 def precheck(repo, revs, action=b'rewrite', check_divergence=True):
53 53 """check if revs can be rewritten
54 54 action is used to control the error message.
55 55
56 check_divergence allows skipping the divergence checks in cases like adding
57 a prune marker (A, ()) to obsstore (which can't be diverging).
58
56 59 Make sure this function is called after taking the lock.
57 60 """
58 61 if nullrev in revs:
59 62 msg = _(b"cannot %s the null revision") % action
60 63 hint = _(b"no changeset checked out")
61 64 raise error.InputError(msg, hint=hint)
62 65
63 66 if any(util.safehasattr(r, 'rev') for r in revs):
64 67 repo.ui.develwarn(b"rewriteutil.precheck called with ctx not revs")
65 68 revs = (r.rev() for r in revs)
66 69
67 70 if len(repo[None].parents()) > 1:
68 71 raise error.StateError(
69 72 _(b"cannot %s changesets while merging") % action
70 73 )
71 74
72 75 publicrevs = repo.revs(b'%ld and public()', revs)
73 76 if publicrevs:
74 77 summary = _formatrevs(repo, publicrevs)
75 78 msg = _(b"cannot %s public changesets: %s") % (action, summary)
76 79 hint = _(b"see 'hg help phases' for details")
77 80 raise error.InputError(msg, hint=hint)
78 81
79 82 newunstable = disallowednewunstable(repo, revs)
80 83 if newunstable:
81 84 hint = _(b"see 'hg help evolution.instability'")
82 85 raise error.InputError(
83 86 _(b"cannot %s changeset, as that will orphan %d descendants")
84 87 % (action, len(newunstable)),
85 88 hint=hint,
86 89 )
87 90
91 if not check_divergence:
92 return
93
88 94 if not obsolete.isenabled(repo, obsolete.allowdivergenceopt):
89 95 new_divergence = _find_new_divergence(repo, revs)
90 96 if new_divergence:
91 97 local_ctx, other_ctx, base_ctx = new_divergence
92 98 msg = _(
93 99 b'cannot %s %s, as that creates content-divergence with %s'
94 100 ) % (
95 101 action,
96 102 local_ctx,
97 103 other_ctx,
98 104 )
99 105 if local_ctx.rev() != base_ctx.rev():
100 106 msg += _(b', from %s') % base_ctx
101 107 if repo.ui.verbose:
102 108 if local_ctx.rev() != base_ctx.rev():
103 109 msg += _(
104 110 b'\n changeset %s is a successor of ' b'changeset %s'
105 111 ) % (local_ctx, base_ctx)
106 112 msg += _(
107 113 b'\n changeset %s already has a successor in '
108 114 b'changeset %s\n'
109 115 b' rewriting changeset %s would create '
110 116 b'"content-divergence"\n'
111 117 b' set experimental.evolution.allowdivergence=True to '
112 118 b'skip this check'
113 119 ) % (base_ctx, other_ctx, local_ctx)
114 120 raise error.InputError(
115 121 msg,
116 122 hint=_(
117 123 b"see 'hg help evolution.instability' for details on content-divergence"
118 124 ),
119 125 )
120 126 else:
121 127 raise error.InputError(
122 128 msg,
123 129 hint=_(
124 130 b"add --verbose for details or see "
125 131 b"'hg help evolution.instability'"
126 132 ),
127 133 )
128 134
129 135
130 136 def disallowednewunstable(repo, revs):
131 137 """Checks whether editing the revs will create new unstable changesets and
132 138 are we allowed to create them.
133 139
134 140 To allow new unstable changesets, set the config:
135 141 `experimental.evolution.allowunstable=True`
136 142 """
137 143 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
138 144 if allowunstable:
139 145 return revset.baseset()
140 146 return repo.revs(b"(%ld::) - %ld", revs, revs)
141 147
142 148
143 149 def _find_new_divergence(repo, revs):
144 150 obsrevs = repo.revs(b'%ld and obsolete()', revs)
145 151 for r in obsrevs:
146 152 div = find_new_divergence_from(repo, repo[r])
147 153 if div:
148 154 return (repo[r], repo[div[0]], repo.unfiltered()[div[1]])
149 155 return None
150 156
151 157
152 158 def find_new_divergence_from(repo, ctx):
153 159 """return divergent revision if rewriting an obsolete cset (ctx) will
154 160 create divergence
155 161
156 162 Returns (<other node>, <common ancestor node>) or None
157 163 """
158 164 if not ctx.obsolete():
159 165 return None
160 166 # We need to check two cases that can cause divergence:
161 167 # case 1: the rev being rewritten has a non-obsolete successor (easily
162 168 # detected by successorssets)
163 169 sset = obsutil.successorssets(repo, ctx.node())
164 170 if sset:
165 171 return (sset[0][0], ctx.node())
166 172 else:
167 173 # case 2: one of the precursors of the rev being revived has a
168 174 # non-obsolete successor (we need divergentsets for this)
169 175 divsets = obsutil.divergentsets(repo, ctx)
170 176 if divsets:
171 177 nsuccset = divsets[0][b'divergentnodes']
172 178 prec = divsets[0][b'commonpredecessor']
173 179 return (nsuccset[0], prec)
174 180 return None
175 181
176 182
177 183 def skip_empty_successor(ui, command):
178 184 empty_successor = ui.config(b'rewrite', b'empty-successor')
179 185 if empty_successor == b'skip':
180 186 return True
181 187 elif empty_successor == b'keep':
182 188 return False
183 189 else:
184 190 raise error.ConfigError(
185 191 _(
186 192 b"%s doesn't know how to handle config "
187 193 b"rewrite.empty-successor=%s (only 'skip' and 'keep' are "
188 194 b"supported)"
189 195 )
190 196 % (command, empty_successor)
191 197 )
192 198
193 199
194 200 def update_hash_refs(repo, commitmsg, pending=None):
195 201 """Replace all obsolete commit hashes in the message with the current hash.
196 202
197 203 If the obsolete commit was split or is divergent, the hash is not replaced
198 204 as there's no way to know which successor to choose.
199 205
200 206 For commands that update a series of commits in the current transaction, the
201 207 new obsolete markers can be considered by setting ``pending`` to a mapping
202 208 of ``pending[oldnode] = [successor_node1, successor_node2,..]``.
203 209 """
204 210 if not pending:
205 211 pending = {}
206 212 cache = {}
207 213 hashes = re.findall(NODE_RE, commitmsg)
208 214 unfi = repo.unfiltered()
209 215 for h in hashes:
210 216 try:
211 217 fullnode = scmutil.resolvehexnodeidprefix(unfi, h)
212 218 except error.WdirUnsupported:
213 219 # Someone has an fffff... in a commit message we're
214 220 # rewriting. Don't try rewriting that.
215 221 continue
216 222 if fullnode is None:
217 223 continue
218 224 ctx = unfi[fullnode]
219 225 if not ctx.obsolete():
220 226 successors = pending.get(fullnode)
221 227 if successors is None:
222 228 continue
223 229 # obsutil.successorssets() returns a list of list of nodes
224 230 successors = [successors]
225 231 else:
226 232 successors = obsutil.successorssets(repo, ctx.node(), cache=cache)
227 233
228 234 # We can't make any assumptions about how to update the hash if the
229 235 # cset in question was split or diverged.
230 236 if len(successors) == 1 and len(successors[0]) == 1:
231 237 successor = successors[0][0]
232 238 if successor is not None:
233 239 newhash = hex(successor)
234 240 commitmsg = commitmsg.replace(h, newhash[: len(h)])
235 241 else:
236 242 repo.ui.note(
237 243 _(
238 244 b'The stale commit message reference to %s could '
239 245 b'not be updated\n(The referenced commit was dropped)\n'
240 246 )
241 247 % h
242 248 )
243 249 else:
244 250 repo.ui.note(
245 251 _(
246 252 b'The stale commit message reference to %s could '
247 253 b'not be updated\n'
248 254 )
249 255 % h
250 256 )
251 257
252 258 return commitmsg
General Comments 0
You need to be logged in to leave comments. Login now