##// END OF EJS Templates
transplant: fix emptied changeset message...
Patrick Mezard -
r17320:0c3c65c0 stable
parent child Browse files
Show More
@@ -1,676 +1,676 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 patches from another branch.
11 11
12 12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 13 map from a changeset hash to its hash in the source repository.
14 14 '''
15 15
16 16 from mercurial.i18n import _
17 17 import os, tempfile
18 18 from mercurial.node import short
19 19 from mercurial import bundlerepo, hg, merge, match
20 20 from mercurial import patch, revlog, scmutil, util, error, cmdutil
21 21 from mercurial import revset, templatekw
22 22
23 23 class TransplantError(error.Abort):
24 24 pass
25 25
26 26 cmdtable = {}
27 27 command = cmdutil.command(cmdtable)
28 28 testedwith = 'internal'
29 29
30 30 class transplantentry(object):
31 31 def __init__(self, lnode, rnode):
32 32 self.lnode = lnode
33 33 self.rnode = rnode
34 34
35 35 class transplants(object):
36 36 def __init__(self, path=None, transplantfile=None, opener=None):
37 37 self.path = path
38 38 self.transplantfile = transplantfile
39 39 self.opener = opener
40 40
41 41 if not opener:
42 42 self.opener = scmutil.opener(self.path)
43 43 self.transplants = {}
44 44 self.dirty = False
45 45 self.read()
46 46
47 47 def read(self):
48 48 abspath = os.path.join(self.path, self.transplantfile)
49 49 if self.transplantfile and os.path.exists(abspath):
50 50 for line in self.opener.read(self.transplantfile).splitlines():
51 51 lnode, rnode = map(revlog.bin, line.split(':'))
52 52 list = self.transplants.setdefault(rnode, [])
53 53 list.append(transplantentry(lnode, rnode))
54 54
55 55 def write(self):
56 56 if self.dirty and self.transplantfile:
57 57 if not os.path.isdir(self.path):
58 58 os.mkdir(self.path)
59 59 fp = self.opener(self.transplantfile, 'w')
60 60 for list in self.transplants.itervalues():
61 61 for t in list:
62 62 l, r = map(revlog.hex, (t.lnode, t.rnode))
63 63 fp.write(l + ':' + r + '\n')
64 64 fp.close()
65 65 self.dirty = False
66 66
67 67 def get(self, rnode):
68 68 return self.transplants.get(rnode) or []
69 69
70 70 def set(self, lnode, rnode):
71 71 list = self.transplants.setdefault(rnode, [])
72 72 list.append(transplantentry(lnode, rnode))
73 73 self.dirty = True
74 74
75 75 def remove(self, transplant):
76 76 list = self.transplants.get(transplant.rnode)
77 77 if list:
78 78 del list[list.index(transplant)]
79 79 self.dirty = True
80 80
81 81 class transplanter(object):
82 82 def __init__(self, ui, repo):
83 83 self.ui = ui
84 84 self.path = repo.join('transplant')
85 85 self.opener = scmutil.opener(self.path)
86 86 self.transplants = transplants(self.path, 'transplants',
87 87 opener=self.opener)
88 88 self.editor = None
89 89
90 90 def applied(self, repo, node, parent):
91 91 '''returns True if a node is already an ancestor of parent
92 92 or is parent or has already been transplanted'''
93 93 if hasnode(repo, parent):
94 94 parentrev = repo.changelog.rev(parent)
95 95 if hasnode(repo, node):
96 96 rev = repo.changelog.rev(node)
97 97 reachable = repo.changelog.incancestors([parentrev], rev)
98 98 if rev in reachable:
99 99 return True
100 100 for t in self.transplants.get(node):
101 101 # it might have been stripped
102 102 if not hasnode(repo, t.lnode):
103 103 self.transplants.remove(t)
104 104 return False
105 105 lnoderev = repo.changelog.rev(t.lnode)
106 106 if lnoderev in repo.changelog.incancestors([parentrev], lnoderev):
107 107 return True
108 108 return False
109 109
110 110 def apply(self, repo, source, revmap, merges, opts={}):
111 111 '''apply the revisions in revmap one by one in revision order'''
112 112 revs = sorted(revmap)
113 113 p1, p2 = repo.dirstate.parents()
114 114 pulls = []
115 115 diffopts = patch.diffopts(self.ui, opts)
116 116 diffopts.git = True
117 117
118 118 lock = wlock = tr = None
119 119 try:
120 120 wlock = repo.wlock()
121 121 lock = repo.lock()
122 122 tr = repo.transaction('transplant')
123 123 for rev in revs:
124 124 node = revmap[rev]
125 125 revstr = '%s:%s' % (rev, short(node))
126 126
127 127 if self.applied(repo, node, p1):
128 128 self.ui.warn(_('skipping already applied revision %s\n') %
129 129 revstr)
130 130 continue
131 131
132 132 parents = source.changelog.parents(node)
133 133 if not (opts.get('filter') or opts.get('log')):
134 134 # If the changeset parent is the same as the
135 135 # wdir's parent, just pull it.
136 136 if parents[0] == p1:
137 137 pulls.append(node)
138 138 p1 = node
139 139 continue
140 140 if pulls:
141 141 if source != repo:
142 142 repo.pull(source.peer(), heads=pulls)
143 143 merge.update(repo, pulls[-1], False, False, None)
144 144 p1, p2 = repo.dirstate.parents()
145 145 pulls = []
146 146
147 147 domerge = False
148 148 if node in merges:
149 149 # pulling all the merge revs at once would mean we
150 150 # couldn't transplant after the latest even if
151 151 # transplants before them fail.
152 152 domerge = True
153 153 if not hasnode(repo, node):
154 154 repo.pull(source, heads=[node])
155 155
156 156 skipmerge = False
157 157 if parents[1] != revlog.nullid:
158 158 if not opts.get('parent'):
159 159 self.ui.note(_('skipping merge changeset %s:%s\n')
160 160 % (rev, short(node)))
161 161 skipmerge = True
162 162 else:
163 163 parent = source.lookup(opts['parent'])
164 164 if parent not in parents:
165 165 raise util.Abort(_('%s is not a parent of %s') %
166 166 (short(parent), short(node)))
167 167 else:
168 168 parent = parents[0]
169 169
170 170 if skipmerge:
171 171 patchfile = None
172 172 else:
173 173 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
174 174 fp = os.fdopen(fd, 'w')
175 175 gen = patch.diff(source, parent, node, opts=diffopts)
176 176 for chunk in gen:
177 177 fp.write(chunk)
178 178 fp.close()
179 179
180 180 del revmap[rev]
181 181 if patchfile or domerge:
182 182 try:
183 183 try:
184 184 n = self.applyone(repo, node,
185 185 source.changelog.read(node),
186 186 patchfile, merge=domerge,
187 187 log=opts.get('log'),
188 188 filter=opts.get('filter'))
189 189 except TransplantError:
190 190 # Do not rollback, it is up to the user to
191 191 # fix the merge or cancel everything
192 192 tr.close()
193 193 raise
194 194 if n and domerge:
195 195 self.ui.status(_('%s merged at %s\n') % (revstr,
196 196 short(n)))
197 197 elif n:
198 198 self.ui.status(_('%s transplanted to %s\n')
199 199 % (short(node),
200 200 short(n)))
201 201 finally:
202 202 if patchfile:
203 203 os.unlink(patchfile)
204 204 tr.close()
205 205 if pulls:
206 206 repo.pull(source.peer(), heads=pulls)
207 207 merge.update(repo, pulls[-1], False, False, None)
208 208 finally:
209 209 self.saveseries(revmap, merges)
210 210 self.transplants.write()
211 211 if tr:
212 212 tr.release()
213 213 lock.release()
214 214 wlock.release()
215 215
216 216 def filter(self, filter, node, changelog, patchfile):
217 217 '''arbitrarily rewrite changeset before applying it'''
218 218
219 219 self.ui.status(_('filtering %s\n') % patchfile)
220 220 user, date, msg = (changelog[1], changelog[2], changelog[4])
221 221 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
222 222 fp = os.fdopen(fd, 'w')
223 223 fp.write("# HG changeset patch\n")
224 224 fp.write("# User %s\n" % user)
225 225 fp.write("# Date %d %d\n" % date)
226 226 fp.write(msg + '\n')
227 227 fp.close()
228 228
229 229 try:
230 230 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
231 231 util.shellquote(patchfile)),
232 232 environ={'HGUSER': changelog[1],
233 233 'HGREVISION': revlog.hex(node),
234 234 },
235 235 onerr=util.Abort, errprefix=_('filter failed'),
236 236 out=self.ui.fout)
237 237 user, date, msg = self.parselog(file(headerfile))[1:4]
238 238 finally:
239 239 os.unlink(headerfile)
240 240
241 241 return (user, date, msg)
242 242
243 243 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
244 244 filter=None):
245 245 '''apply the patch in patchfile to the repository as a transplant'''
246 246 (manifest, user, (time, timezone), files, message) = cl[:5]
247 247 date = "%d %d" % (time, timezone)
248 248 extra = {'transplant_source': node}
249 249 if filter:
250 250 (user, date, message) = self.filter(filter, node, cl, patchfile)
251 251
252 252 if log:
253 253 # we don't translate messages inserted into commits
254 254 message += '\n(transplanted from %s)' % revlog.hex(node)
255 255
256 256 self.ui.status(_('applying %s\n') % short(node))
257 257 self.ui.note('%s %s\n%s\n' % (user, date, message))
258 258
259 259 if not patchfile and not merge:
260 260 raise util.Abort(_('can only omit patchfile if merging'))
261 261 if patchfile:
262 262 try:
263 263 files = set()
264 264 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
265 265 files = list(files)
266 266 except Exception, inst:
267 267 seriespath = os.path.join(self.path, 'series')
268 268 if os.path.exists(seriespath):
269 269 os.unlink(seriespath)
270 270 p1 = repo.dirstate.p1()
271 271 p2 = node
272 272 self.log(user, date, message, p1, p2, merge=merge)
273 273 self.ui.write(str(inst) + '\n')
274 274 raise TransplantError(_('fix up the merge and run '
275 275 'hg transplant --continue'))
276 276 else:
277 277 files = None
278 278 if merge:
279 279 p1, p2 = repo.dirstate.parents()
280 280 repo.setparents(p1, node)
281 281 m = match.always(repo.root, '')
282 282 else:
283 283 m = match.exact(repo.root, '', files)
284 284
285 285 n = repo.commit(message, user, date, extra=extra, match=m,
286 286 editor=self.editor)
287 287 if not n:
288 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
288 self.ui.warn(_('skipping emptied changeset %s\n') % short(node))
289 289 return None
290 290 if not merge:
291 291 self.transplants.set(n, node)
292 292
293 293 return n
294 294
295 295 def resume(self, repo, source, opts=None):
296 296 '''recover last transaction and apply remaining changesets'''
297 297 if os.path.exists(os.path.join(self.path, 'journal')):
298 298 n, node = self.recover(repo)
299 299 self.ui.status(_('%s transplanted as %s\n') % (short(node),
300 300 short(n)))
301 301 seriespath = os.path.join(self.path, 'series')
302 302 if not os.path.exists(seriespath):
303 303 self.transplants.write()
304 304 return
305 305 nodes, merges = self.readseries()
306 306 revmap = {}
307 307 for n in nodes:
308 308 revmap[source.changelog.rev(n)] = n
309 309 os.unlink(seriespath)
310 310
311 311 self.apply(repo, source, revmap, merges, opts)
312 312
313 313 def recover(self, repo):
314 314 '''commit working directory using journal metadata'''
315 315 node, user, date, message, parents = self.readlog()
316 316 merge = False
317 317
318 318 if not user or not date or not message or not parents[0]:
319 319 raise util.Abort(_('transplant log file is corrupt'))
320 320
321 321 parent = parents[0]
322 322 if len(parents) > 1:
323 323 if opts.get('parent'):
324 324 parent = source.lookup(opts['parent'])
325 325 if parent not in parents:
326 326 raise util.Abort(_('%s is not a parent of %s') %
327 327 (short(parent), short(node)))
328 328 else:
329 329 merge = True
330 330
331 331 extra = {'transplant_source': node}
332 332 wlock = repo.wlock()
333 333 try:
334 334 p1, p2 = repo.dirstate.parents()
335 335 if p1 != parent:
336 336 raise util.Abort(
337 337 _('working dir not at transplant parent %s') %
338 338 revlog.hex(parent))
339 339 if merge:
340 340 repo.setparents(p1, parents[1])
341 341 n = repo.commit(message, user, date, extra=extra,
342 342 editor=self.editor)
343 343 if not n:
344 344 raise util.Abort(_('commit failed'))
345 345 if not merge:
346 346 self.transplants.set(n, node)
347 347 self.unlog()
348 348
349 349 return n, node
350 350 finally:
351 351 wlock.release()
352 352
353 353 def readseries(self):
354 354 nodes = []
355 355 merges = []
356 356 cur = nodes
357 357 for line in self.opener.read('series').splitlines():
358 358 if line.startswith('# Merges'):
359 359 cur = merges
360 360 continue
361 361 cur.append(revlog.bin(line))
362 362
363 363 return (nodes, merges)
364 364
365 365 def saveseries(self, revmap, merges):
366 366 if not revmap:
367 367 return
368 368
369 369 if not os.path.isdir(self.path):
370 370 os.mkdir(self.path)
371 371 series = self.opener('series', 'w')
372 372 for rev in sorted(revmap):
373 373 series.write(revlog.hex(revmap[rev]) + '\n')
374 374 if merges:
375 375 series.write('# Merges\n')
376 376 for m in merges:
377 377 series.write(revlog.hex(m) + '\n')
378 378 series.close()
379 379
380 380 def parselog(self, fp):
381 381 parents = []
382 382 message = []
383 383 node = revlog.nullid
384 384 inmsg = False
385 385 user = None
386 386 date = None
387 387 for line in fp.read().splitlines():
388 388 if inmsg:
389 389 message.append(line)
390 390 elif line.startswith('# User '):
391 391 user = line[7:]
392 392 elif line.startswith('# Date '):
393 393 date = line[7:]
394 394 elif line.startswith('# Node ID '):
395 395 node = revlog.bin(line[10:])
396 396 elif line.startswith('# Parent '):
397 397 parents.append(revlog.bin(line[9:]))
398 398 elif not line.startswith('# '):
399 399 inmsg = True
400 400 message.append(line)
401 401 if None in (user, date):
402 402 raise util.Abort(_("filter corrupted changeset (no user or date)"))
403 403 return (node, user, date, '\n'.join(message), parents)
404 404
405 405 def log(self, user, date, message, p1, p2, merge=False):
406 406 '''journal changelog metadata for later recover'''
407 407
408 408 if not os.path.isdir(self.path):
409 409 os.mkdir(self.path)
410 410 fp = self.opener('journal', 'w')
411 411 fp.write('# User %s\n' % user)
412 412 fp.write('# Date %s\n' % date)
413 413 fp.write('# Node ID %s\n' % revlog.hex(p2))
414 414 fp.write('# Parent ' + revlog.hex(p1) + '\n')
415 415 if merge:
416 416 fp.write('# Parent ' + revlog.hex(p2) + '\n')
417 417 fp.write(message.rstrip() + '\n')
418 418 fp.close()
419 419
420 420 def readlog(self):
421 421 return self.parselog(self.opener('journal'))
422 422
423 423 def unlog(self):
424 424 '''remove changelog journal'''
425 425 absdst = os.path.join(self.path, 'journal')
426 426 if os.path.exists(absdst):
427 427 os.unlink(absdst)
428 428
429 429 def transplantfilter(self, repo, source, root):
430 430 def matchfn(node):
431 431 if self.applied(repo, node, root):
432 432 return False
433 433 if source.changelog.parents(node)[1] != revlog.nullid:
434 434 return False
435 435 extra = source.changelog.read(node)[5]
436 436 cnode = extra.get('transplant_source')
437 437 if cnode and self.applied(repo, cnode, root):
438 438 return False
439 439 return True
440 440
441 441 return matchfn
442 442
443 443 def hasnode(repo, node):
444 444 try:
445 445 return repo.changelog.rev(node) is not None
446 446 except error.RevlogError:
447 447 return False
448 448
449 449 def browserevs(ui, repo, nodes, opts):
450 450 '''interactively transplant changesets'''
451 451 def browsehelp(ui):
452 452 ui.write(_('y: transplant this changeset\n'
453 453 'n: skip this changeset\n'
454 454 'm: merge at this changeset\n'
455 455 'p: show patch\n'
456 456 'c: commit selected changesets\n'
457 457 'q: cancel transplant\n'
458 458 '?: show this help\n'))
459 459
460 460 displayer = cmdutil.show_changeset(ui, repo, opts)
461 461 transplants = []
462 462 merges = []
463 463 for node in nodes:
464 464 displayer.show(repo[node])
465 465 action = None
466 466 while not action:
467 467 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
468 468 if action == '?':
469 469 browsehelp(ui)
470 470 action = None
471 471 elif action == 'p':
472 472 parent = repo.changelog.parents(node)[0]
473 473 for chunk in patch.diff(repo, parent, node):
474 474 ui.write(chunk)
475 475 action = None
476 476 elif action not in ('y', 'n', 'm', 'c', 'q'):
477 477 ui.write(_('no such option\n'))
478 478 action = None
479 479 if action == 'y':
480 480 transplants.append(node)
481 481 elif action == 'm':
482 482 merges.append(node)
483 483 elif action == 'c':
484 484 break
485 485 elif action == 'q':
486 486 transplants = ()
487 487 merges = ()
488 488 break
489 489 displayer.close()
490 490 return (transplants, merges)
491 491
492 492 @command('transplant',
493 493 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
494 494 ('b', 'branch', [],
495 495 _('pull patches from branch BRANCH'), _('BRANCH')),
496 496 ('a', 'all', None, _('pull all changesets up to BRANCH')),
497 497 ('p', 'prune', [], _('skip over REV'), _('REV')),
498 498 ('m', 'merge', [], _('merge at REV'), _('REV')),
499 499 ('', 'parent', '',
500 500 _('parent to choose when transplanting merge'), _('REV')),
501 501 ('e', 'edit', False, _('invoke editor on commit messages')),
502 502 ('', 'log', None, _('append transplant info to log message')),
503 503 ('c', 'continue', None, _('continue last transplant session '
504 504 'after repair')),
505 505 ('', 'filter', '',
506 506 _('filter changesets through command'), _('CMD'))],
507 507 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
508 508 '[-m REV] [REV]...'))
509 509 def transplant(ui, repo, *revs, **opts):
510 510 '''transplant changesets from another branch
511 511
512 512 Selected changesets will be applied on top of the current working
513 513 directory with the log of the original changeset. The changesets
514 514 are copied and will thus appear twice in the history. Use the
515 515 rebase extension instead if you want to move a whole branch of
516 516 unpublished changesets.
517 517
518 518 If --log is specified, log messages will have a comment appended
519 519 of the form::
520 520
521 521 (transplanted from CHANGESETHASH)
522 522
523 523 You can rewrite the changelog message with the --filter option.
524 524 Its argument will be invoked with the current changelog message as
525 525 $1 and the patch as $2.
526 526
527 527 If --source/-s is specified, selects changesets from the named
528 528 repository. If --branch/-b is specified, selects changesets from
529 529 the branch holding the named revision, up to that revision. If
530 530 --all/-a is specified, all changesets on the branch will be
531 531 transplanted, otherwise you will be prompted to select the
532 532 changesets you want.
533 533
534 534 :hg:`transplant --branch REV --all` will transplant the
535 535 selected branch (up to the named revision) onto your current
536 536 working directory.
537 537
538 538 You can optionally mark selected transplanted changesets as merge
539 539 changesets. You will not be prompted to transplant any ancestors
540 540 of a merged transplant, and you can merge descendants of them
541 541 normally instead of transplanting them.
542 542
543 543 Merge changesets may be transplanted directly by specifying the
544 544 proper parent changeset by calling :hg:`transplant --parent`.
545 545
546 546 If no merges or revisions are provided, :hg:`transplant` will
547 547 start an interactive changeset browser.
548 548
549 549 If a changeset application fails, you can fix the merge by hand
550 550 and then resume where you left off by calling :hg:`transplant
551 551 --continue/-c`.
552 552 '''
553 553 def incwalk(repo, csets, match=util.always):
554 554 for node in csets:
555 555 if match(node):
556 556 yield node
557 557
558 558 def transplantwalk(repo, root, branches, match=util.always):
559 559 if not branches:
560 560 branches = repo.heads()
561 561 ancestors = []
562 562 for branch in branches:
563 563 ancestors.append(repo.changelog.ancestor(root, branch))
564 564 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
565 565 if match(node):
566 566 yield node
567 567
568 568 def checkopts(opts, revs):
569 569 if opts.get('continue'):
570 570 if opts.get('branch') or opts.get('all') or opts.get('merge'):
571 571 raise util.Abort(_('--continue is incompatible with '
572 572 'branch, all or merge'))
573 573 return
574 574 if not (opts.get('source') or revs or
575 575 opts.get('merge') or opts.get('branch')):
576 576 raise util.Abort(_('no source URL, branch tag or revision '
577 577 'list provided'))
578 578 if opts.get('all'):
579 579 if not opts.get('branch'):
580 580 raise util.Abort(_('--all requires a branch revision'))
581 581 if revs:
582 582 raise util.Abort(_('--all is incompatible with a '
583 583 'revision list'))
584 584
585 585 checkopts(opts, revs)
586 586
587 587 if not opts.get('log'):
588 588 opts['log'] = ui.config('transplant', 'log')
589 589 if not opts.get('filter'):
590 590 opts['filter'] = ui.config('transplant', 'filter')
591 591
592 592 tp = transplanter(ui, repo)
593 593 if opts.get('edit'):
594 594 tp.editor = cmdutil.commitforceeditor
595 595
596 596 p1, p2 = repo.dirstate.parents()
597 597 if len(repo) > 0 and p1 == revlog.nullid:
598 598 raise util.Abort(_('no revision checked out'))
599 599 if not opts.get('continue'):
600 600 if p2 != revlog.nullid:
601 601 raise util.Abort(_('outstanding uncommitted merges'))
602 602 m, a, r, d = repo.status()[:4]
603 603 if m or a or r or d:
604 604 raise util.Abort(_('outstanding local changes'))
605 605
606 606 sourcerepo = opts.get('source')
607 607 if sourcerepo:
608 608 peer = hg.peer(ui, opts, ui.expandpath(sourcerepo))
609 609 branches = map(peer.lookup, opts.get('branch', ()))
610 610 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
611 611 onlyheads=branches, force=True)
612 612 else:
613 613 source = repo
614 614 branches = map(source.lookup, opts.get('branch', ()))
615 615 cleanupfn = None
616 616
617 617 try:
618 618 if opts.get('continue'):
619 619 tp.resume(repo, source, opts)
620 620 return
621 621
622 622 tf = tp.transplantfilter(repo, source, p1)
623 623 if opts.get('prune'):
624 624 prune = [source.lookup(r)
625 625 for r in scmutil.revrange(source, opts.get('prune'))]
626 626 matchfn = lambda x: tf(x) and x not in prune
627 627 else:
628 628 matchfn = tf
629 629 merges = map(source.lookup, opts.get('merge', ()))
630 630 revmap = {}
631 631 if revs:
632 632 for r in scmutil.revrange(source, revs):
633 633 revmap[int(r)] = source.lookup(r)
634 634 elif opts.get('all') or not merges:
635 635 if source != repo:
636 636 alltransplants = incwalk(source, csets, match=matchfn)
637 637 else:
638 638 alltransplants = transplantwalk(source, p1, branches,
639 639 match=matchfn)
640 640 if opts.get('all'):
641 641 revs = alltransplants
642 642 else:
643 643 revs, newmerges = browserevs(ui, source, alltransplants, opts)
644 644 merges.extend(newmerges)
645 645 for r in revs:
646 646 revmap[source.changelog.rev(r)] = r
647 647 for r in merges:
648 648 revmap[source.changelog.rev(r)] = r
649 649
650 650 tp.apply(repo, source, revmap, merges, opts)
651 651 finally:
652 652 if cleanupfn:
653 653 cleanupfn()
654 654
655 655 def revsettransplanted(repo, subset, x):
656 656 """``transplanted([set])``
657 657 Transplanted changesets in set, or all transplanted changesets.
658 658 """
659 659 if x:
660 660 s = revset.getset(repo, subset, x)
661 661 else:
662 662 s = subset
663 663 return [r for r in s if repo[r].extra().get('transplant_source')]
664 664
665 665 def kwtransplanted(repo, ctx, **args):
666 666 """:transplanted: String. The node identifier of the transplanted
667 667 changeset if any."""
668 668 n = ctx.extra().get('transplant_source')
669 669 return n and revlog.hex(n) or ''
670 670
671 671 def extsetup(ui):
672 672 revset.symbols['transplanted'] = revsettransplanted
673 673 templatekw.keywords['transplanted'] = kwtransplanted
674 674
675 675 # tell hggettext to extract docstrings from these functions:
676 676 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,634 +1,634 b''
1 1 $ "$TESTDIR/hghave" serve || exit 80
2 2
3 3 $ cat <<EOF >> $HGRCPATH
4 4 > [extensions]
5 5 > transplant=
6 6 > EOF
7 7
8 8 $ hg init t
9 9 $ cd t
10 10 $ echo r1 > r1
11 11 $ hg ci -Amr1 -d'0 0'
12 12 adding r1
13 13 $ echo r2 > r2
14 14 $ hg ci -Amr2 -d'1 0'
15 15 adding r2
16 16 $ hg up 0
17 17 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
18 18
19 19 $ echo b1 > b1
20 20 $ hg ci -Amb1 -d '0 0'
21 21 adding b1
22 22 created new head
23 23 $ echo b2 > b2
24 24 $ hg ci -Amb2 -d '1 0'
25 25 adding b2
26 26 $ echo b3 > b3
27 27 $ hg ci -Amb3 -d '2 0'
28 28 adding b3
29 29
30 30 $ hg log --template '{rev} {parents} {desc}\n'
31 31 4 b3
32 32 3 b2
33 33 2 0:17ab29e464c6 b1
34 34 1 r2
35 35 0 r1
36 36
37 37 $ hg clone . ../rebase
38 38 updating to branch default
39 39 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 40 $ cd ../rebase
41 41
42 42 $ hg up -C 1
43 43 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
44 44
45 45 rebase b onto r1
46 46
47 47 $ hg transplant -a -b tip
48 48 applying 37a1297eb21b
49 49 37a1297eb21b transplanted to e234d668f844
50 50 applying 722f4667af76
51 51 722f4667af76 transplanted to 539f377d78df
52 52 applying a53251cdf717
53 53 a53251cdf717 transplanted to ffd6818a3975
54 54 $ hg log --template '{rev} {parents} {desc}\n'
55 55 7 b3
56 56 6 b2
57 57 5 1:d11e3596cc1a b1
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 test transplanted revset
65 65
66 66 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
67 67 5 1:d11e3596cc1a b1
68 68 6 b2
69 69 7 b3
70 70 $ hg help revsets | grep transplanted
71 71 "transplanted([set])"
72 72 Transplanted changesets in set, or all transplanted changesets.
73 73
74 74 test tranplanted keyword
75 75
76 76 $ hg log --template '{rev} {transplanted}\n'
77 77 7 a53251cdf717679d1907b289f991534be05c997a
78 78 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
79 79 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
80 80 4
81 81 3
82 82 2
83 83 1
84 84 0
85 85
86 86 test destination() revset predicate with a transplant of a transplant; new
87 87 clone so subsequent rollback isn't affected
88 88 $ hg clone -q . ../destination
89 89 $ cd ../destination
90 90 $ hg up -Cq 0
91 91 $ hg branch -q b4
92 92 $ hg ci -qm "b4"
93 93 $ hg transplant 7
94 94 applying ffd6818a3975
95 95 ffd6818a3975 transplanted to 502236fa76bb
96 96
97 97
98 98 $ hg log -r 'destination()'
99 99 changeset: 5:e234d668f844
100 100 parent: 1:d11e3596cc1a
101 101 user: test
102 102 date: Thu Jan 01 00:00:00 1970 +0000
103 103 summary: b1
104 104
105 105 changeset: 6:539f377d78df
106 106 user: test
107 107 date: Thu Jan 01 00:00:01 1970 +0000
108 108 summary: b2
109 109
110 110 changeset: 7:ffd6818a3975
111 111 user: test
112 112 date: Thu Jan 01 00:00:02 1970 +0000
113 113 summary: b3
114 114
115 115 changeset: 9:502236fa76bb
116 116 branch: b4
117 117 tag: tip
118 118 user: test
119 119 date: Thu Jan 01 00:00:02 1970 +0000
120 120 summary: b3
121 121
122 122 $ hg log -r 'destination(a53251cdf717)'
123 123 changeset: 7:ffd6818a3975
124 124 user: test
125 125 date: Thu Jan 01 00:00:02 1970 +0000
126 126 summary: b3
127 127
128 128 changeset: 9:502236fa76bb
129 129 branch: b4
130 130 tag: tip
131 131 user: test
132 132 date: Thu Jan 01 00:00:02 1970 +0000
133 133 summary: b3
134 134
135 135
136 136 test subset parameter in reverse order
137 137 $ hg log -r 'reverse(all()) and destination(a53251cdf717)'
138 138 changeset: 9:502236fa76bb
139 139 branch: b4
140 140 tag: tip
141 141 user: test
142 142 date: Thu Jan 01 00:00:02 1970 +0000
143 143 summary: b3
144 144
145 145 changeset: 7:ffd6818a3975
146 146 user: test
147 147 date: Thu Jan 01 00:00:02 1970 +0000
148 148 summary: b3
149 149
150 150
151 151 back to the original dir
152 152 $ cd ../rebase
153 153
154 154 rollback the transplant
155 155 $ hg rollback
156 156 repository tip rolled back to revision 4 (undo transplant)
157 157 working directory now based on revision 1
158 158 $ hg tip -q
159 159 4:a53251cdf717
160 160 $ hg parents -q
161 161 1:d11e3596cc1a
162 162 $ hg status
163 163 ? b1
164 164 ? b2
165 165 ? b3
166 166
167 167 $ hg clone ../t ../prune
168 168 updating to branch default
169 169 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 170 $ cd ../prune
171 171
172 172 $ hg up -C 1
173 173 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
174 174
175 175 rebase b onto r1, skipping b2
176 176
177 177 $ hg transplant -a -b tip -p 3
178 178 applying 37a1297eb21b
179 179 37a1297eb21b transplanted to e234d668f844
180 180 applying a53251cdf717
181 181 a53251cdf717 transplanted to 7275fda4d04f
182 182 $ hg log --template '{rev} {parents} {desc}\n'
183 183 6 b3
184 184 5 1:d11e3596cc1a b1
185 185 4 b3
186 186 3 b2
187 187 2 0:17ab29e464c6 b1
188 188 1 r2
189 189 0 r1
190 190
191 191 test same-parent transplant with --log
192 192
193 193 $ hg clone -r 1 ../t ../sameparent
194 194 adding changesets
195 195 adding manifests
196 196 adding file changes
197 197 added 2 changesets with 2 changes to 2 files
198 198 updating to branch default
199 199 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 200 $ cd ../sameparent
201 201 $ hg transplant --log -s ../prune 5
202 202 searching for changes
203 203 applying e234d668f844
204 204 e234d668f844 transplanted to e07aea8ecf9c
205 205 $ hg log --template '{rev} {parents} {desc}\n'
206 206 2 b1
207 207 (transplanted from e234d668f844e1b1a765f01db83a32c0c7bfa170)
208 208 1 r2
209 209 0 r1
210 210 remote transplant
211 211
212 212 $ hg clone -r 1 ../t ../remote
213 213 adding changesets
214 214 adding manifests
215 215 adding file changes
216 216 added 2 changesets with 2 changes to 2 files
217 217 updating to branch default
218 218 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 219 $ cd ../remote
220 220 $ hg transplant --log -s ../t 2 4
221 221 searching for changes
222 222 applying 37a1297eb21b
223 223 37a1297eb21b transplanted to c19cf0ccb069
224 224 applying a53251cdf717
225 225 a53251cdf717 transplanted to f7fe5bf98525
226 226 $ hg log --template '{rev} {parents} {desc}\n'
227 227 3 b3
228 228 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
229 229 2 b1
230 230 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
231 231 1 r2
232 232 0 r1
233 233
234 234 skip previous transplants
235 235
236 236 $ hg transplant -s ../t -a -b 4
237 237 searching for changes
238 238 applying 722f4667af76
239 239 722f4667af76 transplanted to 47156cd86c0b
240 240 $ hg log --template '{rev} {parents} {desc}\n'
241 241 4 b2
242 242 3 b3
243 243 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
244 244 2 b1
245 245 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
246 246 1 r2
247 247 0 r1
248 248
249 249 skip local changes transplanted to the source
250 250
251 251 $ echo b4 > b4
252 252 $ hg ci -Amb4 -d '3 0'
253 253 adding b4
254 254 $ hg clone ../t ../pullback
255 255 updating to branch default
256 256 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 $ cd ../pullback
258 258 $ hg transplant -s ../remote -a -b tip
259 259 searching for changes
260 260 applying 4333daefcb15
261 261 4333daefcb15 transplanted to 5f42c04e07cc
262 262
263 263
264 264 remote transplant with pull
265 265
266 266 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
267 267 $ cat ../t.pid >> $DAEMON_PIDS
268 268
269 269 $ hg clone -r 0 ../t ../rp
270 270 adding changesets
271 271 adding manifests
272 272 adding file changes
273 273 added 1 changesets with 1 changes to 1 files
274 274 updating to branch default
275 275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 276 $ cd ../rp
277 277 $ hg transplant -s http://localhost:$HGPORT/ 2 4
278 278 searching for changes
279 279 searching for changes
280 280 adding changesets
281 281 adding manifests
282 282 adding file changes
283 283 added 1 changesets with 1 changes to 1 files
284 284 applying a53251cdf717
285 285 a53251cdf717 transplanted to 8d9279348abb
286 286 $ hg log --template '{rev} {parents} {desc}\n'
287 287 2 b3
288 288 1 b1
289 289 0 r1
290 290
291 291 transplant --continue
292 292
293 293 $ hg init ../tc
294 294 $ cd ../tc
295 295 $ cat <<EOF > foo
296 296 > foo
297 297 > bar
298 298 > baz
299 299 > EOF
300 300 $ echo toremove > toremove
301 301 $ echo baz > baz
302 302 $ hg ci -Amfoo
303 303 adding baz
304 304 adding foo
305 305 adding toremove
306 306 $ cat <<EOF > foo
307 307 > foo2
308 308 > bar2
309 309 > baz2
310 310 > EOF
311 311 $ rm toremove
312 312 $ echo added > added
313 313 $ hg ci -Amfoo2
314 314 adding added
315 315 removing toremove
316 316 $ echo bar > bar
317 317 $ cat > baz <<EOF
318 318 > before baz
319 319 > baz
320 320 > after baz
321 321 > EOF
322 322 $ hg ci -Ambar
323 323 adding bar
324 324 $ echo bar2 >> bar
325 325 $ hg ci -mbar2
326 326 $ hg up 0
327 327 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
328 328 $ echo foobar > foo
329 329 $ hg ci -mfoobar
330 330 created new head
331 331 $ hg transplant 1:3
332 332 applying 46ae92138f3c
333 333 patching file foo
334 334 Hunk #1 FAILED at 0
335 335 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
336 336 patch failed to apply
337 337 abort: fix up the merge and run hg transplant --continue
338 338 [255]
339 339
340 340 transplant -c shouldn't use an old changeset
341 341
342 342 $ hg up -C
343 343 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 344 $ rm added
345 345 $ hg transplant 1
346 346 applying 46ae92138f3c
347 347 patching file foo
348 348 Hunk #1 FAILED at 0
349 349 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
350 350 patch failed to apply
351 351 abort: fix up the merge and run hg transplant --continue
352 352 [255]
353 353 $ hg transplant --continue
354 354 46ae92138f3c transplanted as 9159dada197d
355 355 $ hg transplant 1:3
356 356 skipping already applied revision 1:46ae92138f3c
357 357 applying 9d6d6b5a8275
358 358 9d6d6b5a8275 transplanted to 2d17a10c922f
359 359 applying 1dab759070cf
360 360 1dab759070cf transplanted to e06a69927eb0
361 361 $ hg locate
362 362 added
363 363 bar
364 364 baz
365 365 foo
366 366
367 367 test multiple revisions and --continue
368 368
369 369 $ hg up -qC 0
370 370 $ echo bazbaz > baz
371 371 $ hg ci -Am anotherbaz baz
372 372 created new head
373 373 $ hg transplant 1:3
374 374 applying 46ae92138f3c
375 375 46ae92138f3c transplanted to 1024233ea0ba
376 376 applying 9d6d6b5a8275
377 377 patching file baz
378 378 Hunk #1 FAILED at 0
379 379 1 out of 1 hunks FAILED -- saving rejects to file baz.rej
380 380 patch failed to apply
381 381 abort: fix up the merge and run hg transplant --continue
382 382 [255]
383 383 $ echo fixed > baz
384 384 $ hg transplant --continue
385 385 9d6d6b5a8275 transplanted as d80c49962290
386 386 applying 1dab759070cf
387 387 1dab759070cf transplanted to aa0ffe6bd5ae
388 388
389 389 $ cd ..
390 390
391 391 Issue1111: Test transplant --merge
392 392
393 393 $ hg init t1111
394 394 $ cd t1111
395 395 $ echo a > a
396 396 $ hg ci -Am adda
397 397 adding a
398 398 $ echo b >> a
399 399 $ hg ci -m appendb
400 400 $ echo c >> a
401 401 $ hg ci -m appendc
402 402 $ hg up -C 0
403 403 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
404 404 $ echo d >> a
405 405 $ hg ci -m appendd
406 406 created new head
407 407
408 408 tranplant
409 409
410 410 $ hg transplant -m 1
411 411 applying 42dc4432fd35
412 412 1:42dc4432fd35 merged at a9f4acbac129
413 413 $ cd ..
414 414
415 415 test transplant into empty repository
416 416
417 417 $ hg init empty
418 418 $ cd empty
419 419 $ hg transplant -s ../t -b tip -a
420 420 adding changesets
421 421 adding manifests
422 422 adding file changes
423 423 added 4 changesets with 4 changes to 4 files
424 424 $ cd ..
425 425
426 426
427 427 #if unix-permissions system-sh
428 428
429 429 test filter
430 430
431 431 $ hg init filter
432 432 $ cd filter
433 433 $ cat <<'EOF' >test-filter
434 434 > #!/bin/sh
435 435 > sed 's/r1/r2/' $1 > $1.new
436 436 > mv $1.new $1
437 437 > EOF
438 438 $ chmod +x test-filter
439 439 $ hg transplant -s ../t -b tip -a --filter ./test-filter
440 440 filtering * (glob)
441 441 applying 17ab29e464c6
442 442 17ab29e464c6 transplanted to e9ffc54ea104
443 443 filtering * (glob)
444 444 applying 37a1297eb21b
445 445 37a1297eb21b transplanted to 348b36d0b6a5
446 446 filtering * (glob)
447 447 applying 722f4667af76
448 448 722f4667af76 transplanted to 0aa6979afb95
449 449 filtering * (glob)
450 450 applying a53251cdf717
451 451 a53251cdf717 transplanted to 14f8512272b5
452 452 $ hg log --template '{rev} {parents} {desc}\n'
453 453 3 b3
454 454 2 b2
455 455 1 b1
456 456 0 r2
457 457 $ cd ..
458 458
459 459
460 460 test filter with failed patch
461 461
462 462 $ cd filter
463 463 $ hg up 0
464 464 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
465 465 $ echo foo > b1
466 466 $ hg ci -Am foo
467 467 adding b1
468 468 adding test-filter
469 469 created new head
470 470 $ hg transplant 1 --filter ./test-filter
471 471 filtering * (glob)
472 472 applying 348b36d0b6a5
473 473 file b1 already exists
474 474 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
475 475 patch failed to apply
476 476 abort: fix up the merge and run hg transplant --continue
477 477 [255]
478 478 $ cd ..
479 479
480 480 test environment passed to filter
481 481
482 482 $ hg init filter-environment
483 483 $ cd filter-environment
484 484 $ cat <<'EOF' >test-filter-environment
485 485 > #!/bin/sh
486 486 > echo "Transplant by $HGUSER" >> $1
487 487 > echo "Transplant from rev $HGREVISION" >> $1
488 488 > EOF
489 489 $ chmod +x test-filter-environment
490 490 $ hg transplant -s ../t --filter ./test-filter-environment 0
491 491 filtering * (glob)
492 492 applying 17ab29e464c6
493 493 17ab29e464c6 transplanted to 5190e68026a0
494 494
495 495 $ hg log --template '{rev} {parents} {desc}\n'
496 496 0 r1
497 497 Transplant by test
498 498 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
499 499 $ cd ..
500 500
501 501 test transplant with filter handles invalid changelog
502 502
503 503 $ hg init filter-invalid-log
504 504 $ cd filter-invalid-log
505 505 $ cat <<'EOF' >test-filter-invalid-log
506 506 > #!/bin/sh
507 507 > echo "" > $1
508 508 > EOF
509 509 $ chmod +x test-filter-invalid-log
510 510 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
511 511 filtering * (glob)
512 512 abort: filter corrupted changeset (no user or date)
513 513 [255]
514 514 $ cd ..
515 515
516 516 #endif
517 517
518 518
519 519 test with a win32ext like setup (differing EOLs)
520 520
521 521 $ hg init twin1
522 522 $ cd twin1
523 523 $ echo a > a
524 524 $ echo b > b
525 525 $ echo b >> b
526 526 $ hg ci -Am t
527 527 adding a
528 528 adding b
529 529 $ echo a > b
530 530 $ echo b >> b
531 531 $ hg ci -m changeb
532 532 $ cd ..
533 533
534 534 $ hg init twin2
535 535 $ cd twin2
536 536 $ echo '[patch]' >> .hg/hgrc
537 537 $ echo 'eol = crlf' >> .hg/hgrc
538 538 $ python -c "file('b', 'wb').write('b\r\nb\r\n')"
539 539 $ hg ci -Am addb
540 540 adding b
541 541 $ hg transplant -s ../twin1 tip
542 542 searching for changes
543 543 warning: repository is unrelated
544 544 applying 2e849d776c17
545 545 2e849d776c17 transplanted to 8e65bebc063e
546 546 $ cat b
547 547 a\r (esc)
548 548 b\r (esc)
549 549 $ cd ..
550 550
551 551 test transplant with merge changeset is skipped
552 552
553 553 $ hg init merge1a
554 554 $ cd merge1a
555 555 $ echo a > a
556 556 $ hg ci -Am a
557 557 adding a
558 558 $ hg branch b
559 559 marked working directory as branch b
560 560 (branches are permanent and global, did you want a bookmark?)
561 561 $ hg ci -m branchb
562 562 $ echo b > b
563 563 $ hg ci -Am b
564 564 adding b
565 565 $ hg update default
566 566 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
567 567 $ hg merge b
568 568 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
569 569 (branch merge, don't forget to commit)
570 570 $ hg ci -m mergeb
571 571 $ cd ..
572 572
573 573 $ hg init merge1b
574 574 $ cd merge1b
575 575 $ hg transplant -s ../merge1a tip
576 576 $ cd ..
577 577
578 578 test transplant with merge changeset accepts --parent
579 579
580 580 $ hg init merge2a
581 581 $ cd merge2a
582 582 $ echo a > a
583 583 $ hg ci -Am a
584 584 adding a
585 585 $ hg branch b
586 586 marked working directory as branch b
587 587 (branches are permanent and global, did you want a bookmark?)
588 588 $ hg ci -m branchb
589 589 $ echo b > b
590 590 $ hg ci -Am b
591 591 adding b
592 592 $ hg update default
593 593 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
594 594 $ hg merge b
595 595 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
596 596 (branch merge, don't forget to commit)
597 597 $ hg ci -m mergeb
598 598 $ cd ..
599 599
600 600 $ hg init merge2b
601 601 $ cd merge2b
602 602 $ hg transplant -s ../merge2a --parent 0 tip
603 603 applying be9f9b39483f
604 604 be9f9b39483f transplanted to 9959e51f94d1
605 605 $ cd ..
606 606
607 607 test transplanting a patch turning into a no-op
608 608
609 609 $ hg init binarysource
610 610 $ cd binarysource
611 611 $ echo a > a
612 612 $ hg ci -Am adda a
613 613 >>> file('b', 'wb').write('\0b1')
614 614 $ hg ci -Am addb b
615 615 >>> file('b', 'wb').write('\0b2')
616 616 $ hg ci -m changeb b
617 617 $ cd ..
618 618
619 619 $ hg clone -r0 binarysource binarydest
620 620 adding changesets
621 621 adding manifests
622 622 adding file changes
623 623 added 1 changesets with 1 changes to 1 files
624 624 updating to branch default
625 625 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
626 626 $ cd binarydest
627 627 $ cp ../binarysource/b b
628 628 $ hg ci -Am addb2 b
629 629 $ hg transplant -s ../binarysource 2
630 630 searching for changes
631 631 applying 7a7d57e15850
632 7a7d57e158501e51588f5fd3288b491cac77e0d8: empty changeset (no-eol)
632 skipping emptied changeset 7a7d57e15850
633 633 $ cd ..
634 634
General Comments 0
You need to be logged in to leave comments. Login now