##// END OF EJS Templates
destutil: use scmutil.revrange for desthistedit (issue5001)...
Gregory Szorc -
r27559:d13bcc9f default
parent child Browse files
Show More
@@ -1,217 +1,220
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.branchtip(wc.branch())
95 node = repo.branchtip(wc.branch())
96 if bookmarks.isactivewdirparent(repo):
96 if bookmarks.isactivewdirparent(repo):
97 movemark = repo['.'].node()
97 movemark = repo['.'].node()
98 except error.RepoLookupError:
98 except error.RepoLookupError:
99 if wc.branch() == 'default': # no default branch!
99 if wc.branch() == 'default': # no default branch!
100 node = repo.lookup('tip') # update to tip
100 node = repo.lookup('tip') # update to tip
101 else:
101 else:
102 raise error.Abort(_("branch %s not found") % wc.branch())
102 raise error.Abort(_("branch %s not found") % wc.branch())
103 return node, movemark, None
103 return node, movemark, None
104
104
105 # order in which each step should be evalutated
105 # order in which each step should be evalutated
106 # steps are run until one finds a destination
106 # steps are run until one finds a destination
107 destupdatesteps = ['evolution', 'bookmark', 'branch']
107 destupdatesteps = ['evolution', 'bookmark', 'branch']
108 # mapping to ease extension overriding steps.
108 # mapping to ease extension overriding steps.
109 destupdatestepmap = {'evolution': _destupdateobs,
109 destupdatestepmap = {'evolution': _destupdateobs,
110 'bookmark': _destupdatebook,
110 'bookmark': _destupdatebook,
111 'branch': _destupdatebranch,
111 'branch': _destupdatebranch,
112 }
112 }
113
113
114 def destupdate(repo, clean=False, check=False):
114 def destupdate(repo, clean=False, check=False):
115 """destination for bare update operation
115 """destination for bare update operation
116
116
117 return (rev, movemark, activemark)
117 return (rev, movemark, activemark)
118
118
119 - rev: the revision to update to,
119 - rev: the revision to update to,
120 - movemark: node to move the active bookmark from
120 - movemark: node to move the active bookmark from
121 (cf bookmark.calculate update),
121 (cf bookmark.calculate update),
122 - activemark: a bookmark to activate at the end of the update.
122 - activemark: a bookmark to activate at the end of the update.
123 """
123 """
124 node = movemark = activemark = None
124 node = movemark = activemark = None
125
125
126 for step in destupdatesteps:
126 for step in destupdatesteps:
127 node, movemark, activemark = destupdatestepmap[step](repo, clean, check)
127 node, movemark, activemark = destupdatestepmap[step](repo, clean, check)
128 if node is not None:
128 if node is not None:
129 break
129 break
130 rev = repo[node].rev()
130 rev = repo[node].rev()
131
131
132 _destupdatevalidate(repo, rev, clean, check)
132 _destupdatevalidate(repo, rev, clean, check)
133
133
134 return rev, movemark, activemark
134 return rev, movemark, activemark
135
135
136 def _destmergebook(repo):
136 def _destmergebook(repo):
137 """find merge destination in the active bookmark case"""
137 """find merge destination in the active bookmark case"""
138 node = None
138 node = None
139 bmheads = repo.bookmarkheads(repo._activebookmark)
139 bmheads = repo.bookmarkheads(repo._activebookmark)
140 curhead = repo[repo._activebookmark].node()
140 curhead = repo[repo._activebookmark].node()
141 if len(bmheads) == 2:
141 if len(bmheads) == 2:
142 if curhead == bmheads[0]:
142 if curhead == bmheads[0]:
143 node = bmheads[1]
143 node = bmheads[1]
144 else:
144 else:
145 node = bmheads[0]
145 node = bmheads[0]
146 elif len(bmheads) > 2:
146 elif len(bmheads) > 2:
147 raise error.Abort(_("multiple matching bookmarks to merge - "
147 raise error.Abort(_("multiple matching bookmarks to merge - "
148 "please merge with an explicit rev or bookmark"),
148 "please merge with an explicit rev or bookmark"),
149 hint=_("run 'hg heads' to see all heads"))
149 hint=_("run 'hg heads' to see all heads"))
150 elif len(bmheads) <= 1:
150 elif len(bmheads) <= 1:
151 raise error.Abort(_("no matching bookmark to merge - "
151 raise error.Abort(_("no matching bookmark to merge - "
152 "please merge with an explicit rev or bookmark"),
152 "please merge with an explicit rev or bookmark"),
153 hint=_("run 'hg heads' to see all heads"))
153 hint=_("run 'hg heads' to see all heads"))
154 assert node is not None
154 assert node is not None
155 return node
155 return node
156
156
157 def _destmergebranch(repo):
157 def _destmergebranch(repo):
158 """find merge destination based on branch heads"""
158 """find merge destination based on branch heads"""
159 node = None
159 node = None
160 branch = repo[None].branch()
160 branch = repo[None].branch()
161 bheads = repo.branchheads(branch)
161 bheads = repo.branchheads(branch)
162 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
162 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
163
163
164 if len(nbhs) > 2:
164 if len(nbhs) > 2:
165 raise error.Abort(_("branch '%s' has %d heads - "
165 raise error.Abort(_("branch '%s' has %d heads - "
166 "please merge with an explicit rev")
166 "please merge with an explicit rev")
167 % (branch, len(bheads)),
167 % (branch, len(bheads)),
168 hint=_("run 'hg heads .' to see heads"))
168 hint=_("run 'hg heads .' to see heads"))
169
169
170 parent = repo.dirstate.p1()
170 parent = repo.dirstate.p1()
171 if len(nbhs) <= 1:
171 if len(nbhs) <= 1:
172 if len(bheads) > 1:
172 if len(bheads) > 1:
173 raise error.Abort(_("heads are bookmarked - "
173 raise error.Abort(_("heads are bookmarked - "
174 "please merge with an explicit rev"),
174 "please merge with an explicit rev"),
175 hint=_("run 'hg heads' to see all heads"))
175 hint=_("run 'hg heads' to see all heads"))
176 if len(repo.heads()) > 1:
176 if len(repo.heads()) > 1:
177 raise error.Abort(_("branch '%s' has one head - "
177 raise error.Abort(_("branch '%s' has one head - "
178 "please merge with an explicit rev")
178 "please merge with an explicit rev")
179 % branch,
179 % branch,
180 hint=_("run 'hg heads' to see all heads"))
180 hint=_("run 'hg heads' to see all heads"))
181 msg, hint = _('nothing to merge'), None
181 msg, hint = _('nothing to merge'), None
182 if parent != repo.lookup(branch):
182 if parent != repo.lookup(branch):
183 hint = _("use 'hg update' instead")
183 hint = _("use 'hg update' instead")
184 raise error.Abort(msg, hint=hint)
184 raise error.Abort(msg, hint=hint)
185
185
186 if parent not in bheads:
186 if parent not in bheads:
187 raise error.Abort(_('working directory not at a head revision'),
187 raise error.Abort(_('working directory not at a head revision'),
188 hint=_("use 'hg update' or merge with an "
188 hint=_("use 'hg update' or merge with an "
189 "explicit revision"))
189 "explicit revision"))
190 if parent == nbhs[0]:
190 if parent == nbhs[0]:
191 node = nbhs[-1]
191 node = nbhs[-1]
192 else:
192 else:
193 node = nbhs[0]
193 node = nbhs[0]
194 assert node is not None
194 assert node is not None
195 return node
195 return node
196
196
197 def destmerge(repo):
197 def destmerge(repo):
198 if repo._activebookmark:
198 if repo._activebookmark:
199 node = _destmergebook(repo)
199 node = _destmergebook(repo)
200 else:
200 else:
201 node = _destmergebranch(repo)
201 node = _destmergebranch(repo)
202 return repo[node].rev()
202 return repo[node].rev()
203
203
204 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
204 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())'
205
205
206 def desthistedit(ui, repo):
206 def desthistedit(ui, repo):
207 """Default base revision to edit for `hg histedit`."""
207 """Default base revision to edit for `hg histedit`."""
208 # Avoid cycle: scmutil -> revset -> destutil
209 from . import scmutil
210
208 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
211 default = ui.config('histedit', 'defaultrev', histeditdefaultrevset)
209 if default:
212 if default:
210 revs = repo.revs(default)
213 revs = scmutil.revrange(repo, [default])
211 if revs:
214 if revs:
212 # The revset supplied by the user may not be in ascending order nor
215 # The revset supplied by the user may not be in ascending order nor
213 # take the first revision. So do this manually.
216 # take the first revision. So do this manually.
214 revs.sort()
217 revs.sort()
215 return revs.first()
218 return revs.first()
216
219
217 return None
220 return None
General Comments 0
You need to be logged in to leave comments. Login now