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