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