|
1 # destutil.py - Mercurial utility function for command destination |
|
2 # |
|
3 # Copyright Matt Mackall <mpm@selenic.com> and other |
|
4 # |
|
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. |
|
7 |
|
8 from .i18n import _ |
|
9 from . import ( |
|
10 bookmarks, |
|
11 error, |
|
12 obsolete, |
|
13 ) |
|
14 |
|
15 def _destupdatevalidate(repo, rev, clean, check): |
|
16 """validate that the destination comply to various rules |
|
17 |
|
18 This exists as its own function to help wrapping from extensions.""" |
|
19 wc = repo[None] |
|
20 p1 = wc.p1() |
|
21 if not clean: |
|
22 # Check that the update is linear. |
|
23 # |
|
24 # Mercurial do not allow update-merge for non linear pattern |
|
25 # (that would be technically possible but was considered too confusing |
|
26 # for user a long time ago) |
|
27 # |
|
28 # See mercurial.merge.update for details |
|
29 if p1.rev() not in repo.changelog.ancestors([rev], inclusive=True): |
|
30 dirty = wc.dirty(missing=True) |
|
31 foreground = obsolete.foreground(repo, [p1.node()]) |
|
32 if not repo[rev].node() in foreground: |
|
33 if dirty: |
|
34 msg = _("uncommitted changes") |
|
35 hint = _("commit and merge, or update --clean to" |
|
36 " discard changes") |
|
37 raise error.UpdateAbort(msg, hint=hint) |
|
38 elif not check: # destination is not a descendant. |
|
39 msg = _("not a linear update") |
|
40 hint = _("merge or update --check to force update") |
|
41 raise error.UpdateAbort(msg, hint=hint) |
|
42 |
|
43 def _destupdateobs(repo, clean, check): |
|
44 """decide of an update destination from obsolescence markers""" |
|
45 node = None |
|
46 wc = repo[None] |
|
47 p1 = wc.p1() |
|
48 movemark = None |
|
49 |
|
50 if p1.obsolete() and not p1.children(): |
|
51 # allow updating to successors |
|
52 successors = obsolete.successorssets(repo, p1.node()) |
|
53 |
|
54 # behavior of certain cases is as follows, |
|
55 # |
|
56 # divergent changesets: update to highest rev, similar to what |
|
57 # is currently done when there are more than one head |
|
58 # (i.e. 'tip') |
|
59 # |
|
60 # replaced changesets: same as divergent except we know there |
|
61 # is no conflict |
|
62 # |
|
63 # pruned changeset: no update is done; though, we could |
|
64 # consider updating to the first non-obsolete parent, |
|
65 # similar to what is current done for 'hg prune' |
|
66 |
|
67 if successors: |
|
68 # flatten the list here handles both divergent (len > 1) |
|
69 # and the usual case (len = 1) |
|
70 successors = [n for sub in successors for n in sub] |
|
71 |
|
72 # get the max revision for the given successors set, |
|
73 # i.e. the 'tip' of a set |
|
74 node = repo.revs('max(%ln)', successors).first() |
|
75 if bookmarks.isactivewdirparent(repo): |
|
76 movemark = repo['.'].node() |
|
77 return node, movemark, None |
|
78 |
|
79 def _destupdatebook(repo, clean, check): |
|
80 """decide on an update destination from active bookmark""" |
|
81 # we also move the active bookmark, if any |
|
82 activemark = None |
|
83 node, movemark = bookmarks.calculateupdate(repo.ui, repo, None) |
|
84 if node is not None: |
|
85 activemark = node |
|
86 return node, movemark, activemark |
|
87 |
|
88 def _destupdatebranch(repo, clean, check): |
|
89 """decide on an update destination from current branch""" |
|
90 wc = repo[None] |
|
91 movemark = node = None |
|
92 try: |
|
93 node = repo.branchtip(wc.branch()) |
|
94 if bookmarks.isactivewdirparent(repo): |
|
95 movemark = repo['.'].node() |
|
96 except error.RepoLookupError: |
|
97 if wc.branch() == 'default': # no default branch! |
|
98 node = repo.lookup('tip') # update to tip |
|
99 else: |
|
100 raise error.Abort(_("branch %s not found") % wc.branch()) |
|
101 return node, movemark, None |
|
102 |
|
103 # order in which each step should be evalutated |
|
104 # steps are run until one finds a destination |
|
105 destupdatesteps = ['evolution', 'bookmark', 'branch'] |
|
106 # mapping to ease extension overriding steps. |
|
107 destupdatestepmap = {'evolution': _destupdateobs, |
|
108 'bookmark': _destupdatebook, |
|
109 'branch': _destupdatebranch, |
|
110 } |
|
111 |
|
112 def destupdate(repo, clean=False, check=False): |
|
113 """destination for bare update operation |
|
114 |
|
115 return (rev, movemark, activemark) |
|
116 |
|
117 - rev: the revision to update to, |
|
118 - movemark: node to move the active bookmark from |
|
119 (cf bookmark.calculate update), |
|
120 - activemark: a bookmark to activate at the end of the update. |
|
121 """ |
|
122 node = movemark = activemark = None |
|
123 |
|
124 for step in destupdatesteps: |
|
125 node, movemark, activemark = destupdatestepmap[step](repo, clean, check) |
|
126 if node is not None: |
|
127 break |
|
128 rev = repo[node].rev() |
|
129 |
|
130 _destupdatevalidate(repo, rev, clean, check) |
|
131 |
|
132 return rev, movemark, activemark |
|
133 |
|
134 def _destmergebook(repo): |
|
135 """find merge destination in the active bookmark case""" |
|
136 node = None |
|
137 bmheads = repo.bookmarkheads(repo._activebookmark) |
|
138 curhead = repo[repo._activebookmark].node() |
|
139 if len(bmheads) == 2: |
|
140 if curhead == bmheads[0]: |
|
141 node = bmheads[1] |
|
142 else: |
|
143 node = bmheads[0] |
|
144 elif len(bmheads) > 2: |
|
145 raise error.Abort(_("multiple matching bookmarks to merge - " |
|
146 "please merge with an explicit rev or bookmark"), |
|
147 hint=_("run 'hg heads' to see all heads")) |
|
148 elif len(bmheads) <= 1: |
|
149 raise error.Abort(_("no matching bookmark to merge - " |
|
150 "please merge with an explicit rev or bookmark"), |
|
151 hint=_("run 'hg heads' to see all heads")) |
|
152 assert node is not None |
|
153 return node |
|
154 |
|
155 def _destmergebranch(repo): |
|
156 """find merge destination based on branch heads""" |
|
157 node = None |
|
158 branch = repo[None].branch() |
|
159 bheads = repo.branchheads(branch) |
|
160 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()] |
|
161 |
|
162 if len(nbhs) > 2: |
|
163 raise error.Abort(_("branch '%s' has %d heads - " |
|
164 "please merge with an explicit rev") |
|
165 % (branch, len(bheads)), |
|
166 hint=_("run 'hg heads .' to see heads")) |
|
167 |
|
168 parent = repo.dirstate.p1() |
|
169 if len(nbhs) <= 1: |
|
170 if len(bheads) > 1: |
|
171 raise error.Abort(_("heads are bookmarked - " |
|
172 "please merge with an explicit rev"), |
|
173 hint=_("run 'hg heads' to see all heads")) |
|
174 if len(repo.heads()) > 1: |
|
175 raise error.Abort(_("branch '%s' has one head - " |
|
176 "please merge with an explicit rev") |
|
177 % branch, |
|
178 hint=_("run 'hg heads' to see all heads")) |
|
179 msg, hint = _('nothing to merge'), None |
|
180 if parent != repo.lookup(branch): |
|
181 hint = _("use 'hg update' instead") |
|
182 raise error.Abort(msg, hint=hint) |
|
183 |
|
184 if parent not in bheads: |
|
185 raise error.Abort(_('working directory not at a head revision'), |
|
186 hint=_("use 'hg update' or merge with an " |
|
187 "explicit revision")) |
|
188 if parent == nbhs[0]: |
|
189 node = nbhs[-1] |
|
190 else: |
|
191 node = nbhs[0] |
|
192 assert node is not None |
|
193 return node |
|
194 |
|
195 def destmerge(repo): |
|
196 if repo._activebookmark: |
|
197 node = _destmergebook(repo) |
|
198 else: |
|
199 node = _destmergebranch(repo) |
|
200 return repo[node].rev() |