Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/destutil.py @ 28139:5476a7a039c0
destutil: allow to specify an explicit source for the merge
We can now specify from where the merge is performed. The experimental revset
is updated to take revisions as argument, allowing to test the feature.
This will become very useful for pick the 'rebase' default destination. For this
reason, we also exclude all descendants from the rebased set from the candidate
destinations. This descendants exclusion was not necessary for merge as default
destination would not be picked from anything else than a head.
I'm not super excited with the current error messages, but I would prefer to
delay an overall messages rework once 'hg rebase' is done getting a default
destination aligned with 'hg merge'.
author | Pierre-Yves David <pierre-yves.david@fb.com> |
---|---|
date | Mon, 08 Feb 2016 19:32:29 +0100 |
parents | 5ad2017454ee |
children | 276644ae9e8d |
comparison
equal
deleted
inserted
replaced
28138:5ad2017454ee | 28139:5476a7a039c0 |
---|---|
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 'emptysourceset': | |
189 {'merge': | |
190 (_('source set is empty'), | |
191 None) | |
192 }, | |
193 'multiplebranchessourceset': | |
194 {'merge': | |
195 (_('source set is rooted in multiple branches'), | |
196 None) | |
197 }, | |
188 } | 198 } |
189 | 199 |
190 def _destmergebook(repo, action='merge'): | 200 def _destmergebook(repo, action='merge', sourceset=None): |
191 """find merge destination in the active bookmark case""" | 201 """find merge destination in the active bookmark case""" |
192 node = None | 202 node = None |
193 bmheads = repo.bookmarkheads(repo._activebookmark) | 203 bmheads = repo.bookmarkheads(repo._activebookmark) |
194 curhead = repo[repo._activebookmark].node() | 204 curhead = repo[repo._activebookmark].node() |
195 if len(bmheads) == 2: | 205 if len(bmheads) == 2: |
204 msg, hint = msgdestmerge['nootherbookmarks'][action] | 214 msg, hint = msgdestmerge['nootherbookmarks'][action] |
205 raise error.Abort(msg, hint=hint) | 215 raise error.Abort(msg, hint=hint) |
206 assert node is not None | 216 assert node is not None |
207 return node | 217 return node |
208 | 218 |
209 def _destmergebranch(repo, action='merge'): | 219 def _destmergebranch(repo, action='merge', sourceset=None): |
210 """find merge destination based on branch heads""" | 220 """find merge destination based on branch heads""" |
211 node = None | 221 node = None |
212 parent = repo.dirstate.p1() | 222 |
213 branch = repo.dirstate.branch() | 223 if sourceset is None: |
224 sourceset = [repo[repo.dirstate.p1()].rev()] | |
225 branch = repo.dirstate.branch() | |
226 elif not sourceset: | |
227 msg, hint = msgdestmerge['emptysourceset'][action] | |
228 raise error.Abort(msg, hint=hint) | |
229 else: | |
230 branch = None | |
231 for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset): | |
232 if branch is not None and ctx.branch() != branch: | |
233 msg, hint = msgdestmerge['multiplebranchessourceset'][action] | |
234 raise error.Abort(msg, hint=hint) | |
235 branch = ctx.branch() | |
236 | |
214 bheads = repo.branchheads(branch) | 237 bheads = repo.branchheads(branch) |
215 | 238 if not repo.revs('%ld and %ln', sourceset, bheads): |
216 if parent not in bheads: | 239 # Case A: working copy if not on a head. (merge only) |
217 # Case A: working copy if not on a head. | |
218 # | 240 # |
219 # This is probably a user mistake We bailout pointing at 'hg update' | 241 # This is probably a user mistake We bailout pointing at 'hg update' |
220 if len(repo.heads()) <= 1: | 242 if len(repo.heads()) <= 1: |
221 msg, hint = msgdestmerge['nootherheadsbehind'][action] | 243 msg, hint = msgdestmerge['nootherheadsbehind'][action] |
222 else: | 244 else: |
223 msg, hint = msgdestmerge['notatheads'][action] | 245 msg, hint = msgdestmerge['notatheads'][action] |
224 raise error.Abort(msg, hint=hint) | 246 raise error.Abort(msg, hint=hint) |
225 # remove current head from the set | 247 # remove heads descendants of source from the set |
226 bheads = [bh for bh in bheads if bh != parent] | 248 bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset)) |
227 # filters out bookmarked heads | 249 # filters out bookmarked heads |
228 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()] | 250 nbhs = list(repo.revs('%ld - bookmark()', bheads)) |
229 if len(nbhs) > 1: | 251 if len(nbhs) > 1: |
230 # Case B: There is more than 1 other anonymous heads | 252 # Case B: There is more than 1 other anonymous heads |
231 # | 253 # |
232 # This means that there will be more than 1 candidate. This is | 254 # This means that there will be more than 1 candidate. This is |
233 # ambiguous. We abort asking the user to pick as explicit destination | 255 # ambiguous. We abort asking the user to pick as explicit destination |
251 else: | 273 else: |
252 node = nbhs[0] | 274 node = nbhs[0] |
253 assert node is not None | 275 assert node is not None |
254 return node | 276 return node |
255 | 277 |
256 def destmerge(repo, action='merge'): | 278 def destmerge(repo, action='merge', sourceset=None): |
257 """return the default destination for a merge | 279 """return the default destination for a merge |
258 | 280 |
259 (or raise exception about why it can't pick one) | 281 (or raise exception about why it can't pick one) |
260 | 282 |
261 :action: the action being performed, controls emitted error message | 283 :action: the action being performed, controls emitted error message |
262 """ | 284 """ |
263 if repo._activebookmark: | 285 if repo._activebookmark: |
264 node = _destmergebook(repo, action=action) | 286 node = _destmergebook(repo, action=action, sourceset=sourceset) |
265 else: | 287 else: |
266 node = _destmergebranch(repo, action=action) | 288 node = _destmergebranch(repo, action=action, sourceset=sourceset) |
267 return repo[node].rev() | 289 return repo[node].rev() |
268 | 290 |
269 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())' | 291 histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())' |
270 | 292 |
271 def desthistedit(ui, repo): | 293 def desthistedit(ui, repo): |