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