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