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):