##// END OF EJS Templates
rebase: always check if rebasing onto an applied mq patch....
Greg Ward -
r10672:c2e1e637 stable
parent child Browse files
Show More
@@ -1,538 +1,542 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 http://mercurial.selenic.com/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import util, repair, merge, cmdutil, commands, error
18 18 from mercurial import extensions, ancestor, copies, patch
19 19 from mercurial.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 nullmerge = -2
26 26
27 27 def rebase(ui, repo, **opts):
28 28 """move changeset (and descendants) to a different branch
29 29
30 30 Rebase uses repeated merging to graft changesets from one part of
31 31 history (the source) onto another (the destination). This can be
32 32 useful for linearizing local changes relative to a master
33 33 development tree.
34 34
35 35 If you don't specify a destination changeset (``-d/--dest``),
36 36 rebase uses the tipmost head of the current named branch as the
37 37 destination. (The destination changeset is not modified by
38 38 rebasing, but new changesets are added as its descendants.)
39 39
40 40 You can specify which changesets to rebase in two ways: as a
41 41 "source" changeset or as a "base" changeset. Both are shorthand
42 42 for a topologically related set of changesets (the "source
43 43 branch"). If you specify source (``-s/--source``), rebase will
44 44 rebase that changeset and all of its descendants onto dest. If you
45 45 specify base (``-b/--base``), rebase will select ancestors of base
46 46 back to but not including the common ancestor with dest. Thus,
47 47 ``-b`` is less precise but more convenient than ``-s``: you can
48 48 specify any changeset in the source branch, and rebase will select
49 49 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
50 50 uses the parent of the working directory as the base.
51 51
52 52 By default, rebase recreates the changesets in the source branch
53 53 as descendants of dest and then destroys the originals. Use
54 54 ``--keep`` to preserve the original source changesets. Some
55 55 changesets in the source branch (e.g. merges from the destination
56 56 branch) may be dropped if they no longer contribute any change.
57 57
58 58 One result of the rules for selecting the destination changeset
59 59 and source branch is that, unlike ``merge``, rebase will do
60 60 nothing if you are at the latest (tipmost) head of a named branch
61 61 with two heads. You need to explicitly specify source and/or
62 62 destination (or ``update`` to the other head, if it's the head of
63 63 the intended source branch).
64 64
65 65 If a rebase is interrupted to manually resolve a merge, it can be
66 66 continued with --continue/-c or aborted with --abort/-a.
67 67 """
68 68 originalwd = target = None
69 69 external = nullrev
70 70 state = {}
71 71 skipped = set()
72 72 targetancestors = set()
73 73
74 74 lock = wlock = None
75 75 try:
76 76 lock = repo.lock()
77 77 wlock = repo.wlock()
78 78
79 79 # Validate input and define rebasing points
80 80 destf = opts.get('dest', None)
81 81 srcf = opts.get('source', None)
82 82 basef = opts.get('base', None)
83 83 contf = opts.get('continue')
84 84 abortf = opts.get('abort')
85 85 collapsef = opts.get('collapse', False)
86 86 extrafn = opts.get('extrafn')
87 87 keepf = opts.get('keep', False)
88 88 keepbranchesf = opts.get('keepbranches', False)
89 89 detachf = opts.get('detach', False)
90 90
91 91 if contf or abortf:
92 92 if contf and abortf:
93 93 raise error.ParseError('rebase',
94 94 _('cannot use both abort and continue'))
95 95 if collapsef:
96 96 raise error.ParseError(
97 97 'rebase', _('cannot use collapse with continue or abort'))
98 98
99 99 if detachf:
100 100 raise error.ParseError(
101 101 'rebase', _('cannot use detach with continue or abort'))
102 102
103 103 if srcf or basef or destf:
104 104 raise error.ParseError('rebase',
105 105 _('abort and continue do not allow specifying revisions'))
106 106
107 107 (originalwd, target, state, collapsef, keepf,
108 108 keepbranchesf, external) = restorestatus(repo)
109 109 if abortf:
110 110 abort(repo, originalwd, target, state)
111 111 return
112 112 else:
113 113 if srcf and basef:
114 114 raise error.ParseError('rebase', _('cannot specify both a '
115 115 'revision and a base'))
116 116 if detachf:
117 117 if not srcf:
118 118 raise error.ParseError(
119 119 'rebase', _('detach requires a revision to be specified'))
120 120 if basef:
121 121 raise error.ParseError(
122 122 'rebase', _('cannot specify a base with detach'))
123 123
124 124 cmdutil.bail_if_changed(repo)
125 125 result = buildstate(repo, destf, srcf, basef, detachf)
126 126 if not result:
127 127 # Empty state built, nothing to rebase
128 128 ui.status(_('nothing to rebase\n'))
129 129 return
130 130 else:
131 131 originalwd, target, state = result
132 132 if collapsef:
133 133 targetancestors = set(repo.changelog.ancestors(target))
134 134 external = checkexternal(repo, state, targetancestors)
135 135
136 136 if keepbranchesf:
137 137 if extrafn:
138 138 raise error.ParseError(
139 139 'rebase', _('cannot use both keepbranches and extrafn'))
140 140 def extrafn(ctx, extra):
141 141 extra['branch'] = ctx.branch()
142 142
143 143 # Rebase
144 144 if not targetancestors:
145 145 targetancestors = set(repo.changelog.ancestors(target))
146 146 targetancestors.add(target)
147 147
148 148 for rev in sorted(state):
149 149 if state[rev] == -1:
150 150 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
151 151 storestatus(repo, originalwd, target, state, collapsef, keepf,
152 152 keepbranchesf, external)
153 153 p1, p2 = defineparents(repo, rev, target, state,
154 154 targetancestors)
155 155 if len(repo.parents()) == 2:
156 156 repo.ui.debug('resuming interrupted rebase\n')
157 157 else:
158 158 stats = rebasenode(repo, rev, p1, p2, state)
159 159 if stats and stats[3] > 0:
160 160 raise util.Abort(_('fix unresolved conflicts with hg '
161 161 'resolve then run hg rebase --continue'))
162 162 updatedirstate(repo, rev, target, p2)
163 163 if not collapsef:
164 164 extra = {'rebase_source': repo[rev].hex()}
165 165 if extrafn:
166 166 extrafn(repo[rev], extra)
167 167 newrev = concludenode(repo, rev, p1, p2, extra=extra)
168 168 else:
169 169 # Skip commit if we are collapsing
170 170 repo.dirstate.setparents(repo[p1].node())
171 171 newrev = None
172 172 # Update the state
173 173 if newrev is not None:
174 174 state[rev] = repo[newrev].rev()
175 175 else:
176 176 if not collapsef:
177 177 ui.note(_('no changes, revision %d skipped\n') % rev)
178 178 ui.debug('next revision set to %s\n' % p1)
179 179 skipped.add(rev)
180 180 state[rev] = p1
181 181
182 182 ui.note(_('rebase merging completed\n'))
183 183
184 184 if collapsef:
185 185 p1, p2 = defineparents(repo, min(state), target,
186 186 state, targetancestors)
187 187 commitmsg = 'Collapsed revision'
188 188 for rebased in state:
189 189 if rebased not in skipped and state[rebased] != nullmerge:
190 190 commitmsg += '\n* %s' % repo[rebased].description()
191 191 commitmsg = ui.edit(commitmsg, repo.ui.username())
192 192 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
193 193 extra=extrafn)
194 194
195 195 if 'qtip' in repo.tags():
196 196 updatemq(repo, state, skipped, **opts)
197 197
198 198 if not keepf:
199 199 # Remove no more useful revisions
200 200 rebased = [rev for rev in state if state[rev] != nullmerge]
201 201 if rebased:
202 202 if set(repo.changelog.descendants(min(rebased))) - set(state):
203 203 ui.warn(_("warning: new changesets detected "
204 204 "on source branch, not stripping\n"))
205 205 else:
206 206 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
207 207
208 208 clearstatus(repo)
209 209 ui.status(_("rebase completed\n"))
210 210 if os.path.exists(repo.sjoin('undo')):
211 211 util.unlink(repo.sjoin('undo'))
212 212 if skipped:
213 213 ui.note(_("%d revisions have been skipped\n") % len(skipped))
214 214 finally:
215 215 release(lock, wlock)
216 216
217 217 def rebasemerge(repo, rev, first=False):
218 218 'return the correct ancestor'
219 219 oldancestor = ancestor.ancestor
220 220
221 221 def newancestor(a, b, pfunc):
222 222 if b == rev:
223 223 return repo[rev].parents()[0].rev()
224 224 return oldancestor(a, b, pfunc)
225 225
226 226 if not first:
227 227 ancestor.ancestor = newancestor
228 228 else:
229 229 repo.ui.debug("first revision, do not change ancestor\n")
230 230 try:
231 231 stats = merge.update(repo, rev, True, True, False)
232 232 return stats
233 233 finally:
234 234 ancestor.ancestor = oldancestor
235 235
236 236 def checkexternal(repo, state, targetancestors):
237 237 """Check whether one or more external revisions need to be taken in
238 238 consideration. In the latter case, abort.
239 239 """
240 240 external = nullrev
241 241 source = min(state)
242 242 for rev in state:
243 243 if rev == source:
244 244 continue
245 245 # Check externals and fail if there are more than one
246 246 for p in repo[rev].parents():
247 247 if (p.rev() not in state
248 248 and p.rev() not in targetancestors):
249 249 if external != nullrev:
250 250 raise util.Abort(_('unable to collapse, there is more '
251 251 'than one external parent'))
252 252 external = p.rev()
253 253 return external
254 254
255 255 def updatedirstate(repo, rev, p1, p2):
256 256 """Keep track of renamed files in the revision that is going to be rebased
257 257 """
258 258 # Here we simulate the copies and renames in the source changeset
259 259 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
260 260 m1 = repo[rev].manifest()
261 261 m2 = repo[p1].manifest()
262 262 for k, v in cop.iteritems():
263 263 if k in m1:
264 264 if v in m1 or v in m2:
265 265 repo.dirstate.copy(v, k)
266 266 if v in m2 and v not in m1:
267 267 repo.dirstate.remove(v)
268 268
269 269 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
270 270 'Commit the changes and store useful information in extra'
271 271 try:
272 272 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
273 273 if commitmsg is None:
274 274 commitmsg = repo[rev].description()
275 275 if extra is None:
276 276 extra = {}
277 277 # Commit might fail if unresolved files exist
278 278 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
279 279 date=repo[rev].date(), extra=extra)
280 280 repo.dirstate.setbranch(repo[newrev].branch())
281 281 return newrev
282 282 except util.Abort:
283 283 # Invalidate the previous setparents
284 284 repo.dirstate.invalidate()
285 285 raise
286 286
287 287 def rebasenode(repo, rev, p1, p2, state):
288 288 'Rebase a single revision'
289 289 # Merge phase
290 290 # Update to target and merge it with local
291 291 if repo['.'].rev() != repo[p1].rev():
292 292 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
293 293 merge.update(repo, p1, False, True, False)
294 294 else:
295 295 repo.ui.debug(" already in target\n")
296 296 repo.dirstate.write()
297 297 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
298 298 first = repo[rev].rev() == repo[min(state)].rev()
299 299 stats = rebasemerge(repo, rev, first)
300 300 return stats
301 301
302 302 def defineparents(repo, rev, target, state, targetancestors):
303 303 'Return the new parent relationship of the revision that will be rebased'
304 304 parents = repo[rev].parents()
305 305 p1 = p2 = nullrev
306 306
307 307 P1n = parents[0].rev()
308 308 if P1n in targetancestors:
309 309 p1 = target
310 310 elif P1n in state:
311 311 if state[P1n] == nullmerge:
312 312 p1 = target
313 313 else:
314 314 p1 = state[P1n]
315 315 else: # P1n external
316 316 p1 = target
317 317 p2 = P1n
318 318
319 319 if len(parents) == 2 and parents[1].rev() not in targetancestors:
320 320 P2n = parents[1].rev()
321 321 # interesting second parent
322 322 if P2n in state:
323 323 if p1 == target: # P1n in targetancestors or external
324 324 p1 = state[P2n]
325 325 else:
326 326 p2 = state[P2n]
327 327 else: # P2n external
328 328 if p2 != nullrev: # P1n external too => rev is a merged revision
329 329 raise util.Abort(_('cannot use revision %d as base, result '
330 330 'would have 3 parents') % rev)
331 331 p2 = P2n
332 332 repo.ui.debug(" future parents are %d and %d\n" %
333 333 (repo[p1].rev(), repo[p2].rev()))
334 334 return p1, p2
335 335
336 336 def isagitpatch(repo, patchname):
337 337 'Return true if the given patch is in git format'
338 338 mqpatch = os.path.join(repo.mq.path, patchname)
339 339 for line in patch.linereader(file(mqpatch, 'rb')):
340 340 if line.startswith('diff --git'):
341 341 return True
342 342 return False
343 343
344 344 def updatemq(repo, state, skipped, **opts):
345 345 'Update rebased mq patches - finalize and then import them'
346 346 mqrebase = {}
347 347 for p in repo.mq.applied:
348 348 if repo[p.rev].rev() in state:
349 349 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
350 350 (repo[p.rev].rev(), p.name))
351 351 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
352 352
353 353 if mqrebase:
354 354 repo.mq.finish(repo, mqrebase.keys())
355 355
356 356 # We must start import from the newest revision
357 357 for rev in sorted(mqrebase, reverse=True):
358 358 if rev not in skipped:
359 359 repo.ui.debug('import mq patch %d (%s)\n'
360 360 % (state[rev], mqrebase[rev][0]))
361 361 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
362 362 git=mqrebase[rev][1],rev=[str(state[rev])])
363 363 repo.mq.save_dirty()
364 364
365 365 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
366 366 external):
367 367 'Store the current status to allow recovery'
368 368 f = repo.opener("rebasestate", "w")
369 369 f.write(repo[originalwd].hex() + '\n')
370 370 f.write(repo[target].hex() + '\n')
371 371 f.write(repo[external].hex() + '\n')
372 372 f.write('%d\n' % int(collapse))
373 373 f.write('%d\n' % int(keep))
374 374 f.write('%d\n' % int(keepbranches))
375 375 for d, v in state.iteritems():
376 376 oldrev = repo[d].hex()
377 377 newrev = repo[v].hex()
378 378 f.write("%s:%s\n" % (oldrev, newrev))
379 379 f.close()
380 380 repo.ui.debug('rebase status stored\n')
381 381
382 382 def clearstatus(repo):
383 383 'Remove the status files'
384 384 if os.path.exists(repo.join("rebasestate")):
385 385 util.unlink(repo.join("rebasestate"))
386 386
387 387 def restorestatus(repo):
388 388 'Restore a previously stored status'
389 389 try:
390 390 target = None
391 391 collapse = False
392 392 external = nullrev
393 393 state = {}
394 394 f = repo.opener("rebasestate")
395 395 for i, l in enumerate(f.read().splitlines()):
396 396 if i == 0:
397 397 originalwd = repo[l].rev()
398 398 elif i == 1:
399 399 target = repo[l].rev()
400 400 elif i == 2:
401 401 external = repo[l].rev()
402 402 elif i == 3:
403 403 collapse = bool(int(l))
404 404 elif i == 4:
405 405 keep = bool(int(l))
406 406 elif i == 5:
407 407 keepbranches = bool(int(l))
408 408 else:
409 409 oldrev, newrev = l.split(':')
410 410 state[repo[oldrev].rev()] = repo[newrev].rev()
411 411 repo.ui.debug('rebase status resumed\n')
412 412 return originalwd, target, state, collapse, keep, keepbranches, external
413 413 except IOError, err:
414 414 if err.errno != errno.ENOENT:
415 415 raise
416 416 raise util.Abort(_('no rebase in progress'))
417 417
418 418 def abort(repo, originalwd, target, state):
419 419 'Restore the repository to its original state'
420 420 if set(repo.changelog.descendants(target)) - set(state.values()):
421 421 repo.ui.warn(_("warning: new changesets detected on target branch, "
422 422 "not stripping\n"))
423 423 else:
424 424 # Strip from the first rebased revision
425 425 merge.update(repo, repo[originalwd].rev(), False, True, False)
426 426 rebased = filter(lambda x: x > -1, state.values())
427 427 if rebased:
428 428 strippoint = min(rebased)
429 429 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
430 430 clearstatus(repo)
431 431 repo.ui.status(_('rebase aborted\n'))
432 432
433 433 def buildstate(repo, dest, src, base, detach):
434 434 'Define which revisions are going to be rebased and where'
435 435 targetancestors = set()
436 436 detachset = set()
437 437
438 438 if not dest:
439 439 # Destination defaults to the latest revision in the current branch
440 440 branch = repo[None].branch()
441 441 dest = repo[branch].rev()
442 442 else:
443 dest = repo[dest].rev()
444
445 # This check isn't strictly necessary, since mq detects commits over an
446 # applied patch. But it prevents messing up the working directory when
447 # a partially completed rebase is blocked by mq.
443 448 if 'qtip' in repo.tags() and (repo[dest].hex() in
444 449 [s.rev for s in repo.mq.applied]):
445 450 raise util.Abort(_('cannot rebase onto an applied mq patch'))
446 dest = repo[dest].rev()
447 451
448 452 if src:
449 453 commonbase = repo[src].ancestor(repo[dest])
450 454 if commonbase == repo[src]:
451 455 raise util.Abort(_('source is ancestor of destination'))
452 456 if commonbase == repo[dest]:
453 457 raise util.Abort(_('source is descendant of destination'))
454 458 source = repo[src].rev()
455 459 if detach:
456 460 # We need to keep track of source's ancestors up to the common base
457 461 srcancestors = set(repo.changelog.ancestors(source))
458 462 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
459 463 detachset = srcancestors - baseancestors
460 464 detachset.remove(commonbase.rev())
461 465 else:
462 466 if base:
463 467 cwd = repo[base].rev()
464 468 else:
465 469 cwd = repo['.'].rev()
466 470
467 471 if cwd == dest:
468 472 repo.ui.debug('source and destination are the same\n')
469 473 return None
470 474
471 475 targetancestors = set(repo.changelog.ancestors(dest))
472 476 if cwd in targetancestors:
473 477 repo.ui.debug('source is ancestor of destination\n')
474 478 return None
475 479
476 480 cwdancestors = set(repo.changelog.ancestors(cwd))
477 481 if dest in cwdancestors:
478 482 repo.ui.debug('source is descendant of destination\n')
479 483 return None
480 484
481 485 cwdancestors.add(cwd)
482 486 rebasingbranch = cwdancestors - targetancestors
483 487 source = min(rebasingbranch)
484 488
485 489 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
486 490 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
487 491 state.update(dict.fromkeys(detachset, nullmerge))
488 492 state[source] = nullrev
489 493 return repo['.'].rev(), repo[dest].rev(), state
490 494
491 495 def pullrebase(orig, ui, repo, *args, **opts):
492 496 'Call rebase after pull if the latter has been invoked with --rebase'
493 497 if opts.get('rebase'):
494 498 if opts.get('update'):
495 499 del opts['update']
496 500 ui.debug('--update and --rebase are not compatible, ignoring '
497 501 'the update flag\n')
498 502
499 503 cmdutil.bail_if_changed(repo)
500 504 revsprepull = len(repo)
501 505 orig(ui, repo, *args, **opts)
502 506 revspostpull = len(repo)
503 507 if revspostpull > revsprepull:
504 508 rebase(ui, repo, **opts)
505 509 branch = repo[None].branch()
506 510 dest = repo[branch].rev()
507 511 if dest != repo['.'].rev():
508 512 # there was nothing to rebase we force an update
509 513 merge.update(repo, dest, False, False, False)
510 514 else:
511 515 orig(ui, repo, *args, **opts)
512 516
513 517 def uisetup(ui):
514 518 'Replace pull with a decorator to provide --rebase option'
515 519 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
516 520 entry[1].append(('', 'rebase', None,
517 521 _("rebase working directory to branch head"))
518 522 )
519 523
520 524 cmdtable = {
521 525 "rebase":
522 526 (rebase,
523 527 [
524 528 ('s', 'source', '', _('rebase from the specified changeset')),
525 529 ('b', 'base', '', _('rebase from the base of the specified changeset '
526 530 '(up to greatest common ancestor of base and dest)')),
527 531 ('d', 'dest', '', _('rebase onto the specified changeset')),
528 532 ('', 'collapse', False, _('collapse the rebased changesets')),
529 533 ('', 'keep', False, _('keep original changesets')),
530 534 ('', 'keepbranches', False, _('keep original branch names')),
531 535 ('', 'detach', False, _('force detaching of source from its original '
532 536 'branch')),
533 537 ('c', 'continue', False, _('continue an interrupted rebase')),
534 538 ('a', 'abort', False, _('abort an interrupted rebase'))] +
535 539 templateopts,
536 540 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
537 541 'hg rebase {-a|-c}'))
538 542 }
@@ -1,115 +1,121 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "graphlog=" >> $HGRCPATH
5 5 echo "rebase=" >> $HGRCPATH
6 6 echo "mq=" >> $HGRCPATH
7 7
8 8 echo "[mq]" >> $HGRCPATH
9 9 echo "plain=true" >> $HGRCPATH
10 10
11 11 filterpatch()
12 12 {
13 13 sed -e "s/^\(# Date\).*/\1/" \
14 14 -e "s/^\(# Node ID\).*/\1/" \
15 15 -e "s/^\(# Parent\).*/\1/" \
16 16 -e "s/^\(diff -r \)\([a-f0-9]* \)\(-r \)\([a-f0-9]* \)/\1x \3y /" \
17 17 -e "s/^\(diff -r \)\([a-f0-9]* \)/\1x /" \
18 18 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" \
19 19 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/"
20 20 }
21 21
22 22 hg init a
23 23 cd a
24 24 hg qinit -c # This must work even with a managed mq queue
25 25
26 26 echo 'c1' > f
27 27 hg add f
28 28 hg commit -d '0 0' -m "C1"
29 29
30 30 echo 'r1' > f
31 31 hg commit -d '2 0' -m "R1"
32 32
33 33 hg up 0
34 34 hg qnew f.patch
35 35 echo 'mq1' > f
36 36 hg qref -m 'P0'
37 37
38 38 hg qnew f2.patch
39 39 echo 'mq2' > f
40 40 hg qref -m 'P1'
41 41 hg glog --template '{rev} {desc} tags: {tags}\n'
42 42
43 43 echo
44 44 echo '% Rebase - try to rebase on an applied mq patch'
45 45 hg rebase -s 1 -d 3
46 46
47 47 echo
48 echo '% Rebase - same thing, but mq patch is default dest'
49 hg update -q 1
50 hg rebase
51 hg update -q qtip
52
53 echo
48 54 echo '% Rebase - generate a conflict'
49 55 hg rebase -s 2 -d 1
50 56
51 57 echo
52 58 echo '% Fix the 1st conflict'
53 59 echo 'mq1r1' > f
54 60 hg resolve -m f
55 61 hg rebase -c 2>&1 | sed -e 's/\(saving bundle to \).*/\1/'
56 62
57 63 echo
58 64 echo '% Fix the 2nd conflict'
59 65 echo 'mq1r1mq2' > f
60 66 hg resolve -m f
61 67 hg rebase -c 2>&1 | sed -e 's/\(saving bundle to \).*/\1/'
62 68
63 69 hg glog --template '{rev} {desc} tags: {tags}\n'
64 70
65 71 echo
66 72 echo '% Update to qbase'
67 73 hg up qbase
68 74 echo '% f correctly reflects the merge result'
69 75 cat f
70 76 echo '% And the patch is correct'
71 77 cat .hg/patches/f.patch | filterpatch
72 78
73 79 echo
74 80 echo '% Update to qtip'
75 81 hg up qtip
76 82 echo '% f correctly reflects the merge result'
77 83 cat f
78 84 echo '% And the patch is correct'
79 85 cat .hg/patches/f2.patch | filterpatch
80 86
81 87 echo
82 88 echo '% Adding one git-style patch and one normal'
83 89 hg qpop -a
84 90 rm -fr .hg/patches
85 91 hg qinit -c
86 92
87 93 hg up 0
88 94 hg qnew --git f_git.patch
89 95 echo 'mq1' > p
90 96 hg add p
91 97 hg qref --git -m 'P0 (git)'
92 98
93 99 hg qnew f.patch
94 100 echo 'mq2' > p
95 101 hg qref -m 'P1'
96 102
97 103 echo '% Git patch'
98 104 cat .hg/patches/f_git.patch | filterpatch
99 105
100 106 echo
101 107 echo '% Normal patch'
102 108 cat .hg/patches/f.patch | filterpatch
103 109
104 110 echo
105 111 echo '% Rebase the applied mq patches'
106 112 hg rebase -s 2 -d 1 --quiet 2>&1 | sed -e 's/\(saving bundle to \).*/\1/'
107 113
108 114 echo '% And the patches are correct'
109 115 echo '% Git patch'
110 116 cat .hg/patches/f_git.patch | filterpatch
111 117
112 118 echo
113 119 echo '% Normal patch'
114 120 cat .hg/patches/f.patch | filterpatch
115 121
@@ -1,136 +1,139 b''
1 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2 2 @ 3 P1 tags: qtip tip f2.patch
3 3 |
4 4 o 2 P0 tags: f.patch qbase
5 5 |
6 6 | o 1 R1 tags:
7 7 |/
8 8 o 0 C1 tags: qparent
9 9
10 10
11 11 % Rebase - try to rebase on an applied mq patch
12 12 abort: cannot rebase onto an applied mq patch
13 13
14 % Rebase - same thing, but mq patch is default dest
15 abort: cannot rebase onto an applied mq patch
16
14 17 % Rebase - generate a conflict
15 18 merging f
16 19 warning: conflicts during merge.
17 20 merging f failed!
18 21 abort: fix unresolved conflicts with hg resolve then run hg rebase --continue
19 22
20 23 % Fix the 1st conflict
21 24 merging f
22 25 warning: conflicts during merge.
23 26 merging f failed!
24 27 abort: fix unresolved conflicts with hg resolve then run hg rebase --continue
25 28
26 29 % Fix the 2nd conflict
27 30 saving bundle to
28 31 adding branch
29 32 adding changesets
30 33 adding manifests
31 34 adding file changes
32 35 added 2 changesets with 2 changes to 1 files
33 36 rebase completed
34 37 @ 3 P1 tags: qtip tip f2.patch
35 38 |
36 39 o 2 P0 tags: f.patch qbase
37 40 |
38 41 o 1 R1 tags: qparent
39 42 |
40 43 o 0 C1 tags:
41 44
42 45
43 46 % Update to qbase
44 47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 48 % f correctly reflects the merge result
46 49 mq1r1
47 50 % And the patch is correct
48 51 # HG changeset patch
49 52 # User test
50 53 # Date
51 54 # Node ID
52 55 # Parent
53 56 P0
54 57
55 58 diff -r x -r y f
56 59 --- a/f
57 60 +++ b/f
58 61 @@ -1,1 +1,1 @@
59 62 -r1
60 63 +mq1r1
61 64
62 65 % Update to qtip
63 66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 67 % f correctly reflects the merge result
65 68 mq1r1mq2
66 69 % And the patch is correct
67 70 # HG changeset patch
68 71 # User test
69 72 # Date
70 73 # Node ID
71 74 # Parent
72 75 P1
73 76
74 77 diff -r x -r y f
75 78 --- a/f
76 79 +++ b/f
77 80 @@ -1,1 +1,1 @@
78 81 -mq1r1
79 82 +mq1r1mq2
80 83
81 84 % Adding one git-style patch and one normal
82 85 popping f2.patch
83 86 popping f.patch
84 87 patch queue now empty
85 88 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 89 % Git patch
87 90 P0 (git)
88 91
89 92 diff --git a/p b/p
90 93 new file mode 100644
91 94 --- /dev/null
92 95 +++ b/p
93 96 @@ -0,0 +1,1 @@
94 97 +mq1
95 98
96 99 % Normal patch
97 100 P1
98 101
99 102 diff -r x p
100 103 --- a/p
101 104 +++ b/p
102 105 @@ -1,1 +1,1 @@
103 106 -mq1
104 107 +mq2
105 108
106 109 % Rebase the applied mq patches
107 110 saving bundle to
108 111 % And the patches are correct
109 112 % Git patch
110 113 # HG changeset patch
111 114 # User test
112 115 # Date
113 116 # Node ID
114 117 # Parent
115 118 P0 (git)
116 119
117 120 diff --git a/p b/p
118 121 new file mode 100644
119 122 --- /dev/null
120 123 +++ b/p
121 124 @@ -0,0 +1,1 @@
122 125 +mq1
123 126
124 127 % Normal patch
125 128 # HG changeset patch
126 129 # User test
127 130 # Date
128 131 # Node ID
129 132 # Parent
130 133 P1
131 134
132 135 --- a/p
133 136 +++ b/p
134 137 @@ -1,1 +1,1 @@
135 138 -mq1
136 139 +mq2
General Comments 0
You need to be logged in to leave comments. Login now