##// END OF EJS Templates
destutil: document various failure cases...
Pierre-Yves David -
r28105:1fc7b536 default
parent child Browse files
Show More
@@ -1,284 +1,293 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 (_("multiple matching bookmarks to merge -"
140 (_("multiple matching bookmarks to merge -"
141 " please merge with an explicit rev or bookmark"),
141 " please merge with an explicit rev or bookmark"),
142 _("run 'hg heads' to see all heads")),
142 _("run 'hg heads' to see all heads")),
143 # no other matching divergent bookmark
143 # no other matching divergent bookmark
144 'nootherbookmarks':
144 'nootherbookmarks':
145 (_("no matching bookmark to merge - "
145 (_("no matching bookmark to merge - "
146 "please merge with an explicit rev or bookmark"),
146 "please merge with an explicit rev or bookmark"),
147 _("run 'hg heads' to see all heads")),
147 _("run 'hg heads' to see all heads")),
148 # branch have too many unbookmarked heads, no obvious destination
148 # branch have too many unbookmarked heads, no obvious destination
149 'toomanyheads':
149 'toomanyheads':
150 (_("branch '%s' has %d heads - please merge with an explicit rev"),
150 (_("branch '%s' has %d heads - please merge with an explicit rev"),
151 _("run 'hg heads .' to see heads")),
151 _("run 'hg heads .' to see heads")),
152 # branch have no other unbookmarked heads
152 # branch have no other unbookmarked heads
153 'bookmarkedheads':
153 'bookmarkedheads':
154 (_("heads are bookmarked - please merge with an explicit rev"),
154 (_("heads are bookmarked - please merge with an explicit rev"),
155 _("run 'hg heads' to see all heads")),
155 _("run 'hg heads' to see all heads")),
156 # branch have just a single heads, but there is other branches
156 # branch have just a single heads, but there is other branches
157 'nootherbranchheads':
157 'nootherbranchheads':
158 (_("branch '%s' has one head - please merge with an explicit rev"),
158 (_("branch '%s' has one head - please merge with an explicit rev"),
159 _("run 'hg heads' to see all heads")),
159 _("run 'hg heads' to see all heads")),
160 # repository have a single head
160 # repository have a single head
161 'nootherheads':
161 'nootherheads':
162 (_('nothing to merge'),
162 (_('nothing to merge'),
163 None),
163 None),
164 # repository have a single head and we are not on it
164 # repository have a single head and we are not on it
165 'nootherheadsbehind':
165 'nootherheadsbehind':
166 (_('nothing to merge'),
166 (_('nothing to merge'),
167 _("use 'hg update' instead")),
167 _("use 'hg update' instead")),
168 # We are not on a head
168 # We are not on a head
169 'notatheads':
169 'notatheads':
170 (_('working directory not at a head revision'),
170 (_('working directory not at a head revision'),
171 _("use 'hg update' or merge with an explicit revision"))
171 _("use 'hg update' or merge with an explicit revision"))
172 }
172 }
173
173
174 def _destmergebook(repo):
174 def _destmergebook(repo):
175 """find merge destination in the active bookmark case"""
175 """find merge destination in the active bookmark case"""
176 node = None
176 node = None
177 bmheads = repo.bookmarkheads(repo._activebookmark)
177 bmheads = repo.bookmarkheads(repo._activebookmark)
178 curhead = repo[repo._activebookmark].node()
178 curhead = repo[repo._activebookmark].node()
179 if len(bmheads) == 2:
179 if len(bmheads) == 2:
180 if curhead == bmheads[0]:
180 if curhead == bmheads[0]:
181 node = bmheads[1]
181 node = bmheads[1]
182 else:
182 else:
183 node = bmheads[0]
183 node = bmheads[0]
184 elif len(bmheads) > 2:
184 elif len(bmheads) > 2:
185 msg, hint = msgdestmerge['toomanybookmarks']
185 msg, hint = msgdestmerge['toomanybookmarks']
186 raise error.Abort(msg, hint=hint)
186 raise error.Abort(msg, hint=hint)
187 elif len(bmheads) <= 1:
187 elif len(bmheads) <= 1:
188 msg, hint = msgdestmerge['nootherbookmarks']
188 msg, hint = msgdestmerge['nootherbookmarks']
189 raise error.Abort(msg, hint=hint)
189 raise error.Abort(msg, hint=hint)
190 assert node is not None
190 assert node is not None
191 return node
191 return node
192
192
193 def _destmergebranch(repo):
193 def _destmergebranch(repo):
194 """find merge destination based on branch heads"""
194 """find merge destination based on branch heads"""
195 node = None
195 node = None
196 parent = repo.dirstate.p1()
196 parent = repo.dirstate.p1()
197 branch = repo.dirstate.branch()
197 branch = repo.dirstate.branch()
198 bheads = repo.branchheads(branch)
198 bheads = repo.branchheads(branch)
199 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
199 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
200
200
201 if parent not in bheads:
201 if parent not in bheads:
202 # Case A: working copy if not on a head.
203 #
204 # This is probably a user mistake We bailout pointing at 'hg update'
202 if len(repo.heads()) <= 1:
205 if len(repo.heads()) <= 1:
203 msg, hint = msgdestmerge['nootherheadsbehind']
206 msg, hint = msgdestmerge['nootherheadsbehind']
204 else:
207 else:
205 msg, hint = msgdestmerge['notatheads']
208 msg, hint = msgdestmerge['notatheads']
206 raise error.Abort(msg, hint=hint)
209 raise error.Abort(msg, hint=hint)
207
210 elif len(nbhs) > 2:
208 if len(nbhs) > 2:
211 # Case B: There is more than 2 anonymous heads
212 #
213 # This means that there will be more than 1 candidate. This is
214 # ambiguous. We abort asking the user to pick as explicit destination
215 # instead.
209 msg, hint = msgdestmerge['toomanyheads']
216 msg, hint = msgdestmerge['toomanyheads']
210 msg %= (branch, len(bheads))
217 msg %= (branch, len(bheads))
211 raise error.Abort(msg, hint=hint)
218 raise error.Abort(msg, hint=hint)
212
219 elif len(nbhs) <= 1:
213 if len(nbhs) <= 1:
220 # Case B: There is no other anonymous head that the one we are one
221 #
222 # This means that there is no natural candidate to merge with.
223 # We abort, with various messages for various cases.
214 if len(bheads) > 1:
224 if len(bheads) > 1:
215 msg, hint = msgdestmerge['bookmarkedheads']
225 msg, hint = msgdestmerge['bookmarkedheads']
216 elif len(repo.heads()) > 1:
226 elif len(repo.heads()) > 1:
217 msg, hint = msgdestmerge['nootherbranchheads']
227 msg, hint = msgdestmerge['nootherbranchheads']
218 msg %= branch
228 msg %= branch
219 else:
229 else:
220 msg, hint = msgdestmerge['nootherheads']
230 msg, hint = msgdestmerge['nootherheads']
221 raise error.Abort(msg, hint=hint)
231 raise error.Abort(msg, hint=hint)
222
232 elif parent == nbhs[0]:
223 if parent == nbhs[0]:
224 node = nbhs[-1]
233 node = nbhs[-1]
225 else:
234 else:
226 node = nbhs[0]
235 node = nbhs[0]
227 assert node is not None
236 assert node is not None
228 return node
237 return node
229
238
230 def destmerge(repo):
239 def destmerge(repo):
231 if repo._activebookmark:
240 if repo._activebookmark:
232 node = _destmergebook(repo)
241 node = _destmergebook(repo)
233 else:
242 else:
234 node = _destmergebranch(repo)
243 node = _destmergebranch(repo)
235 return repo[node].rev()
244 return repo[node].rev()
236
245
237 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
246 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
238
247
239 def desthistedit(ui, repo):
248 def desthistedit(ui, repo):
240 """Default base revision to edit for `hg histedit`."""
249 """Default base revision to edit for `hg histedit`."""
241 # Avoid cycle: scmutil -> revset -> destutil
250 # Avoid cycle: scmutil -> revset -> destutil
242 from . import scmutil
251 from . import scmutil
243
252
244 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
253 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
245 if default:
254 if default:
246 revs = scmutil.revrange(repo, [default])
255 revs = scmutil.revrange(repo, [default])
247 if revs:
256 if revs:
248 # The revset supplied by the user may not be in ascending order nor
257 # The revset supplied by the user may not be in ascending order nor
249 # take the first revision. So do this manually.
258 # take the first revision. So do this manually.
250 revs.sort()
259 revs.sort()
251 return revs.first()
260 return revs.first()
252
261
253 return None
262 return None
254
263
255 def _statusotherbook(ui, repo):
264 def _statusotherbook(ui, repo):
256 bmheads = repo.bookmarkheads(repo._activebookmark)
265 bmheads = repo.bookmarkheads(repo._activebookmark)
257 curhead = repo[repo._activebookmark].node()
266 curhead = repo[repo._activebookmark].node()
258 if repo.revs('%n and parents()', curhead):
267 if repo.revs('%n and parents()', curhead):
259 # we are on the active bookmark
268 # we are on the active bookmark
260 bmheads = [b for b in bmheads if curhead != b]
269 bmheads = [b for b in bmheads if curhead != b]
261 if bmheads:
270 if bmheads:
262 msg = _('%i other divergent bookmarks for "%s"\n')
271 msg = _('%i other divergent bookmarks for "%s"\n')
263 ui.status(msg % (len(bmheads), repo._activebookmark))
272 ui.status(msg % (len(bmheads), repo._activebookmark))
264
273
265 def _statusotherbranchheads(ui, repo):
274 def _statusotherbranchheads(ui, repo):
266 currentbranch = repo.dirstate.branch()
275 currentbranch = repo.dirstate.branch()
267 heads = repo.branchheads(currentbranch)
276 heads = repo.branchheads(currentbranch)
268 l = len(heads)
277 l = len(heads)
269 if repo.revs('%ln and parents()', heads):
278 if repo.revs('%ln and parents()', heads):
270 # we are on a head
279 # we are on a head
271 heads = repo.revs('%ln - parents()', heads)
280 heads = repo.revs('%ln - parents()', heads)
272 if heads and l != len(heads):
281 if heads and l != len(heads):
273 ui.status(_('%i other heads for branch "%s"\n') %
282 ui.status(_('%i other heads for branch "%s"\n') %
274 (len(heads), currentbranch))
283 (len(heads), currentbranch))
275
284
276 def statusotherdests(ui, repo):
285 def statusotherdests(ui, repo):
277 """Print message about other head"""
286 """Print message about other head"""
278 # XXX we should probably include a hint:
287 # XXX we should probably include a hint:
279 # - about what to do
288 # - about what to do
280 # - how to see such heads
289 # - how to see such heads
281 if repo._activebookmark:
290 if repo._activebookmark:
282 _statusotherbook(ui, repo)
291 _statusotherbook(ui, repo)
283 else:
292 else:
284 _statusotherbranchheads(ui, repo)
293 _statusotherbranchheads(ui, repo)
General Comments 0
You need to be logged in to leave comments. Login now