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