##// END OF EJS Templates
continue: added support for transplant...
Taapas Agrawal -
r42943:0a4303c7 default
parent child Browse files
Show More
@@ -1,769 +1,777 b''
1 1 # Patch transplanting extension for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.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 transplant changesets from another branch
9 9
10 10 This extension allows you to transplant changes to another parent revision,
11 11 possibly in another repository. The transplant is done using 'diff' patches.
12 12
13 13 Transplanted patches are recorded in .hg/transplant/transplants, as a
14 14 map from a changeset hash to its hash in the source repository.
15 15 '''
16 16 from __future__ import absolute_import
17 17
18 18 import os
19 19
20 20 from mercurial.i18n import _
21 21 from mercurial import (
22 22 bundlerepo,
23 23 cmdutil,
24 24 error,
25 25 exchange,
26 26 hg,
27 27 logcmdutil,
28 28 match,
29 29 merge,
30 30 node as nodemod,
31 31 patch,
32 32 pycompat,
33 33 registrar,
34 34 revlog,
35 35 revset,
36 36 scmutil,
37 37 smartset,
38 38 state as statemod,
39 39 util,
40 40 vfs as vfsmod,
41 41 )
42 42 from mercurial.utils import (
43 43 procutil,
44 44 stringutil,
45 45 )
46 46
47 47 class TransplantError(error.Abort):
48 48 pass
49 49
50 50 cmdtable = {}
51 51 command = registrar.command(cmdtable)
52 52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
53 53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 54 # be specifying the version(s) of Mercurial they are tested with, or
55 55 # leave the attribute unspecified.
56 56 testedwith = 'ships-with-hg-core'
57 57
58 58 configtable = {}
59 59 configitem = registrar.configitem(configtable)
60 60
61 61 configitem('transplant', 'filter',
62 62 default=None,
63 63 )
64 64 configitem('transplant', 'log',
65 65 default=None,
66 66 )
67 67
68 68 class transplantentry(object):
69 69 def __init__(self, lnode, rnode):
70 70 self.lnode = lnode
71 71 self.rnode = rnode
72 72
73 73 class transplants(object):
74 74 def __init__(self, path=None, transplantfile=None, opener=None):
75 75 self.path = path
76 76 self.transplantfile = transplantfile
77 77 self.opener = opener
78 78
79 79 if not opener:
80 80 self.opener = vfsmod.vfs(self.path)
81 81 self.transplants = {}
82 82 self.dirty = False
83 83 self.read()
84 84
85 85 def read(self):
86 86 abspath = os.path.join(self.path, self.transplantfile)
87 87 if self.transplantfile and os.path.exists(abspath):
88 88 for line in self.opener.read(self.transplantfile).splitlines():
89 89 lnode, rnode = map(revlog.bin, line.split(':'))
90 90 list = self.transplants.setdefault(rnode, [])
91 91 list.append(transplantentry(lnode, rnode))
92 92
93 93 def write(self):
94 94 if self.dirty and self.transplantfile:
95 95 if not os.path.isdir(self.path):
96 96 os.mkdir(self.path)
97 97 fp = self.opener(self.transplantfile, 'w')
98 98 for list in self.transplants.itervalues():
99 99 for t in list:
100 100 l, r = map(nodemod.hex, (t.lnode, t.rnode))
101 101 fp.write(l + ':' + r + '\n')
102 102 fp.close()
103 103 self.dirty = False
104 104
105 105 def get(self, rnode):
106 106 return self.transplants.get(rnode) or []
107 107
108 108 def set(self, lnode, rnode):
109 109 list = self.transplants.setdefault(rnode, [])
110 110 list.append(transplantentry(lnode, rnode))
111 111 self.dirty = True
112 112
113 113 def remove(self, transplant):
114 114 list = self.transplants.get(transplant.rnode)
115 115 if list:
116 116 del list[list.index(transplant)]
117 117 self.dirty = True
118 118
119 119 class transplanter(object):
120 120 def __init__(self, ui, repo, opts):
121 121 self.ui = ui
122 122 self.path = repo.vfs.join('transplant')
123 123 self.opener = vfsmod.vfs(self.path)
124 124 self.transplants = transplants(self.path, 'transplants',
125 125 opener=self.opener)
126 126 def getcommiteditor():
127 127 editform = cmdutil.mergeeditform(repo[None], 'transplant')
128 128 return cmdutil.getcommiteditor(editform=editform,
129 129 **pycompat.strkwargs(opts))
130 130 self.getcommiteditor = getcommiteditor
131 131
132 132 def applied(self, repo, node, parent):
133 133 '''returns True if a node is already an ancestor of parent
134 134 or is parent or has already been transplanted'''
135 135 if hasnode(repo, parent):
136 136 parentrev = repo.changelog.rev(parent)
137 137 if hasnode(repo, node):
138 138 rev = repo.changelog.rev(node)
139 139 reachable = repo.changelog.ancestors([parentrev], rev,
140 140 inclusive=True)
141 141 if rev in reachable:
142 142 return True
143 143 for t in self.transplants.get(node):
144 144 # it might have been stripped
145 145 if not hasnode(repo, t.lnode):
146 146 self.transplants.remove(t)
147 147 return False
148 148 lnoderev = repo.changelog.rev(t.lnode)
149 149 if lnoderev in repo.changelog.ancestors([parentrev], lnoderev,
150 150 inclusive=True):
151 151 return True
152 152 return False
153 153
154 154 def apply(self, repo, source, revmap, merges, opts=None):
155 155 '''apply the revisions in revmap one by one in revision order'''
156 156 if opts is None:
157 157 opts = {}
158 158 revs = sorted(revmap)
159 159 p1 = repo.dirstate.p1()
160 160 pulls = []
161 161 diffopts = patch.difffeatureopts(self.ui, opts)
162 162 diffopts.git = True
163 163
164 164 lock = tr = None
165 165 try:
166 166 lock = repo.lock()
167 167 tr = repo.transaction('transplant')
168 168 for rev in revs:
169 169 node = revmap[rev]
170 170 revstr = '%d:%s' % (rev, nodemod.short(node))
171 171
172 172 if self.applied(repo, node, p1):
173 173 self.ui.warn(_('skipping already applied revision %s\n') %
174 174 revstr)
175 175 continue
176 176
177 177 parents = source.changelog.parents(node)
178 178 if not (opts.get('filter') or opts.get('log')):
179 179 # If the changeset parent is the same as the
180 180 # wdir's parent, just pull it.
181 181 if parents[0] == p1:
182 182 pulls.append(node)
183 183 p1 = node
184 184 continue
185 185 if pulls:
186 186 if source != repo:
187 187 exchange.pull(repo, source.peer(), heads=pulls)
188 188 merge.update(repo, pulls[-1], branchmerge=False,
189 189 force=False)
190 190 p1 = repo.dirstate.p1()
191 191 pulls = []
192 192
193 193 domerge = False
194 194 if node in merges:
195 195 # pulling all the merge revs at once would mean we
196 196 # couldn't transplant after the latest even if
197 197 # transplants before them fail.
198 198 domerge = True
199 199 if not hasnode(repo, node):
200 200 exchange.pull(repo, source.peer(), heads=[node])
201 201
202 202 skipmerge = False
203 203 if parents[1] != revlog.nullid:
204 204 if not opts.get('parent'):
205 205 self.ui.note(_('skipping merge changeset %d:%s\n')
206 206 % (rev, nodemod.short(node)))
207 207 skipmerge = True
208 208 else:
209 209 parent = source.lookup(opts['parent'])
210 210 if parent not in parents:
211 211 raise error.Abort(_('%s is not a parent of %s') %
212 212 (nodemod.short(parent),
213 213 nodemod.short(node)))
214 214 else:
215 215 parent = parents[0]
216 216
217 217 if skipmerge:
218 218 patchfile = None
219 219 else:
220 220 fd, patchfile = pycompat.mkstemp(prefix='hg-transplant-')
221 221 fp = os.fdopen(fd, r'wb')
222 222 gen = patch.diff(source, parent, node, opts=diffopts)
223 223 for chunk in gen:
224 224 fp.write(chunk)
225 225 fp.close()
226 226
227 227 del revmap[rev]
228 228 if patchfile or domerge:
229 229 try:
230 230 try:
231 231 n = self.applyone(repo, node,
232 232 source.changelog.read(node),
233 233 patchfile, merge=domerge,
234 234 log=opts.get('log'),
235 235 filter=opts.get('filter'))
236 236 except TransplantError:
237 237 # Do not rollback, it is up to the user to
238 238 # fix the merge or cancel everything
239 239 tr.close()
240 240 raise
241 241 if n and domerge:
242 242 self.ui.status(_('%s merged at %s\n') % (revstr,
243 243 nodemod.short(n)))
244 244 elif n:
245 245 self.ui.status(_('%s transplanted to %s\n')
246 246 % (nodemod.short(node),
247 247 nodemod.short(n)))
248 248 finally:
249 249 if patchfile:
250 250 os.unlink(patchfile)
251 251 tr.close()
252 252 if pulls:
253 253 exchange.pull(repo, source.peer(), heads=pulls)
254 254 merge.update(repo, pulls[-1], branchmerge=False, force=False)
255 255 finally:
256 256 self.saveseries(revmap, merges)
257 257 self.transplants.write()
258 258 if tr:
259 259 tr.release()
260 260 if lock:
261 261 lock.release()
262 262
263 263 def filter(self, filter, node, changelog, patchfile):
264 264 '''arbitrarily rewrite changeset before applying it'''
265 265
266 266 self.ui.status(_('filtering %s\n') % patchfile)
267 267 user, date, msg = (changelog[1], changelog[2], changelog[4])
268 268 fd, headerfile = pycompat.mkstemp(prefix='hg-transplant-')
269 269 fp = os.fdopen(fd, r'wb')
270 270 fp.write("# HG changeset patch\n")
271 271 fp.write("# User %s\n" % user)
272 272 fp.write("# Date %d %d\n" % date)
273 273 fp.write(msg + '\n')
274 274 fp.close()
275 275
276 276 try:
277 277 self.ui.system('%s %s %s' % (filter,
278 278 procutil.shellquote(headerfile),
279 279 procutil.shellquote(patchfile)),
280 280 environ={'HGUSER': changelog[1],
281 281 'HGREVISION': nodemod.hex(node),
282 282 },
283 283 onerr=error.Abort, errprefix=_('filter failed'),
284 284 blockedtag='transplant_filter')
285 285 user, date, msg = self.parselog(open(headerfile, 'rb'))[1:4]
286 286 finally:
287 287 os.unlink(headerfile)
288 288
289 289 return (user, date, msg)
290 290
291 291 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
292 292 filter=None):
293 293 '''apply the patch in patchfile to the repository as a transplant'''
294 294 (manifest, user, (time, timezone), files, message) = cl[:5]
295 295 date = "%d %d" % (time, timezone)
296 296 extra = {'transplant_source': node}
297 297 if filter:
298 298 (user, date, message) = self.filter(filter, node, cl, patchfile)
299 299
300 300 if log:
301 301 # we don't translate messages inserted into commits
302 302 message += '\n(transplanted from %s)' % nodemod.hex(node)
303 303
304 304 self.ui.status(_('applying %s\n') % nodemod.short(node))
305 305 self.ui.note('%s %s\n%s\n' % (user, date, message))
306 306
307 307 if not patchfile and not merge:
308 308 raise error.Abort(_('can only omit patchfile if merging'))
309 309 if patchfile:
310 310 try:
311 311 files = set()
312 312 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
313 313 files = list(files)
314 314 except Exception as inst:
315 315 seriespath = os.path.join(self.path, 'series')
316 316 if os.path.exists(seriespath):
317 317 os.unlink(seriespath)
318 318 p1 = repo.dirstate.p1()
319 319 p2 = node
320 320 self.log(user, date, message, p1, p2, merge=merge)
321 321 self.ui.write(stringutil.forcebytestr(inst) + '\n')
322 322 raise TransplantError(_('fix up the working directory and run '
323 323 'hg transplant --continue'))
324 324 else:
325 325 files = None
326 326 if merge:
327 327 p1 = repo.dirstate.p1()
328 328 repo.setparents(p1, node)
329 329 m = match.always()
330 330 else:
331 331 m = match.exact(files)
332 332
333 333 n = repo.commit(message, user, date, extra=extra, match=m,
334 334 editor=self.getcommiteditor())
335 335 if not n:
336 336 self.ui.warn(_('skipping emptied changeset %s\n') %
337 337 nodemod.short(node))
338 338 return None
339 339 if not merge:
340 340 self.transplants.set(n, node)
341 341
342 342 return n
343 343
344 344 def canresume(self):
345 345 return os.path.exists(os.path.join(self.path, 'journal'))
346 346
347 347 def resume(self, repo, source, opts):
348 348 '''recover last transaction and apply remaining changesets'''
349 349 if os.path.exists(os.path.join(self.path, 'journal')):
350 350 n, node = self.recover(repo, source, opts)
351 351 if n:
352 352 self.ui.status(_('%s transplanted as %s\n') %
353 353 (nodemod.short(node),
354 354 nodemod.short(n)))
355 355 else:
356 356 self.ui.status(_('%s skipped due to empty diff\n')
357 357 % (nodemod.short(node),))
358 358 seriespath = os.path.join(self.path, 'series')
359 359 if not os.path.exists(seriespath):
360 360 self.transplants.write()
361 361 return
362 362 nodes, merges = self.readseries()
363 363 revmap = {}
364 364 for n in nodes:
365 365 revmap[source.changelog.rev(n)] = n
366 366 os.unlink(seriespath)
367 367
368 368 self.apply(repo, source, revmap, merges, opts)
369 369
370 370 def recover(self, repo, source, opts):
371 371 '''commit working directory using journal metadata'''
372 372 node, user, date, message, parents = self.readlog()
373 373 merge = False
374 374
375 375 if not user or not date or not message or not parents[0]:
376 376 raise error.Abort(_('transplant log file is corrupt'))
377 377
378 378 parent = parents[0]
379 379 if len(parents) > 1:
380 380 if opts.get('parent'):
381 381 parent = source.lookup(opts['parent'])
382 382 if parent not in parents:
383 383 raise error.Abort(_('%s is not a parent of %s') %
384 384 (nodemod.short(parent),
385 385 nodemod.short(node)))
386 386 else:
387 387 merge = True
388 388
389 389 extra = {'transplant_source': node}
390 390 try:
391 391 p1 = repo.dirstate.p1()
392 392 if p1 != parent:
393 393 raise error.Abort(_('working directory not at transplant '
394 394 'parent %s') % nodemod.hex(parent))
395 395 if merge:
396 396 repo.setparents(p1, parents[1])
397 397 modified, added, removed, deleted = repo.status()[:4]
398 398 if merge or modified or added or removed or deleted:
399 399 n = repo.commit(message, user, date, extra=extra,
400 400 editor=self.getcommiteditor())
401 401 if not n:
402 402 raise error.Abort(_('commit failed'))
403 403 if not merge:
404 404 self.transplants.set(n, node)
405 405 else:
406 406 n = None
407 407 self.unlog()
408 408
409 409 return n, node
410 410 finally:
411 411 # TODO: get rid of this meaningless try/finally enclosing.
412 412 # this is kept only to reduce changes in a patch.
413 413 pass
414 414
415 415 def readseries(self):
416 416 nodes = []
417 417 merges = []
418 418 cur = nodes
419 419 for line in self.opener.read('series').splitlines():
420 420 if line.startswith('# Merges'):
421 421 cur = merges
422 422 continue
423 423 cur.append(revlog.bin(line))
424 424
425 425 return (nodes, merges)
426 426
427 427 def saveseries(self, revmap, merges):
428 428 if not revmap:
429 429 return
430 430
431 431 if not os.path.isdir(self.path):
432 432 os.mkdir(self.path)
433 433 series = self.opener('series', 'w')
434 434 for rev in sorted(revmap):
435 435 series.write(nodemod.hex(revmap[rev]) + '\n')
436 436 if merges:
437 437 series.write('# Merges\n')
438 438 for m in merges:
439 439 series.write(nodemod.hex(m) + '\n')
440 440 series.close()
441 441
442 442 def parselog(self, fp):
443 443 parents = []
444 444 message = []
445 445 node = revlog.nullid
446 446 inmsg = False
447 447 user = None
448 448 date = None
449 449 for line in fp.read().splitlines():
450 450 if inmsg:
451 451 message.append(line)
452 452 elif line.startswith('# User '):
453 453 user = line[7:]
454 454 elif line.startswith('# Date '):
455 455 date = line[7:]
456 456 elif line.startswith('# Node ID '):
457 457 node = revlog.bin(line[10:])
458 458 elif line.startswith('# Parent '):
459 459 parents.append(revlog.bin(line[9:]))
460 460 elif not line.startswith('# '):
461 461 inmsg = True
462 462 message.append(line)
463 463 if None in (user, date):
464 464 raise error.Abort(_("filter corrupted changeset (no user or date)"))
465 465 return (node, user, date, '\n'.join(message), parents)
466 466
467 467 def log(self, user, date, message, p1, p2, merge=False):
468 468 '''journal changelog metadata for later recover'''
469 469
470 470 if not os.path.isdir(self.path):
471 471 os.mkdir(self.path)
472 472 fp = self.opener('journal', 'w')
473 473 fp.write('# User %s\n' % user)
474 474 fp.write('# Date %s\n' % date)
475 475 fp.write('# Node ID %s\n' % nodemod.hex(p2))
476 476 fp.write('# Parent ' + nodemod.hex(p1) + '\n')
477 477 if merge:
478 478 fp.write('# Parent ' + nodemod.hex(p2) + '\n')
479 479 fp.write(message.rstrip() + '\n')
480 480 fp.close()
481 481
482 482 def readlog(self):
483 483 return self.parselog(self.opener('journal'))
484 484
485 485 def unlog(self):
486 486 '''remove changelog journal'''
487 487 absdst = os.path.join(self.path, 'journal')
488 488 if os.path.exists(absdst):
489 489 os.unlink(absdst)
490 490
491 491 def transplantfilter(self, repo, source, root):
492 492 def matchfn(node):
493 493 if self.applied(repo, node, root):
494 494 return False
495 495 if source.changelog.parents(node)[1] != revlog.nullid:
496 496 return False
497 497 extra = source.changelog.read(node)[5]
498 498 cnode = extra.get('transplant_source')
499 499 if cnode and self.applied(repo, cnode, root):
500 500 return False
501 501 return True
502 502
503 503 return matchfn
504 504
505 505 def hasnode(repo, node):
506 506 try:
507 507 return repo.changelog.rev(node) is not None
508 508 except error.StorageError:
509 509 return False
510 510
511 511 def browserevs(ui, repo, nodes, opts):
512 512 '''interactively transplant changesets'''
513 513 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
514 514 transplants = []
515 515 merges = []
516 516 prompt = _('apply changeset? [ynmpcq?]:'
517 517 '$$ &yes, transplant this changeset'
518 518 '$$ &no, skip this changeset'
519 519 '$$ &merge at this changeset'
520 520 '$$ show &patch'
521 521 '$$ &commit selected changesets'
522 522 '$$ &quit and cancel transplant'
523 523 '$$ &? (show this help)')
524 524 for node in nodes:
525 525 displayer.show(repo[node])
526 526 action = None
527 527 while not action:
528 528 choice = ui.promptchoice(prompt)
529 529 action = 'ynmpcq?'[choice:choice + 1]
530 530 if action == '?':
531 531 for c, t in ui.extractchoices(prompt)[1]:
532 532 ui.write('%s: %s\n' % (c, t))
533 533 action = None
534 534 elif action == 'p':
535 535 parent = repo.changelog.parents(node)[0]
536 536 for chunk in patch.diff(repo, parent, node):
537 537 ui.write(chunk)
538 538 action = None
539 539 if action == 'y':
540 540 transplants.append(node)
541 541 elif action == 'm':
542 542 merges.append(node)
543 543 elif action == 'c':
544 544 break
545 545 elif action == 'q':
546 546 transplants = ()
547 547 merges = ()
548 548 break
549 549 displayer.close()
550 550 return (transplants, merges)
551 551
552 552 @command('transplant',
553 553 [('s', 'source', '', _('transplant changesets from REPO'), _('REPO')),
554 554 ('b', 'branch', [], _('use this source changeset as head'), _('REV')),
555 555 ('a', 'all', None, _('pull all changesets up to the --branch revisions')),
556 556 ('p', 'prune', [], _('skip over REV'), _('REV')),
557 557 ('m', 'merge', [], _('merge at REV'), _('REV')),
558 558 ('', 'parent', '',
559 559 _('parent to choose when transplanting merge'), _('REV')),
560 560 ('e', 'edit', False, _('invoke editor on commit messages')),
561 561 ('', 'log', None, _('append transplant info to log message')),
562 562 ('c', 'continue', None, _('continue last transplant session '
563 563 'after fixing conflicts')),
564 564 ('', 'filter', '',
565 565 _('filter changesets through command'), _('CMD'))],
566 566 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
567 567 '[-m REV] [REV]...'),
568 568 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
569 569 def transplant(ui, repo, *revs, **opts):
570 570 '''transplant changesets from another branch
571 571
572 572 Selected changesets will be applied on top of the current working
573 573 directory with the log of the original changeset. The changesets
574 574 are copied and will thus appear twice in the history with different
575 575 identities.
576 576
577 577 Consider using the graft command if everything is inside the same
578 578 repository - it will use merges and will usually give a better result.
579 579 Use the rebase extension if the changesets are unpublished and you want
580 580 to move them instead of copying them.
581 581
582 582 If --log is specified, log messages will have a comment appended
583 583 of the form::
584 584
585 585 (transplanted from CHANGESETHASH)
586 586
587 587 You can rewrite the changelog message with the --filter option.
588 588 Its argument will be invoked with the current changelog message as
589 589 $1 and the patch as $2.
590 590
591 591 --source/-s specifies another repository to use for selecting changesets,
592 592 just as if it temporarily had been pulled.
593 593 If --branch/-b is specified, these revisions will be used as
594 594 heads when deciding which changesets to transplant, just as if only
595 595 these revisions had been pulled.
596 596 If --all/-a is specified, all the revisions up to the heads specified
597 597 with --branch will be transplanted.
598 598
599 599 Example:
600 600
601 601 - transplant all changes up to REV on top of your current revision::
602 602
603 603 hg transplant --branch REV --all
604 604
605 605 You can optionally mark selected transplanted changesets as merge
606 606 changesets. You will not be prompted to transplant any ancestors
607 607 of a merged transplant, and you can merge descendants of them
608 608 normally instead of transplanting them.
609 609
610 610 Merge changesets may be transplanted directly by specifying the
611 611 proper parent changeset by calling :hg:`transplant --parent`.
612 612
613 613 If no merges or revisions are provided, :hg:`transplant` will
614 614 start an interactive changeset browser.
615 615
616 616 If a changeset application fails, you can fix the merge by hand
617 617 and then resume where you left off by calling :hg:`transplant
618 618 --continue/-c`.
619 619 '''
620 620 with repo.wlock():
621 621 return _dotransplant(ui, repo, *revs, **opts)
622 622
623 623 def _dotransplant(ui, repo, *revs, **opts):
624 624 def incwalk(repo, csets, match=util.always):
625 625 for node in csets:
626 626 if match(node):
627 627 yield node
628 628
629 629 def transplantwalk(repo, dest, heads, match=util.always):
630 630 '''Yield all nodes that are ancestors of a head but not ancestors
631 631 of dest.
632 632 If no heads are specified, the heads of repo will be used.'''
633 633 if not heads:
634 634 heads = repo.heads()
635 635 ancestors = []
636 636 ctx = repo[dest]
637 637 for head in heads:
638 638 ancestors.append(ctx.ancestor(repo[head]).node())
639 639 for node in repo.changelog.nodesbetween(ancestors, heads)[0]:
640 640 if match(node):
641 641 yield node
642 642
643 643 def checkopts(opts, revs):
644 644 if opts.get('continue'):
645 645 if opts.get('branch') or opts.get('all') or opts.get('merge'):
646 646 raise error.Abort(_('--continue is incompatible with '
647 647 '--branch, --all and --merge'))
648 648 return
649 649 if not (opts.get('source') or revs or
650 650 opts.get('merge') or opts.get('branch')):
651 651 raise error.Abort(_('no source URL, branch revision, or revision '
652 652 'list provided'))
653 653 if opts.get('all'):
654 654 if not opts.get('branch'):
655 655 raise error.Abort(_('--all requires a branch revision'))
656 656 if revs:
657 657 raise error.Abort(_('--all is incompatible with a '
658 658 'revision list'))
659 659
660 660 opts = pycompat.byteskwargs(opts)
661 661 checkopts(opts, revs)
662 662
663 663 if not opts.get('log'):
664 664 # deprecated config: transplant.log
665 665 opts['log'] = ui.config('transplant', 'log')
666 666 if not opts.get('filter'):
667 667 # deprecated config: transplant.filter
668 668 opts['filter'] = ui.config('transplant', 'filter')
669 669
670 670 tp = transplanter(ui, repo, opts)
671 671
672 672 p1 = repo.dirstate.p1()
673 673 if len(repo) > 0 and p1 == revlog.nullid:
674 674 raise error.Abort(_('no revision checked out'))
675 675 if opts.get('continue'):
676 676 if not tp.canresume():
677 677 raise error.Abort(_('no transplant to continue'))
678 678 else:
679 679 cmdutil.checkunfinished(repo)
680 680 cmdutil.bailifchanged(repo)
681 681
682 682 sourcerepo = opts.get('source')
683 683 if sourcerepo:
684 684 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
685 685 heads = pycompat.maplist(peer.lookup, opts.get('branch', ()))
686 686 target = set(heads)
687 687 for r in revs:
688 688 try:
689 689 target.add(peer.lookup(r))
690 690 except error.RepoError:
691 691 pass
692 692 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
693 693 onlyheads=sorted(target), force=True)
694 694 else:
695 695 source = repo
696 696 heads = pycompat.maplist(source.lookup, opts.get('branch', ()))
697 697 cleanupfn = None
698 698
699 699 try:
700 700 if opts.get('continue'):
701 701 tp.resume(repo, source, opts)
702 702 return
703 703
704 704 tf = tp.transplantfilter(repo, source, p1)
705 705 if opts.get('prune'):
706 706 prune = set(source[r].node()
707 707 for r in scmutil.revrange(source, opts.get('prune')))
708 708 matchfn = lambda x: tf(x) and x not in prune
709 709 else:
710 710 matchfn = tf
711 711 merges = pycompat.maplist(source.lookup, opts.get('merge', ()))
712 712 revmap = {}
713 713 if revs:
714 714 for r in scmutil.revrange(source, revs):
715 715 revmap[int(r)] = source[r].node()
716 716 elif opts.get('all') or not merges:
717 717 if source != repo:
718 718 alltransplants = incwalk(source, csets, match=matchfn)
719 719 else:
720 720 alltransplants = transplantwalk(source, p1, heads,
721 721 match=matchfn)
722 722 if opts.get('all'):
723 723 revs = alltransplants
724 724 else:
725 725 revs, newmerges = browserevs(ui, source, alltransplants, opts)
726 726 merges.extend(newmerges)
727 727 for r in revs:
728 728 revmap[source.changelog.rev(r)] = r
729 729 for r in merges:
730 730 revmap[source.changelog.rev(r)] = r
731 731
732 732 tp.apply(repo, source, revmap, merges, opts)
733 733 finally:
734 734 if cleanupfn:
735 735 cleanupfn()
736 736
737 def continuecmd(ui, repo):
738 """logic to resume an interrupted transplant using
739 'hg continue'"""
740 with repo.wlock():
741 tp = transplanter(ui, repo, {})
742 return tp.resume(repo, repo, {})
743
737 744 revsetpredicate = registrar.revsetpredicate()
738 745
739 746 @revsetpredicate('transplanted([set])')
740 747 def revsettransplanted(repo, subset, x):
741 748 """Transplanted changesets in set, or all transplanted changesets.
742 749 """
743 750 if x:
744 751 s = revset.getset(repo, subset, x)
745 752 else:
746 753 s = subset
747 754 return smartset.baseset([r for r in s if
748 755 repo[r].extra().get('transplant_source')])
749 756
750 757 templatekeyword = registrar.templatekeyword()
751 758
752 759 @templatekeyword('transplanted', requires={'ctx'})
753 760 def kwtransplanted(context, mapping):
754 761 """String. The node identifier of the transplanted
755 762 changeset if any."""
756 763 ctx = context.resource(mapping, 'ctx')
757 764 n = ctx.extra().get('transplant_source')
758 765 return n and nodemod.hex(n) or ''
759 766
760 767 def extsetup(ui):
761 768 statemod.addunfinished (
762 769 'transplant', fname='transplant/journal', clearable=True,
770 continuefunc=continuecmd,
763 771 statushint=_('To continue: hg transplant --continue\n'
764 772 'To abort: hg update'),
765 773 cmdhint=_("use 'hg transplant --continue' or 'hg update' to abort")
766 774 )
767 775
768 776 # tell hggettext to extract docstrings from these functions:
769 777 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,1017 +1,1026 b''
1 #testcases commandmode continueflag
1 2 $ cat <<EOF >> $HGRCPATH
2 3 > [extensions]
3 4 > transplant=
4 5 > EOF
5 6
7 #if continueflag
8 $ cat >> $HGRCPATH <<EOF
9 > [alias]
10 > continue = transplant --continue
11 > EOF
12 #endif
13
6 14 $ hg init t
7 15 $ cd t
8 16 $ hg transplant
9 17 abort: no source URL, branch revision, or revision list provided
10 18 [255]
11 19 $ hg transplant --continue --all
12 20 abort: --continue is incompatible with --branch, --all and --merge
13 21 [255]
14 22 $ hg transplant --all tip
15 23 abort: --all requires a branch revision
16 24 [255]
17 25 $ hg transplant --all --branch default tip
18 26 abort: --all is incompatible with a revision list
19 27 [255]
20 28 $ echo r1 > r1
21 29 $ hg ci -Amr1 -d'0 0'
22 30 adding r1
23 31 $ hg co -q null
24 32 $ hg transplant tip
25 33 abort: no revision checked out
26 34 [255]
27 35 $ hg up -q
28 36 $ echo r2 > r2
29 37 $ hg ci -Amr2 -d'1 0'
30 38 adding r2
31 39 $ hg up 0
32 40 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
33 41
34 42 $ echo b1 > b1
35 43 $ hg ci -Amb1 -d '0 0'
36 44 adding b1
37 45 created new head
38 46 $ hg merge 1
39 47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 48 (branch merge, don't forget to commit)
41 49 $ hg transplant 1
42 50 abort: outstanding uncommitted merge
43 51 (use 'hg commit' or 'hg merge --abort')
44 52 [255]
45 53 $ hg up -qC tip
46 54 $ echo b0 > b1
47 55 $ hg transplant 1
48 56 abort: uncommitted changes
49 57 [255]
50 58 $ hg up -qC tip
51 59 $ echo b2 > b2
52 60 $ hg ci -Amb2 -d '1 0'
53 61 adding b2
54 62 $ echo b3 > b3
55 63 $ hg ci -Amb3 -d '2 0'
56 64 adding b3
57 65
58 66 $ hg log --template '{rev} {parents} {desc}\n'
59 67 4 b3
60 68 3 b2
61 69 2 0:17ab29e464c6 b1
62 70 1 r2
63 71 0 r1
64 72
65 73 $ hg clone . ../rebase
66 74 updating to branch default
67 75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 76 $ hg init ../emptydest
69 77 $ cd ../emptydest
70 78 $ hg transplant --source=../t > /dev/null
71 79 $ cd ../rebase
72 80
73 81 $ hg up -C 1
74 82 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
75 83
76 84 rebase b onto r1
77 85 (this also tests that editor is not invoked if '--edit' is not specified)
78 86
79 87 $ HGEDITOR=cat hg transplant -a -b tip
80 88 applying 37a1297eb21b
81 89 37a1297eb21b transplanted to e234d668f844
82 90 applying 722f4667af76
83 91 722f4667af76 transplanted to 539f377d78df
84 92 applying a53251cdf717
85 93 a53251cdf717 transplanted to ffd6818a3975
86 94 $ hg log --template '{rev} {parents} {desc}\n'
87 95 7 b3
88 96 6 b2
89 97 5 1:d11e3596cc1a b1
90 98 4 b3
91 99 3 b2
92 100 2 0:17ab29e464c6 b1
93 101 1 r2
94 102 0 r1
95 103
96 104 test format of transplant_source
97 105
98 106 $ hg log -r7 --debug | grep transplant_source
99 107 extra: transplant_source=\xa52Q\xcd\xf7\x17g\x9d\x19\x07\xb2\x89\xf9\x91SK\xe0\\\x99z
100 108 $ hg log -r7 -T '{extras}\n'
101 109 branch=defaulttransplant_source=\xa52Q\xcd\xf7\x17g\x9d\x19\x07\xb2\x89\xf9\x91SK\xe0\\\x99z
102 110 $ hg log -r7 -T '{join(extras, " ")}\n'
103 111 branch=default transplant_source=\xa52Q\xcd\xf7\x17g\x9d\x19\x07\xb2\x89\xf9\x91SK\xe0\\\x99z
104 112
105 113 test transplanted revset
106 114
107 115 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
108 116 5 1:d11e3596cc1a b1
109 117 6 b2
110 118 7 b3
111 119 $ hg log -r 'transplanted(head())' --template '{rev} {parents} {desc}\n'
112 120 7 b3
113 121 $ hg help revisions.transplanted
114 122 "transplanted([set])"
115 123 Transplanted changesets in set, or all transplanted changesets.
116 124
117 125
118 126 test transplanted keyword
119 127
120 128 $ hg log --template '{rev} {transplanted}\n'
121 129 7 a53251cdf717679d1907b289f991534be05c997a
122 130 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
123 131 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
124 132 4
125 133 3
126 134 2
127 135 1
128 136 0
129 137
130 138 test destination() revset predicate with a transplant of a transplant; new
131 139 clone so subsequent rollback isn't affected
132 140 (this also tests that editor is invoked if '--edit' is specified)
133 141
134 142 $ hg clone -q . ../destination
135 143 $ cd ../destination
136 144 $ hg up -Cq 0
137 145 $ hg branch -q b4
138 146 $ hg ci -qm "b4"
139 147 $ hg status --rev "7^1" --rev 7
140 148 A b3
141 149 $ cat > $TESTTMP/checkeditform.sh <<EOF
142 150 > env | grep HGEDITFORM
143 151 > true
144 152 > EOF
145 153 $ cat > $TESTTMP/checkeditform-n-cat.sh <<EOF
146 154 > env | grep HGEDITFORM
147 155 > cat \$*
148 156 > EOF
149 157 $ HGEDITOR="sh $TESTTMP/checkeditform-n-cat.sh" hg transplant --edit 7
150 158 applying ffd6818a3975
151 159 HGEDITFORM=transplant.normal
152 160 b3
153 161
154 162
155 163 HG: Enter commit message. Lines beginning with 'HG:' are removed.
156 164 HG: Leave message empty to abort commit.
157 165 HG: --
158 166 HG: user: test
159 167 HG: branch 'b4'
160 168 HG: added b3
161 169 ffd6818a3975 transplanted to 502236fa76bb
162 170
163 171
164 172 $ hg log -r 'destination()'
165 173 changeset: 5:e234d668f844
166 174 parent: 1:d11e3596cc1a
167 175 user: test
168 176 date: Thu Jan 01 00:00:00 1970 +0000
169 177 summary: b1
170 178
171 179 changeset: 6:539f377d78df
172 180 user: test
173 181 date: Thu Jan 01 00:00:01 1970 +0000
174 182 summary: b2
175 183
176 184 changeset: 7:ffd6818a3975
177 185 user: test
178 186 date: Thu Jan 01 00:00:02 1970 +0000
179 187 summary: b3
180 188
181 189 changeset: 9:502236fa76bb
182 190 branch: b4
183 191 tag: tip
184 192 user: test
185 193 date: Thu Jan 01 00:00:02 1970 +0000
186 194 summary: b3
187 195
188 196 $ hg log -r 'destination(a53251cdf717)'
189 197 changeset: 7:ffd6818a3975
190 198 user: test
191 199 date: Thu Jan 01 00:00:02 1970 +0000
192 200 summary: b3
193 201
194 202 changeset: 9:502236fa76bb
195 203 branch: b4
196 204 tag: tip
197 205 user: test
198 206 date: Thu Jan 01 00:00:02 1970 +0000
199 207 summary: b3
200 208
201 209
202 210 test subset parameter in reverse order
203 211 $ hg log -r 'reverse(all()) and destination(a53251cdf717)'
204 212 changeset: 9:502236fa76bb
205 213 branch: b4
206 214 tag: tip
207 215 user: test
208 216 date: Thu Jan 01 00:00:02 1970 +0000
209 217 summary: b3
210 218
211 219 changeset: 7:ffd6818a3975
212 220 user: test
213 221 date: Thu Jan 01 00:00:02 1970 +0000
214 222 summary: b3
215 223
216 224
217 225 back to the original dir
218 226 $ cd ../rebase
219 227
220 228 rollback the transplant
221 229 $ hg rollback
222 230 repository tip rolled back to revision 4 (undo transplant)
223 231 working directory now based on revision 1
224 232 $ hg tip -q
225 233 4:a53251cdf717
226 234 $ hg parents -q
227 235 1:d11e3596cc1a
228 236 $ hg status
229 237 ? b1
230 238 ? b2
231 239 ? b3
232 240
233 241 $ hg clone ../t ../prune
234 242 updating to branch default
235 243 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 244 $ cd ../prune
237 245
238 246 $ hg up -C 1
239 247 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
240 248
241 249 rebase b onto r1, skipping b2
242 250
243 251 $ hg transplant -a -b tip -p 3
244 252 applying 37a1297eb21b
245 253 37a1297eb21b transplanted to e234d668f844
246 254 applying a53251cdf717
247 255 a53251cdf717 transplanted to 7275fda4d04f
248 256 $ hg log --template '{rev} {parents} {desc}\n'
249 257 6 b3
250 258 5 1:d11e3596cc1a b1
251 259 4 b3
252 260 3 b2
253 261 2 0:17ab29e464c6 b1
254 262 1 r2
255 263 0 r1
256 264
257 265 test same-parent transplant with --log
258 266
259 267 $ hg clone -r 1 ../t ../sameparent
260 268 adding changesets
261 269 adding manifests
262 270 adding file changes
263 271 added 2 changesets with 2 changes to 2 files
264 272 new changesets 17ab29e464c6:d11e3596cc1a
265 273 updating to branch default
266 274 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 275 $ cd ../sameparent
268 276 $ hg transplant --log -s ../prune 5
269 277 searching for changes
270 278 applying e234d668f844
271 279 e234d668f844 transplanted to e07aea8ecf9c
272 280 $ hg log --template '{rev} {parents} {desc}\n'
273 281 2 b1
274 282 (transplanted from e234d668f844e1b1a765f01db83a32c0c7bfa170)
275 283 1 r2
276 284 0 r1
277 285 remote transplant, and also test that transplant doesn't break with
278 286 format-breaking diffopts
279 287
280 288 $ hg clone -r 1 ../t ../remote
281 289 adding changesets
282 290 adding manifests
283 291 adding file changes
284 292 added 2 changesets with 2 changes to 2 files
285 293 new changesets 17ab29e464c6:d11e3596cc1a
286 294 updating to branch default
287 295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 296 $ cd ../remote
289 297 $ hg --config diff.noprefix=True transplant --log -s ../t 2 4
290 298 searching for changes
291 299 applying 37a1297eb21b
292 300 37a1297eb21b transplanted to c19cf0ccb069
293 301 applying a53251cdf717
294 302 a53251cdf717 transplanted to f7fe5bf98525
295 303 $ hg log --template '{rev} {parents} {desc}\n'
296 304 3 b3
297 305 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
298 306 2 b1
299 307 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
300 308 1 r2
301 309 0 r1
302 310
303 311 skip previous transplants
304 312
305 313 $ hg transplant -s ../t -a -b 4
306 314 searching for changes
307 315 applying 722f4667af76
308 316 722f4667af76 transplanted to 47156cd86c0b
309 317 $ hg log --template '{rev} {parents} {desc}\n'
310 318 4 b2
311 319 3 b3
312 320 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
313 321 2 b1
314 322 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
315 323 1 r2
316 324 0 r1
317 325
318 326 skip local changes transplanted to the source
319 327
320 328 $ echo b4 > b4
321 329 $ hg ci -Amb4 -d '3 0'
322 330 adding b4
323 331 $ hg clone ../t ../pullback
324 332 updating to branch default
325 333 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
326 334 $ cd ../pullback
327 335 $ hg transplant -s ../remote -a -b tip
328 336 searching for changes
329 337 applying 4333daefcb15
330 338 4333daefcb15 transplanted to 5f42c04e07cc
331 339
332 340
333 341 remote transplant with pull
334 342
335 343 $ hg serve -R ../t -p $HGPORT -d --pid-file=../t.pid
336 344 $ cat ../t.pid >> $DAEMON_PIDS
337 345
338 346 $ hg clone -r 0 ../t ../rp
339 347 adding changesets
340 348 adding manifests
341 349 adding file changes
342 350 added 1 changesets with 1 changes to 1 files
343 351 new changesets 17ab29e464c6
344 352 updating to branch default
345 353 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 354 $ cd ../rp
347 355 $ hg transplant -s http://localhost:$HGPORT/ 37a1297eb21b a53251cdf717
348 356 searching for changes
349 357 searching for changes
350 358 adding changesets
351 359 adding manifests
352 360 adding file changes
353 361 added 1 changesets with 1 changes to 1 files
354 362 applying a53251cdf717
355 363 a53251cdf717 transplanted to 8d9279348abb
356 364 $ hg log --template '{rev} {parents} {desc}\n'
357 365 2 b3
358 366 1 b1
359 367 0 r1
360 368
361 369 remote transplant without pull
362 370 (It was using "2" and "4" (as the previous transplant used to) which referenced
363 371 revision different from one run to another)
364 372
365 373 $ hg pull -q http://localhost:$HGPORT/
366 374 $ hg transplant -s http://localhost:$HGPORT/ 8d9279348abb 722f4667af76
367 375 skipping already applied revision 2:8d9279348abb
368 376 applying 722f4667af76
369 377 722f4667af76 transplanted to 76e321915884
370 378
371 379 transplant --continue
372 380
373 381 $ hg init ../tc
374 382 $ cd ../tc
375 383 $ cat <<EOF > foo
376 384 > foo
377 385 > bar
378 386 > baz
379 387 > EOF
380 388 $ echo toremove > toremove
381 389 $ echo baz > baz
382 390 $ hg ci -Amfoo
383 391 adding baz
384 392 adding foo
385 393 adding toremove
386 394 $ cat <<EOF > foo
387 395 > foo2
388 396 > bar2
389 397 > baz2
390 398 > EOF
391 399 $ rm toremove
392 400 $ echo added > added
393 401 $ hg ci -Amfoo2
394 402 adding added
395 403 removing toremove
396 404 $ echo bar > bar
397 405 $ cat > baz <<EOF
398 406 > before baz
399 407 > baz
400 408 > after baz
401 409 > EOF
402 410 $ hg ci -Ambar
403 411 adding bar
404 412 $ echo bar2 >> bar
405 413 $ hg ci -mbar2
406 414 $ hg up 0
407 415 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
408 416 $ echo foobar > foo
409 417 $ hg ci -mfoobar
410 418 created new head
411 419 $ hg transplant 1:3
412 420 applying 46ae92138f3c
413 421 patching file foo
414 422 Hunk #1 FAILED at 0
415 423 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
416 424 patch failed to apply
417 425 abort: fix up the working directory and run hg transplant --continue
418 426 [255]
419 427
420 428 transplant -c shouldn't use an old changeset
421 429
422 430 $ hg up -C
423 431 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
424 432 updated to "e8643552fde5: foobar"
425 433 1 other heads for branch "default"
426 434 $ rm added
427 $ hg transplant --continue
428 abort: no transplant to continue
435 $ hg continue
436 abort: no transplant to continue (continueflag !)
437 abort: no operation in progress (no-continueflag !)
429 438 [255]
430 439 $ hg transplant 1
431 440 applying 46ae92138f3c
432 441 patching file foo
433 442 Hunk #1 FAILED at 0
434 443 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
435 444 patch failed to apply
436 445 abort: fix up the working directory and run hg transplant --continue
437 446 [255]
438 447 $ cp .hg/transplant/journal .hg/transplant/journal.orig
439 448 $ cat .hg/transplant/journal
440 449 # User test
441 450 # Date 0 0
442 451 # Node ID 46ae92138f3ce0249f6789650403286ead052b6d
443 452 # Parent e8643552fde58f57515e19c4b373a57c96e62af3
444 453 foo2
445 454 $ grep -v 'Date' .hg/transplant/journal.orig > .hg/transplant/journal
446 455 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
447 456 abort: filter corrupted changeset (no user or date)
448 457 [255]
449 458 $ cp .hg/transplant/journal.orig .hg/transplant/journal
450 459 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
451 460 HGEDITFORM=transplant.normal
452 461 46ae92138f3c transplanted as 9159dada197d
453 462 $ hg transplant 1:3
454 463 skipping already applied revision 1:46ae92138f3c
455 464 applying 9d6d6b5a8275
456 465 9d6d6b5a8275 transplanted to 2d17a10c922f
457 466 applying 1dab759070cf
458 467 1dab759070cf transplanted to e06a69927eb0
459 468 $ hg locate
460 469 added
461 470 bar
462 471 baz
463 472 foo
464 473
465 474 test multiple revisions, --continue and hg status --verbose
466 475
467 476 $ hg up -qC 0
468 477 $ echo bazbaz > baz
469 478 $ hg ci -Am anotherbaz baz
470 479 created new head
471 480 $ hg transplant 1:3
472 481 applying 46ae92138f3c
473 482 46ae92138f3c transplanted to 1024233ea0ba
474 483 applying 9d6d6b5a8275
475 484 patching file baz
476 485 Hunk #1 FAILED at 0
477 486 1 out of 1 hunks FAILED -- saving rejects to file baz.rej
478 487 patch failed to apply
479 488 abort: fix up the working directory and run hg transplant --continue
480 489 [255]
481 490 $ hg transplant 1:3
482 491 abort: transplant in progress
483 492 (use 'hg transplant --continue' or 'hg update' to abort)
484 493 [255]
485 494 $ hg status -v
486 495 A bar
487 496 ? baz.rej
488 497 ? foo.rej
489 498 # The repository is in an unfinished *transplant* state.
490 499
491 500 # To continue: hg transplant --continue
492 501 # To abort: hg update
493 502
494 503 $ echo fixed > baz
495 $ hg transplant --continue
504 $ hg continue
496 505 9d6d6b5a8275 transplanted as d80c49962290
497 506 applying 1dab759070cf
498 507 1dab759070cf transplanted to aa0ffe6bd5ae
499 508
500 509 $ cd ..
501 510
502 511 Issue1111: Test transplant --merge
503 512
504 513 $ hg init t1111
505 514 $ cd t1111
506 515 $ echo a > a
507 516 $ hg ci -Am adda
508 517 adding a
509 518 $ echo b >> a
510 519 $ hg ci -m appendb
511 520 $ echo c >> a
512 521 $ hg ci -m appendc
513 522 $ hg up -C 0
514 523 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
515 524 $ echo d >> a
516 525 $ hg ci -m appendd
517 526 created new head
518 527
519 528 transplant
520 529
521 530 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant -m 1 -e
522 531 applying 42dc4432fd35
523 532 HGEDITFORM=transplant.merge
524 533 1:42dc4432fd35 merged at a9f4acbac129
525 534 $ hg update -q -C 2
526 535 $ cat > a <<EOF
527 536 > x
528 537 > y
529 538 > z
530 539 > EOF
531 540 $ hg commit -m replace
532 541 $ hg update -q -C 4
533 542 $ hg transplant -m 5
534 543 applying 600a3cdcb41d
535 544 patching file a
536 545 Hunk #1 FAILED at 0
537 546 1 out of 1 hunks FAILED -- saving rejects to file a.rej
538 547 patch failed to apply
539 548 abort: fix up the working directory and run hg transplant --continue
540 549 [255]
541 550 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
542 551 HGEDITFORM=transplant.merge
543 552 600a3cdcb41d transplanted as a3f88be652e0
544 553
545 554 $ cd ..
546 555
547 556 test transplant into empty repository
548 557
549 558 $ hg init empty
550 559 $ cd empty
551 560 $ hg transplant -s ../t -b tip -a
552 561 adding changesets
553 562 adding manifests
554 563 adding file changes
555 564 added 4 changesets with 4 changes to 4 files
556 565 new changesets 17ab29e464c6:a53251cdf717
557 566
558 567 test "--merge" causing pull from source repository on local host
559 568
560 569 $ hg --config extensions.mq= -q strip 2
561 570 $ hg transplant -s ../t --merge tip
562 571 searching for changes
563 572 searching for changes
564 573 adding changesets
565 574 adding manifests
566 575 adding file changes
567 576 added 2 changesets with 2 changes to 2 files
568 577 applying a53251cdf717
569 578 4:a53251cdf717 merged at 4831f4dc831a
570 579
571 580 test interactive transplant
572 581
573 582 $ hg --config extensions.strip= -q strip 0
574 583 $ hg -R ../t log -G --template "{rev}:{node|short}"
575 584 @ 4:a53251cdf717
576 585 |
577 586 o 3:722f4667af76
578 587 |
579 588 o 2:37a1297eb21b
580 589 |
581 590 | o 1:d11e3596cc1a
582 591 |/
583 592 o 0:17ab29e464c6
584 593
585 594 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
586 595 > ?
587 596 > x
588 597 > q
589 598 > EOF
590 599 0:17ab29e464c6
591 600 apply changeset? [ynmpcq?]: ?
592 601 y: yes, transplant this changeset
593 602 n: no, skip this changeset
594 603 m: merge at this changeset
595 604 p: show patch
596 605 c: commit selected changesets
597 606 q: quit and cancel transplant
598 607 ?: ? (show this help)
599 608 apply changeset? [ynmpcq?]: x
600 609 unrecognized response
601 610 apply changeset? [ynmpcq?]: q
602 611 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
603 612 > p
604 613 > y
605 614 > n
606 615 > n
607 616 > m
608 617 > c
609 618 > EOF
610 619 0:17ab29e464c6
611 620 apply changeset? [ynmpcq?]: p
612 621 diff -r 000000000000 -r 17ab29e464c6 r1
613 622 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
614 623 +++ b/r1 Thu Jan 01 00:00:00 1970 +0000
615 624 @@ -0,0 +1,1 @@
616 625 +r1
617 626 apply changeset? [ynmpcq?]: y
618 627 1:d11e3596cc1a
619 628 apply changeset? [ynmpcq?]: n
620 629 2:37a1297eb21b
621 630 apply changeset? [ynmpcq?]: n
622 631 3:722f4667af76
623 632 apply changeset? [ynmpcq?]: m
624 633 4:a53251cdf717
625 634 apply changeset? [ynmpcq?]: c
626 635 $ hg log -G --template "{node|short}"
627 636 @ 88be5dde5260
628 637 |\
629 638 | o 722f4667af76
630 639 | |
631 640 | o 37a1297eb21b
632 641 |/
633 642 o 17ab29e464c6
634 643
635 644 $ hg transplant -q --config ui.interactive=true -s ../t <<EOF
636 645 > x
637 646 > ?
638 647 > y
639 648 > q
640 649 > EOF
641 650 1:d11e3596cc1a
642 651 apply changeset? [ynmpcq?]: x
643 652 unrecognized response
644 653 apply changeset? [ynmpcq?]: ?
645 654 y: yes, transplant this changeset
646 655 n: no, skip this changeset
647 656 m: merge at this changeset
648 657 p: show patch
649 658 c: commit selected changesets
650 659 q: quit and cancel transplant
651 660 ?: ? (show this help)
652 661 apply changeset? [ynmpcq?]: y
653 662 4:a53251cdf717
654 663 apply changeset? [ynmpcq?]: q
655 664 $ hg heads --template "{node|short}\n"
656 665 88be5dde5260
657 666
658 667 $ cd ..
659 668
660 669
661 670 #if unix-permissions system-sh
662 671
663 672 test filter
664 673
665 674 $ hg init filter
666 675 $ cd filter
667 676 $ cat <<'EOF' >test-filter
668 677 > #!/bin/sh
669 678 > sed 's/r1/r2/' $1 > $1.new
670 679 > mv $1.new $1
671 680 > EOF
672 681 $ chmod +x test-filter
673 682 $ hg transplant -s ../t -b tip -a --filter ./test-filter
674 683 filtering * (glob)
675 684 applying 17ab29e464c6
676 685 17ab29e464c6 transplanted to e9ffc54ea104
677 686 filtering * (glob)
678 687 applying 37a1297eb21b
679 688 37a1297eb21b transplanted to 348b36d0b6a5
680 689 filtering * (glob)
681 690 applying 722f4667af76
682 691 722f4667af76 transplanted to 0aa6979afb95
683 692 filtering * (glob)
684 693 applying a53251cdf717
685 694 a53251cdf717 transplanted to 14f8512272b5
686 695 $ hg log --template '{rev} {parents} {desc}\n'
687 696 3 b3
688 697 2 b2
689 698 1 b1
690 699 0 r2
691 700 $ cd ..
692 701
693 702
694 703 test filter with failed patch
695 704
696 705 $ cd filter
697 706 $ hg up 0
698 707 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
699 708 $ echo foo > b1
700 709 $ hg ci -Am foo
701 710 adding b1
702 711 adding test-filter
703 712 created new head
704 713 $ hg transplant 1 --filter ./test-filter
705 714 filtering * (glob)
706 715 applying 348b36d0b6a5
707 716 file b1 already exists
708 717 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
709 718 patch failed to apply
710 719 abort: fix up the working directory and run hg transplant --continue
711 720 [255]
712 721 $ cd ..
713 722
714 723 test environment passed to filter
715 724
716 725 $ hg init filter-environment
717 726 $ cd filter-environment
718 727 $ cat <<'EOF' >test-filter-environment
719 728 > #!/bin/sh
720 729 > echo "Transplant by $HGUSER" >> $1
721 730 > echo "Transplant from rev $HGREVISION" >> $1
722 731 > EOF
723 732 $ chmod +x test-filter-environment
724 733 $ hg transplant -s ../t --filter ./test-filter-environment 0
725 734 filtering * (glob)
726 735 applying 17ab29e464c6
727 736 17ab29e464c6 transplanted to 5190e68026a0
728 737
729 738 $ hg log --template '{rev} {parents} {desc}\n'
730 739 0 r1
731 740 Transplant by test
732 741 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
733 742 $ cd ..
734 743
735 744 test transplant with filter handles invalid changelog
736 745
737 746 $ hg init filter-invalid-log
738 747 $ cd filter-invalid-log
739 748 $ cat <<'EOF' >test-filter-invalid-log
740 749 > #!/bin/sh
741 750 > echo "" > $1
742 751 > EOF
743 752 $ chmod +x test-filter-invalid-log
744 753 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
745 754 filtering * (glob)
746 755 abort: filter corrupted changeset (no user or date)
747 756 [255]
748 757 $ cd ..
749 758
750 759 #endif
751 760
752 761
753 762 test with a win32ext like setup (differing EOLs)
754 763
755 764 $ hg init twin1
756 765 $ cd twin1
757 766 $ echo a > a
758 767 $ echo b > b
759 768 $ echo b >> b
760 769 $ hg ci -Am t
761 770 adding a
762 771 adding b
763 772 $ echo a > b
764 773 $ echo b >> b
765 774 $ hg ci -m changeb
766 775 $ cd ..
767 776
768 777 $ hg init twin2
769 778 $ cd twin2
770 779 $ echo '[patch]' >> .hg/hgrc
771 780 $ echo 'eol = crlf' >> .hg/hgrc
772 781 $ "$PYTHON" -c "open('b', 'wb').write(b'b\r\nb\r\n')"
773 782 $ hg ci -Am addb
774 783 adding b
775 784 $ hg transplant -s ../twin1 tip
776 785 searching for changes
777 786 warning: repository is unrelated
778 787 applying 2e849d776c17
779 788 2e849d776c17 transplanted to 8e65bebc063e
780 789 $ cat b
781 790 a\r (esc)
782 791 b\r (esc)
783 792 $ cd ..
784 793
785 794 test transplant with merge changeset is skipped
786 795
787 796 $ hg init merge1a
788 797 $ cd merge1a
789 798 $ echo a > a
790 799 $ hg ci -Am a
791 800 adding a
792 801 $ hg branch b
793 802 marked working directory as branch b
794 803 (branches are permanent and global, did you want a bookmark?)
795 804 $ hg ci -m branchb
796 805 $ echo b > b
797 806 $ hg ci -Am b
798 807 adding b
799 808 $ hg update default
800 809 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
801 810 $ hg merge b
802 811 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
803 812 (branch merge, don't forget to commit)
804 813 $ hg ci -m mergeb
805 814 $ cd ..
806 815
807 816 $ hg init merge1b
808 817 $ cd merge1b
809 818 $ hg transplant -s ../merge1a tip
810 819 $ cd ..
811 820
812 821 test transplant with merge changeset accepts --parent
813 822
814 823 $ hg init merge2a
815 824 $ cd merge2a
816 825 $ echo a > a
817 826 $ hg ci -Am a
818 827 adding a
819 828 $ hg branch b
820 829 marked working directory as branch b
821 830 (branches are permanent and global, did you want a bookmark?)
822 831 $ hg ci -m branchb
823 832 $ echo b > b
824 833 $ hg ci -Am b
825 834 adding b
826 835 $ hg update default
827 836 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
828 837 $ hg merge b
829 838 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
830 839 (branch merge, don't forget to commit)
831 840 $ hg ci -m mergeb
832 841 $ cd ..
833 842
834 843 $ hg init merge2b
835 844 $ cd merge2b
836 845 $ hg transplant -s ../merge2a --parent tip tip
837 846 abort: be9f9b39483f is not a parent of be9f9b39483f
838 847 [255]
839 848 $ hg transplant -s ../merge2a --parent 0 tip
840 849 applying be9f9b39483f
841 850 be9f9b39483f transplanted to 9959e51f94d1
842 851 $ cd ..
843 852
844 853 test transplanting a patch turning into a no-op
845 854
846 855 $ hg init binarysource
847 856 $ cd binarysource
848 857 $ echo a > a
849 858 $ hg ci -Am adda a
850 859 >>> open('b', 'wb').write(b'\0b1') and None
851 860 $ hg ci -Am addb b
852 861 >>> open('b', 'wb').write(b'\0b2') and None
853 862 $ hg ci -m changeb b
854 863 $ cd ..
855 864
856 865 $ hg clone -r0 binarysource binarydest
857 866 adding changesets
858 867 adding manifests
859 868 adding file changes
860 869 added 1 changesets with 1 changes to 1 files
861 870 new changesets 07f494440405
862 871 updating to branch default
863 872 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
864 873 $ cd binarydest
865 874 $ cp ../binarysource/b b
866 875 $ hg ci -Am addb2 b
867 876 $ hg transplant -s ../binarysource 2
868 877 searching for changes
869 878 applying 7a7d57e15850
870 879 skipping emptied changeset 7a7d57e15850
871 880
872 881 Test empty result in --continue
873 882
874 883 $ hg transplant -s ../binarysource 1
875 884 searching for changes
876 885 applying 645035761929
877 886 file b already exists
878 887 1 out of 1 hunks FAILED -- saving rejects to file b.rej
879 888 patch failed to apply
880 889 abort: fix up the working directory and run hg transplant --continue
881 890 [255]
882 891 $ hg status
883 892 ? b.rej
884 $ hg transplant --continue
893 $ hg continue
885 894 645035761929 skipped due to empty diff
886 895
887 896 $ cd ..
888 897
889 898 Explicitly kill daemons to let the test exit on Windows
890 899
891 900 $ killdaemons.py
892 901
893 902 Test that patch-ed files are treated as "modified", when transplant is
894 903 aborted by failure of patching, even if none of mode, size and
895 904 timestamp of them isn't changed on the filesystem (see also issue4583)
896 905
897 906 $ cd t
898 907
899 908 $ cat > $TESTTMP/abort.py <<EOF
900 909 > # emulate that patch.patch() is aborted at patching on "abort" file
901 910 > from mercurial import error, extensions, patch as patchmod
902 911 > def patch(orig, ui, repo, patchname,
903 912 > strip=1, prefix=b'', files=None,
904 913 > eolmode=b'strict', similarity=0):
905 914 > if files is None:
906 915 > files = set()
907 916 > r = orig(ui, repo, patchname,
908 917 > strip=strip, prefix=prefix, files=files,
909 918 > eolmode=eolmode, similarity=similarity)
910 919 > if b'abort' in files:
911 920 > raise error.PatchError('intentional error while patching')
912 921 > return r
913 922 > def extsetup(ui):
914 923 > extensions.wrapfunction(patchmod, 'patch', patch)
915 924 > EOF
916 925
917 926 $ echo X1 > r1
918 927 $ hg diff --nodates r1
919 928 diff -r a53251cdf717 r1
920 929 --- a/r1
921 930 +++ b/r1
922 931 @@ -1,1 +1,1 @@
923 932 -r1
924 933 +X1
925 934 $ hg commit -m "X1 as r1"
926 935
927 936 $ echo 'marking to abort patching' > abort
928 937 $ hg add abort
929 938 $ echo Y1 > r1
930 939 $ hg diff --nodates r1
931 940 diff -r 22c515968f13 r1
932 941 --- a/r1
933 942 +++ b/r1
934 943 @@ -1,1 +1,1 @@
935 944 -X1
936 945 +Y1
937 946 $ hg commit -m "Y1 as r1"
938 947
939 948 $ hg update -q -C d11e3596cc1a
940 949 $ cat r1
941 950 r1
942 951
943 952 $ cat >> .hg/hgrc <<EOF
944 953 > [fakedirstatewritetime]
945 954 > # emulate invoking dirstate.write() via repo.status() or markcommitted()
946 955 > # at 2000-01-01 00:00
947 956 > fakenow = 200001010000
948 957 >
949 958 > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
950 959 > [fakepatchtime]
951 960 > fakenow = 200001010000
952 961 >
953 962 > [extensions]
954 963 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
955 964 > fakepatchtime = $TESTDIR/fakepatchtime.py
956 965 > abort = $TESTTMP/abort.py
957 966 > EOF
958 967 $ hg transplant "22c515968f13::"
959 968 applying 22c515968f13
960 969 22c515968f13 transplanted to * (glob)
961 970 applying e38700ba9dd3
962 971 intentional error while patching
963 972 abort: fix up the working directory and run hg transplant --continue
964 973 [255]
965 974 $ cat >> .hg/hgrc <<EOF
966 975 > [hooks]
967 976 > fakedirstatewritetime = !
968 977 > fakepatchtime = !
969 978 > [extensions]
970 979 > abort = !
971 980 > EOF
972 981
973 982 $ cat r1
974 983 Y1
975 984 $ hg debugstate | grep ' r1$'
976 985 n 644 3 unset r1
977 986 $ hg status -A r1
978 987 M r1
979 988
980 989 Test that rollback by unexpected failure after transplanting the first
981 990 revision restores dirstate correctly.
982 991
983 992 $ hg rollback -q
984 993 $ rm -f abort
985 994 $ hg update -q -C d11e3596cc1a
986 995 $ hg parents -T "{node|short}\n"
987 996 d11e3596cc1a
988 997 $ hg status -A
989 998 C r1
990 999 C r2
991 1000
992 1001 $ cat >> .hg/hgrc <<EOF
993 1002 > [hooks]
994 1003 > # emulate failure at transplanting the 2nd revision
995 1004 > pretxncommit.abort = test ! -f abort
996 1005 > EOF
997 1006 $ hg transplant "22c515968f13::"
998 1007 applying 22c515968f13
999 1008 22c515968f13 transplanted to * (glob)
1000 1009 applying e38700ba9dd3
1001 1010 transaction abort!
1002 1011 rollback completed
1003 1012 abort: pretxncommit.abort hook exited with status 1
1004 1013 [255]
1005 1014 $ cat >> .hg/hgrc <<EOF
1006 1015 > [hooks]
1007 1016 > pretxncommit.abort = !
1008 1017 > EOF
1009 1018
1010 1019 $ hg parents -T "{node|short}\n"
1011 1020 d11e3596cc1a
1012 1021 $ hg status -A
1013 1022 M r1
1014 1023 ? abort
1015 1024 C r2
1016 1025
1017 1026 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now