##// END OF EJS Templates
destutil: ensure we offer 'hg update' hint when not at head in all cases...
Pierre-Yves David -
r28161:3324345a default
parent child Browse files
Show More
@@ -1,339 +1,344
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 wc = repo[None]
93 93 movemark = node = None
94 94 try:
95 95 node = repo.revs('max(.::(head() and branch(%s)))'
96 96 , wc.branch()).first()
97 97 if bookmarks.isactivewdirparent(repo):
98 98 movemark = repo['.'].node()
99 99 except error.RepoLookupError:
100 100 if wc.branch() == 'default': # no default branch!
101 101 node = repo.lookup('tip') # update to tip
102 102 else:
103 103 raise error.Abort(_("branch %s not found") % wc.branch())
104 104 return node, movemark, None
105 105
106 106 # order in which each step should be evalutated
107 107 # steps are run until one finds a destination
108 108 destupdatesteps = ['evolution', 'bookmark', 'branch']
109 109 # mapping to ease extension overriding steps.
110 110 destupdatestepmap = {'evolution': _destupdateobs,
111 111 'bookmark': _destupdatebook,
112 112 'branch': _destupdatebranch,
113 113 }
114 114
115 115 def destupdate(repo, clean=False, check=False):
116 116 """destination for bare update operation
117 117
118 118 return (rev, movemark, activemark)
119 119
120 120 - rev: the revision to update to,
121 121 - movemark: node to move the active bookmark from
122 122 (cf bookmark.calculate update),
123 123 - activemark: a bookmark to activate at the end of the update.
124 124 """
125 125 node = movemark = activemark = None
126 126
127 127 for step in destupdatesteps:
128 128 node, movemark, activemark = destupdatestepmap[step](repo, clean, check)
129 129 if node is not None:
130 130 break
131 131 rev = repo[node].rev()
132 132
133 133 _destupdatevalidate(repo, rev, clean, check)
134 134
135 135 return rev, movemark, activemark
136 136
137 137 msgdestmerge = {
138 138 # too many matching divergent bookmark
139 139 'toomanybookmarks':
140 140 {'merge':
141 141 (_("multiple matching bookmarks to merge -"
142 142 " please merge with an explicit rev or bookmark"),
143 143 _("run 'hg heads' to see all heads")),
144 144 },
145 145 # no other matching divergent bookmark
146 146 'nootherbookmarks':
147 147 {'merge':
148 148 (_("no matching bookmark to merge - "
149 149 "please merge with an explicit rev or bookmark"),
150 150 _("run 'hg heads' to see all heads")),
151 151 },
152 152 # branch have too many unbookmarked heads, no obvious destination
153 153 'toomanyheads':
154 154 {'merge':
155 155 (_("branch '%s' has %d heads - please merge with an explicit rev"),
156 156 _("run 'hg heads .' to see heads")),
157 157 },
158 158 # branch have no other unbookmarked heads
159 159 'bookmarkedheads':
160 160 {'merge':
161 161 (_("heads are bookmarked - please merge with an explicit rev"),
162 162 _("run 'hg heads' to see all heads")),
163 163 },
164 164 # branch have just a single heads, but there is other branches
165 165 'nootherbranchheads':
166 166 {'merge':
167 167 (_("branch '%s' has one head - please merge with an explicit rev"),
168 168 _("run 'hg heads' to see all heads")),
169 169 },
170 170 # repository have a single head
171 171 'nootherheads':
172 172 {'merge':
173 173 (_('nothing to merge'),
174 174 None),
175 175 },
176 176 # repository have a single head and we are not on it
177 177 'nootherheadsbehind':
178 178 {'merge':
179 179 (_('nothing to merge'),
180 180 _("use 'hg update' instead")),
181 181 },
182 182 # We are not on a head
183 183 'notatheads':
184 184 {'merge':
185 185 (_('working directory not at a head revision'),
186 186 _("use 'hg update' or merge with an explicit revision"))
187 187 },
188 188 'emptysourceset':
189 189 {'merge':
190 190 (_('source set is empty'),
191 191 None)
192 192 },
193 193 'multiplebranchessourceset':
194 194 {'merge':
195 195 (_('source set is rooted in multiple branches'),
196 196 None)
197 197 },
198 198 }
199 199
200 200 def _destmergebook(repo, action='merge', sourceset=None):
201 201 """find merge destination in the active bookmark case"""
202 202 node = None
203 203 bmheads = repo.bookmarkheads(repo._activebookmark)
204 204 curhead = repo[repo._activebookmark].node()
205 205 if len(bmheads) == 2:
206 206 if curhead == bmheads[0]:
207 207 node = bmheads[1]
208 208 else:
209 209 node = bmheads[0]
210 210 elif len(bmheads) > 2:
211 211 msg, hint = msgdestmerge['toomanybookmarks'][action]
212 212 raise error.ManyMergeDestAbort(msg, hint=hint)
213 213 elif len(bmheads) <= 1:
214 214 msg, hint = msgdestmerge['nootherbookmarks'][action]
215 215 raise error.NoMergeDestAbort(msg, hint=hint)
216 216 assert node is not None
217 217 return node
218 218
219 219 def _destmergebranch(repo, action='merge', sourceset=None, onheadcheck=True):
220 220 """find merge destination based on branch heads"""
221 221 node = None
222 222
223 223 if sourceset is None:
224 224 sourceset = [repo[repo.dirstate.p1()].rev()]
225 225 branch = repo.dirstate.branch()
226 226 elif not sourceset:
227 227 msg, hint = msgdestmerge['emptysourceset'][action]
228 228 raise error.NoMergeDestAbort(msg, hint=hint)
229 229 else:
230 230 branch = None
231 231 for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset):
232 232 if branch is not None and ctx.branch() != branch:
233 233 msg, hint = msgdestmerge['multiplebranchessourceset'][action]
234 234 raise error.ManyMergeDestAbort(msg, hint=hint)
235 235 branch = ctx.branch()
236 236
237 237 bheads = repo.branchheads(branch)
238 if onheadcheck and not repo.revs('%ld and %ln', sourceset, bheads):
238 onhead = repo.revs('%ld and %ln', sourceset, bheads)
239 if onheadcheck and not onhead:
239 240 # Case A: working copy if not on a head. (merge only)
240 241 #
241 242 # This is probably a user mistake We bailout pointing at 'hg update'
242 243 if len(repo.heads()) <= 1:
243 244 msg, hint = msgdestmerge['nootherheadsbehind'][action]
244 245 else:
245 246 msg, hint = msgdestmerge['notatheads'][action]
246 247 raise error.Abort(msg, hint=hint)
247 248 # remove heads descendants of source from the set
248 249 bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset))
249 250 # filters out bookmarked heads
250 251 nbhs = list(repo.revs('%ld - bookmark()', bheads))
251 252 if len(nbhs) > 1:
252 253 # Case B: There is more than 1 other anonymous heads
253 254 #
254 255 # This means that there will be more than 1 candidate. This is
255 256 # ambiguous. We abort asking the user to pick as explicit destination
256 257 # instead.
257 258 msg, hint = msgdestmerge['toomanyheads'][action]
258 259 msg %= (branch, len(bheads) + 1)
259 260 raise error.ManyMergeDestAbort(msg, hint=hint)
260 261 elif not nbhs:
261 262 # Case B: There is no other anonymous heads
262 263 #
263 264 # This means that there is no natural candidate to merge with.
264 265 # We abort, with various messages for various cases.
265 266 if bheads:
266 267 msg, hint = msgdestmerge['bookmarkedheads'][action]
267 268 elif len(repo.heads()) > 1:
268 269 msg, hint = msgdestmerge['nootherbranchheads'][action]
269 270 msg %= branch
271 elif not onhead:
272 # if 'onheadcheck == False' (rebase case),
273 # this was not caught in Case A.
274 msg, hint = msgdestmerge['nootherheadsbehind'][action]
270 275 else:
271 276 msg, hint = msgdestmerge['nootherheads'][action]
272 277 raise error.NoMergeDestAbort(msg, hint=hint)
273 278 else:
274 279 node = nbhs[0]
275 280 assert node is not None
276 281 return node
277 282
278 283 def destmerge(repo, action='merge', sourceset=None, onheadcheck=True):
279 284 """return the default destination for a merge
280 285
281 286 (or raise exception about why it can't pick one)
282 287
283 288 :action: the action being performed, controls emitted error message
284 289 """
285 290 if repo._activebookmark:
286 291 node = _destmergebook(repo, action=action, sourceset=sourceset)
287 292 else:
288 293 node = _destmergebranch(repo, action=action, sourceset=sourceset,
289 294 onheadcheck=onheadcheck)
290 295 return repo[node].rev()
291 296
292 297 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
293 298
294 299 def desthistedit(ui, repo):
295 300 """Default base revision to edit for `hg histedit`."""
296 301 # Avoid cycle: scmutil -> revset -> destutil
297 302 from . import scmutil
298 303
299 304 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
300 305 if default:
301 306 revs = scmutil.revrange(repo, [default])
302 307 if revs:
303 308 # The revset supplied by the user may not be in ascending order nor
304 309 # take the first revision. So do this manually.
305 310 revs.sort()
306 311 return revs.first()
307 312
308 313 return None
309 314
310 315 def _statusotherbook(ui, repo):
311 316 bmheads = repo.bookmarkheads(repo._activebookmark)
312 317 curhead = repo[repo._activebookmark].node()
313 318 if repo.revs('%n and parents()', curhead):
314 319 # we are on the active bookmark
315 320 bmheads = [b for b in bmheads if curhead != b]
316 321 if bmheads:
317 322 msg = _('%i other divergent bookmarks for "%s"\n')
318 323 ui.status(msg % (len(bmheads), repo._activebookmark))
319 324
320 325 def _statusotherbranchheads(ui, repo):
321 326 currentbranch = repo.dirstate.branch()
322 327 heads = repo.branchheads(currentbranch)
323 328 l = len(heads)
324 329 if repo.revs('%ln and parents()', heads):
325 330 # we are on a head
326 331 heads = repo.revs('%ln - parents()', heads)
327 332 if heads and l != len(heads):
328 333 ui.status(_('%i other heads for branch "%s"\n') %
329 334 (len(heads), currentbranch))
330 335
331 336 def statusotherdests(ui, repo):
332 337 """Print message about other head"""
333 338 # XXX we should probably include a hint:
334 339 # - about what to do
335 340 # - how to see such heads
336 341 if repo._activebookmark:
337 342 _statusotherbook(ui, repo)
338 343 else:
339 344 _statusotherbranchheads(ui, repo)
General Comments 0
You need to be logged in to leave comments. Login now