##// END OF EJS Templates
destutil: show message and hint at updating to the closed head as warning
FUJIWARA Katsunori -
r28684:1ace17e1 default
parent child Browse files
Show More
@@ -1,422 +1,422 b''
1 1 # destutil.py - Mercurial utility function for command destination
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com> and other
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 from .i18n import _
11 11 from . import (
12 12 bookmarks,
13 13 error,
14 14 obsolete,
15 15 )
16 16
17 17 def _destupdatevalidate(repo, rev, clean, check):
18 18 """validate that the destination comply to various rules
19 19
20 20 This exists as its own function to help wrapping from extensions."""
21 21 wc = repo[None]
22 22 p1 = wc.p1()
23 23 if not clean:
24 24 # Check that the update is linear.
25 25 #
26 26 # Mercurial do not allow update-merge for non linear pattern
27 27 # (that would be technically possible but was considered too confusing
28 28 # for user a long time ago)
29 29 #
30 30 # See mercurial.merge.update for details
31 31 if p1.rev() not in repo.changelog.ancestors([rev], inclusive=True):
32 32 dirty = wc.dirty(missing=True)
33 33 foreground = obsolete.foreground(repo, [p1.node()])
34 34 if not repo[rev].node() in foreground:
35 35 if dirty:
36 36 msg = _("uncommitted changes")
37 37 hint = _("commit and merge, or update --clean to"
38 38 " discard changes")
39 39 raise error.UpdateAbort(msg, hint=hint)
40 40 elif not check: # destination is not a descendant.
41 41 msg = _("not a linear update")
42 42 hint = _("merge or update --check to force update")
43 43 raise error.UpdateAbort(msg, hint=hint)
44 44
45 45 def _destupdateobs(repo, clean, check):
46 46 """decide of an update destination from obsolescence markers"""
47 47 node = None
48 48 wc = repo[None]
49 49 p1 = wc.p1()
50 50 movemark = None
51 51
52 52 if p1.obsolete() and not p1.children():
53 53 # allow updating to successors
54 54 successors = obsolete.successorssets(repo, p1.node())
55 55
56 56 # behavior of certain cases is as follows,
57 57 #
58 58 # divergent changesets: update to highest rev, similar to what
59 59 # is currently done when there are more than one head
60 60 # (i.e. 'tip')
61 61 #
62 62 # replaced changesets: same as divergent except we know there
63 63 # is no conflict
64 64 #
65 65 # pruned changeset: no update is done; though, we could
66 66 # consider updating to the first non-obsolete parent,
67 67 # similar to what is current done for 'hg prune'
68 68
69 69 if successors:
70 70 # flatten the list here handles both divergent (len > 1)
71 71 # and the usual case (len = 1)
72 72 successors = [n for sub in successors for n in sub]
73 73
74 74 # get the max revision for the given successors set,
75 75 # i.e. the 'tip' of a set
76 76 node = repo.revs('max(%ln)', successors).first()
77 77 if bookmarks.isactivewdirparent(repo):
78 78 movemark = repo['.'].node()
79 79 return node, movemark, None
80 80
81 81 def _destupdatebook(repo, clean, check):
82 82 """decide on an update destination from active bookmark"""
83 83 # we also move the active bookmark, if any
84 84 activemark = None
85 85 node, movemark = bookmarks.calculateupdate(repo.ui, repo, None)
86 86 if node is not None:
87 87 activemark = node
88 88 return node, movemark, activemark
89 89
90 90 def _destupdatebranch(repo, clean, check):
91 91 """decide on an update destination from current branch
92 92
93 93 This ignores closed branch heads.
94 94 """
95 95 wc = repo[None]
96 96 movemark = node = None
97 97 currentbranch = wc.branch()
98 98 if currentbranch in repo.branchmap():
99 99 heads = repo.branchheads(currentbranch)
100 100 if heads:
101 101 node = repo.revs('max(.::(%ln))', heads).first()
102 102 if bookmarks.isactivewdirparent(repo):
103 103 movemark = repo['.'].node()
104 104 else:
105 105 if currentbranch == 'default': # no default branch!
106 106 # update to the tipmost non-closed branch head
107 107 node = repo.revs('max(head() and not closed())').first()
108 108 else:
109 109 raise error.Abort(_("branch %s not found") % currentbranch)
110 110 return node, movemark, None
111 111
112 112 def _destupdatebranchfallback(repo, clean, check):
113 113 """decide on an update destination from closed heads in current branch"""
114 114 wc = repo[None]
115 115 currentbranch = wc.branch()
116 116 movemark = None
117 117 if currentbranch in repo.branchmap():
118 118 # here, all descendant branch heads are closed
119 119 heads = repo.branchheads(currentbranch, closed=True)
120 120 assert heads, "any branch has at least one head"
121 121 node = repo.revs('max(.::(%ln))', heads).first()
122 122 assert node is not None, ("any revision has at least "
123 123 "one descendant branch head")
124 124 if bookmarks.isactivewdirparent(repo):
125 125 movemark = repo['.'].node()
126 126 else:
127 127 # here, no "default" branch, and all branches are closed
128 128 node = repo.lookup('tip')
129 129 assert node is not None, "'tip' exists even in empty repository"
130 130 return node, movemark, None
131 131
132 132 # order in which each step should be evalutated
133 133 # steps are run until one finds a destination
134 134 destupdatesteps = ['evolution', 'bookmark', 'branch', 'branchfallback']
135 135 # mapping to ease extension overriding steps.
136 136 destupdatestepmap = {'evolution': _destupdateobs,
137 137 'bookmark': _destupdatebook,
138 138 'branch': _destupdatebranch,
139 139 'branchfallback': _destupdatebranchfallback,
140 140 }
141 141
142 142 def destupdate(repo, clean=False, check=False):
143 143 """destination for bare update operation
144 144
145 145 return (rev, movemark, activemark)
146 146
147 147 - rev: the revision to update to,
148 148 - movemark: node to move the active bookmark from
149 149 (cf bookmark.calculate update),
150 150 - activemark: a bookmark to activate at the end of the update.
151 151 """
152 152 node = movemark = activemark = None
153 153
154 154 for step in destupdatesteps:
155 155 node, movemark, activemark = destupdatestepmap[step](repo, clean, check)
156 156 if node is not None:
157 157 break
158 158 rev = repo[node].rev()
159 159
160 160 _destupdatevalidate(repo, rev, clean, check)
161 161
162 162 return rev, movemark, activemark
163 163
164 164 msgdestmerge = {
165 165 # too many matching divergent bookmark
166 166 'toomanybookmarks':
167 167 {'merge':
168 168 (_("multiple matching bookmarks to merge -"
169 169 " please merge with an explicit rev or bookmark"),
170 170 _("run 'hg heads' to see all heads")),
171 171 'rebase':
172 172 (_("multiple matching bookmarks to rebase -"
173 173 " please rebase to an explicit rev or bookmark"),
174 174 _("run 'hg heads' to see all heads")),
175 175 },
176 176 # no other matching divergent bookmark
177 177 'nootherbookmarks':
178 178 {'merge':
179 179 (_("no matching bookmark to merge - "
180 180 "please merge with an explicit rev or bookmark"),
181 181 _("run 'hg heads' to see all heads")),
182 182 'rebase':
183 183 (_("no matching bookmark to rebase - "
184 184 "please rebase to an explicit rev or bookmark"),
185 185 _("run 'hg heads' to see all heads")),
186 186 },
187 187 # branch have too many unbookmarked heads, no obvious destination
188 188 'toomanyheads':
189 189 {'merge':
190 190 (_("branch '%s' has %d heads - please merge with an explicit rev"),
191 191 _("run 'hg heads .' to see heads")),
192 192 'rebase':
193 193 (_("branch '%s' has %d heads - please rebase to an explicit rev"),
194 194 _("run 'hg heads .' to see heads")),
195 195 },
196 196 # branch have no other unbookmarked heads
197 197 'bookmarkedheads':
198 198 {'merge':
199 199 (_("heads are bookmarked - please merge with an explicit rev"),
200 200 _("run 'hg heads' to see all heads")),
201 201 'rebase':
202 202 (_("heads are bookmarked - please rebase to an explicit rev"),
203 203 _("run 'hg heads' to see all heads")),
204 204 },
205 205 # branch have just a single heads, but there is other branches
206 206 'nootherbranchheads':
207 207 {'merge':
208 208 (_("branch '%s' has one head - please merge with an explicit rev"),
209 209 _("run 'hg heads' to see all heads")),
210 210 'rebase':
211 211 (_("branch '%s' has one head - please rebase to an explicit rev"),
212 212 _("run 'hg heads' to see all heads")),
213 213 },
214 214 # repository have a single head
215 215 'nootherheads':
216 216 {'merge':
217 217 (_('nothing to merge'),
218 218 None),
219 219 'rebase':
220 220 (_('nothing to rebase'),
221 221 None),
222 222 },
223 223 # repository have a single head and we are not on it
224 224 'nootherheadsbehind':
225 225 {'merge':
226 226 (_('nothing to merge'),
227 227 _("use 'hg update' instead")),
228 228 'rebase':
229 229 (_('nothing to rebase'),
230 230 _("use 'hg update' instead")),
231 231 },
232 232 # We are not on a head
233 233 'notatheads':
234 234 {'merge':
235 235 (_('working directory not at a head revision'),
236 236 _("use 'hg update' or merge with an explicit revision")),
237 237 'rebase':
238 238 (_('working directory not at a head revision'),
239 239 _("use 'hg update' or rebase to an explicit revision"))
240 240 },
241 241 'emptysourceset':
242 242 {'merge':
243 243 (_('source set is empty'),
244 244 None),
245 245 'rebase':
246 246 (_('source set is empty'),
247 247 None),
248 248 },
249 249 'multiplebranchessourceset':
250 250 {'merge':
251 251 (_('source set is rooted in multiple branches'),
252 252 None),
253 253 'rebase':
254 254 (_('rebaseset is rooted in multiple named branches'),
255 255 _('specify an explicit destination with --dest')),
256 256 },
257 257 }
258 258
259 259 def _destmergebook(repo, action='merge', sourceset=None):
260 260 """find merge destination in the active bookmark case"""
261 261 node = None
262 262 bmheads = repo.bookmarkheads(repo._activebookmark)
263 263 curhead = repo[repo._activebookmark].node()
264 264 if len(bmheads) == 2:
265 265 if curhead == bmheads[0]:
266 266 node = bmheads[1]
267 267 else:
268 268 node = bmheads[0]
269 269 elif len(bmheads) > 2:
270 270 msg, hint = msgdestmerge['toomanybookmarks'][action]
271 271 raise error.ManyMergeDestAbort(msg, hint=hint)
272 272 elif len(bmheads) <= 1:
273 273 msg, hint = msgdestmerge['nootherbookmarks'][action]
274 274 raise error.NoMergeDestAbort(msg, hint=hint)
275 275 assert node is not None
276 276 return node
277 277
278 278 def _destmergebranch(repo, action='merge', sourceset=None, onheadcheck=True):
279 279 """find merge destination based on branch heads"""
280 280 node = None
281 281
282 282 if sourceset is None:
283 283 sourceset = [repo[repo.dirstate.p1()].rev()]
284 284 branch = repo.dirstate.branch()
285 285 elif not sourceset:
286 286 msg, hint = msgdestmerge['emptysourceset'][action]
287 287 raise error.NoMergeDestAbort(msg, hint=hint)
288 288 else:
289 289 branch = None
290 290 for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset):
291 291 if branch is not None and ctx.branch() != branch:
292 292 msg, hint = msgdestmerge['multiplebranchessourceset'][action]
293 293 raise error.ManyMergeDestAbort(msg, hint=hint)
294 294 branch = ctx.branch()
295 295
296 296 bheads = repo.branchheads(branch)
297 297 onhead = repo.revs('%ld and %ln', sourceset, bheads)
298 298 if onheadcheck and not onhead:
299 299 # Case A: working copy if not on a head. (merge only)
300 300 #
301 301 # This is probably a user mistake We bailout pointing at 'hg update'
302 302 if len(repo.heads()) <= 1:
303 303 msg, hint = msgdestmerge['nootherheadsbehind'][action]
304 304 else:
305 305 msg, hint = msgdestmerge['notatheads'][action]
306 306 raise error.Abort(msg, hint=hint)
307 307 # remove heads descendants of source from the set
308 308 bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset))
309 309 # filters out bookmarked heads
310 310 nbhs = list(repo.revs('%ld - bookmark()', bheads))
311 311 if len(nbhs) > 1:
312 312 # Case B: There is more than 1 other anonymous heads
313 313 #
314 314 # This means that there will be more than 1 candidate. This is
315 315 # ambiguous. We abort asking the user to pick as explicit destination
316 316 # instead.
317 317 msg, hint = msgdestmerge['toomanyheads'][action]
318 318 msg %= (branch, len(bheads) + 1)
319 319 raise error.ManyMergeDestAbort(msg, hint=hint)
320 320 elif not nbhs:
321 321 # Case B: There is no other anonymous heads
322 322 #
323 323 # This means that there is no natural candidate to merge with.
324 324 # We abort, with various messages for various cases.
325 325 if bheads:
326 326 msg, hint = msgdestmerge['bookmarkedheads'][action]
327 327 elif len(repo.heads()) > 1:
328 328 msg, hint = msgdestmerge['nootherbranchheads'][action]
329 329 msg %= branch
330 330 elif not onhead:
331 331 # if 'onheadcheck == False' (rebase case),
332 332 # this was not caught in Case A.
333 333 msg, hint = msgdestmerge['nootherheadsbehind'][action]
334 334 else:
335 335 msg, hint = msgdestmerge['nootherheads'][action]
336 336 raise error.NoMergeDestAbort(msg, hint=hint)
337 337 else:
338 338 node = nbhs[0]
339 339 assert node is not None
340 340 return node
341 341
342 342 def destmerge(repo, action='merge', sourceset=None, onheadcheck=True):
343 343 """return the default destination for a merge
344 344
345 345 (or raise exception about why it can't pick one)
346 346
347 347 :action: the action being performed, controls emitted error message
348 348 """
349 349 if repo._activebookmark:
350 350 node = _destmergebook(repo, action=action, sourceset=sourceset)
351 351 else:
352 352 node = _destmergebranch(repo, action=action, sourceset=sourceset,
353 353 onheadcheck=onheadcheck)
354 354 return repo[node].rev()
355 355
356 356 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
357 357
358 358 def desthistedit(ui, repo):
359 359 """Default base revision to edit for `hg histedit`."""
360 360 # Avoid cycle: scmutil -> revset -> destutil
361 361 from . import scmutil
362 362
363 363 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
364 364 if default:
365 365 revs = scmutil.revrange(repo, [default])
366 366 if revs:
367 367 # The revset supplied by the user may not be in ascending order nor
368 368 # take the first revision. So do this manually.
369 369 revs.sort()
370 370 return revs.first()
371 371
372 372 return None
373 373
374 374 def _statusotherbook(ui, repo):
375 375 bmheads = repo.bookmarkheads(repo._activebookmark)
376 376 curhead = repo[repo._activebookmark].node()
377 377 if repo.revs('%n and parents()', curhead):
378 378 # we are on the active bookmark
379 379 bmheads = [b for b in bmheads if curhead != b]
380 380 if bmheads:
381 381 msg = _('%i other divergent bookmarks for "%s"\n')
382 382 ui.status(msg % (len(bmheads), repo._activebookmark))
383 383
384 384 def _statusotherbranchheads(ui, repo):
385 385 currentbranch = repo.dirstate.branch()
386 386 allheads = repo.branchheads(currentbranch, closed=True)
387 387 heads = repo.branchheads(currentbranch)
388 388 if repo.revs('%ln and parents()', allheads):
389 389 # we are on a head, even though it might be closed
390 390 #
391 391 # on closed otherheads
392 392 # ========= ==========
393 393 # o 0 all heads for current branch are closed
394 394 # N only descendant branch heads are closed
395 395 # x 0 there is only one non-closed branch head
396 396 # N there are some non-closed branch heads
397 397 # ========= ==========
398 398 otherheads = repo.revs('%ln - parents()', heads)
399 399 if repo['.'].closesbranch():
400 ui.status(_('no open descendant heads on branch "%s", '
400 ui.warn(_('no open descendant heads on branch "%s", '
401 401 'updating to a closed head\n') %
402 402 (currentbranch))
403 403 if otherheads:
404 ui.status(_('(committing will reopen the head, '
404 ui.warn(_('(committing will reopen the head, '
405 405 'use `hg heads .` to see %i other heads)\n') %
406 406 (len(otherheads)))
407 407 else:
408 ui.status(_('(committing will reopen branch "%s")\n') %
408 ui.warn(_('(committing will reopen branch "%s")\n') %
409 409 (currentbranch))
410 410 elif otherheads:
411 411 ui.status(_('%i other heads for branch "%s"\n') %
412 412 (len(otherheads), currentbranch))
413 413
414 414 def statusotherdests(ui, repo):
415 415 """Print message about other head"""
416 416 # XXX we should probably include a hint:
417 417 # - about what to do
418 418 # - how to see such heads
419 419 if repo._activebookmark:
420 420 _statusotherbook(ui, repo)
421 421 else:
422 422 _statusotherbranchheads(ui, repo)
General Comments 0
You need to be logged in to leave comments. Login now