##// END OF EJS Templates
transplant: pass source through to recover
Bryan O'Sullivan -
r18926:8deaa703 default
parent child Browse files
Show More
@@ -1,678 +1,678
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.ancestors([parentrev], rev,
98 98 inclusive=True)
99 99 if rev in reachable:
100 100 return True
101 101 for t in self.transplants.get(node):
102 102 # it might have been stripped
103 103 if not hasnode(repo, t.lnode):
104 104 self.transplants.remove(t)
105 105 return False
106 106 lnoderev = repo.changelog.rev(t.lnode)
107 107 if lnoderev in repo.changelog.ancestors([parentrev], lnoderev,
108 108 inclusive=True):
109 109 return True
110 110 return False
111 111
112 112 def apply(self, repo, source, revmap, merges, opts={}):
113 113 '''apply the revisions in revmap one by one in revision order'''
114 114 revs = sorted(revmap)
115 115 p1, p2 = repo.dirstate.parents()
116 116 pulls = []
117 117 diffopts = patch.diffopts(self.ui, opts)
118 118 diffopts.git = True
119 119
120 120 lock = wlock = tr = None
121 121 try:
122 122 wlock = repo.wlock()
123 123 lock = repo.lock()
124 124 tr = repo.transaction('transplant')
125 125 for rev in revs:
126 126 node = revmap[rev]
127 127 revstr = '%s:%s' % (rev, short(node))
128 128
129 129 if self.applied(repo, node, p1):
130 130 self.ui.warn(_('skipping already applied revision %s\n') %
131 131 revstr)
132 132 continue
133 133
134 134 parents = source.changelog.parents(node)
135 135 if not (opts.get('filter') or opts.get('log')):
136 136 # If the changeset parent is the same as the
137 137 # wdir's parent, just pull it.
138 138 if parents[0] == p1:
139 139 pulls.append(node)
140 140 p1 = node
141 141 continue
142 142 if pulls:
143 143 if source != repo:
144 144 repo.pull(source.peer(), heads=pulls)
145 145 merge.update(repo, pulls[-1], False, False, None)
146 146 p1, p2 = repo.dirstate.parents()
147 147 pulls = []
148 148
149 149 domerge = False
150 150 if node in merges:
151 151 # pulling all the merge revs at once would mean we
152 152 # couldn't transplant after the latest even if
153 153 # transplants before them fail.
154 154 domerge = True
155 155 if not hasnode(repo, node):
156 156 repo.pull(source, heads=[node])
157 157
158 158 skipmerge = False
159 159 if parents[1] != revlog.nullid:
160 160 if not opts.get('parent'):
161 161 self.ui.note(_('skipping merge changeset %s:%s\n')
162 162 % (rev, short(node)))
163 163 skipmerge = True
164 164 else:
165 165 parent = source.lookup(opts['parent'])
166 166 if parent not in parents:
167 167 raise util.Abort(_('%s is not a parent of %s') %
168 168 (short(parent), short(node)))
169 169 else:
170 170 parent = parents[0]
171 171
172 172 if skipmerge:
173 173 patchfile = None
174 174 else:
175 175 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
176 176 fp = os.fdopen(fd, 'w')
177 177 gen = patch.diff(source, parent, node, opts=diffopts)
178 178 for chunk in gen:
179 179 fp.write(chunk)
180 180 fp.close()
181 181
182 182 del revmap[rev]
183 183 if patchfile or domerge:
184 184 try:
185 185 try:
186 186 n = self.applyone(repo, node,
187 187 source.changelog.read(node),
188 188 patchfile, merge=domerge,
189 189 log=opts.get('log'),
190 190 filter=opts.get('filter'))
191 191 except TransplantError:
192 192 # Do not rollback, it is up to the user to
193 193 # fix the merge or cancel everything
194 194 tr.close()
195 195 raise
196 196 if n and domerge:
197 197 self.ui.status(_('%s merged at %s\n') % (revstr,
198 198 short(n)))
199 199 elif n:
200 200 self.ui.status(_('%s transplanted to %s\n')
201 201 % (short(node),
202 202 short(n)))
203 203 finally:
204 204 if patchfile:
205 205 os.unlink(patchfile)
206 206 tr.close()
207 207 if pulls:
208 208 repo.pull(source.peer(), heads=pulls)
209 209 merge.update(repo, pulls[-1], False, False, None)
210 210 finally:
211 211 self.saveseries(revmap, merges)
212 212 self.transplants.write()
213 213 if tr:
214 214 tr.release()
215 215 lock.release()
216 216 wlock.release()
217 217
218 218 def filter(self, filter, node, changelog, patchfile):
219 219 '''arbitrarily rewrite changeset before applying it'''
220 220
221 221 self.ui.status(_('filtering %s\n') % patchfile)
222 222 user, date, msg = (changelog[1], changelog[2], changelog[4])
223 223 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
224 224 fp = os.fdopen(fd, 'w')
225 225 fp.write("# HG changeset patch\n")
226 226 fp.write("# User %s\n" % user)
227 227 fp.write("# Date %d %d\n" % date)
228 228 fp.write(msg + '\n')
229 229 fp.close()
230 230
231 231 try:
232 232 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
233 233 util.shellquote(patchfile)),
234 234 environ={'HGUSER': changelog[1],
235 235 'HGREVISION': revlog.hex(node),
236 236 },
237 237 onerr=util.Abort, errprefix=_('filter failed'),
238 238 out=self.ui.fout)
239 239 user, date, msg = self.parselog(file(headerfile))[1:4]
240 240 finally:
241 241 os.unlink(headerfile)
242 242
243 243 return (user, date, msg)
244 244
245 245 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
246 246 filter=None):
247 247 '''apply the patch in patchfile to the repository as a transplant'''
248 248 (manifest, user, (time, timezone), files, message) = cl[:5]
249 249 date = "%d %d" % (time, timezone)
250 250 extra = {'transplant_source': node}
251 251 if filter:
252 252 (user, date, message) = self.filter(filter, node, cl, patchfile)
253 253
254 254 if log:
255 255 # we don't translate messages inserted into commits
256 256 message += '\n(transplanted from %s)' % revlog.hex(node)
257 257
258 258 self.ui.status(_('applying %s\n') % short(node))
259 259 self.ui.note('%s %s\n%s\n' % (user, date, message))
260 260
261 261 if not patchfile and not merge:
262 262 raise util.Abort(_('can only omit patchfile if merging'))
263 263 if patchfile:
264 264 try:
265 265 files = set()
266 266 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
267 267 files = list(files)
268 268 except Exception, inst:
269 269 seriespath = os.path.join(self.path, 'series')
270 270 if os.path.exists(seriespath):
271 271 os.unlink(seriespath)
272 272 p1 = repo.dirstate.p1()
273 273 p2 = node
274 274 self.log(user, date, message, p1, p2, merge=merge)
275 275 self.ui.write(str(inst) + '\n')
276 276 raise TransplantError(_('fix up the merge and run '
277 277 'hg transplant --continue'))
278 278 else:
279 279 files = None
280 280 if merge:
281 281 p1, p2 = repo.dirstate.parents()
282 282 repo.setparents(p1, node)
283 283 m = match.always(repo.root, '')
284 284 else:
285 285 m = match.exact(repo.root, '', files)
286 286
287 287 n = repo.commit(message, user, date, extra=extra, match=m,
288 288 editor=self.editor)
289 289 if not n:
290 290 self.ui.warn(_('skipping emptied changeset %s\n') % short(node))
291 291 return None
292 292 if not merge:
293 293 self.transplants.set(n, node)
294 294
295 295 return n
296 296
297 297 def resume(self, repo, source, opts):
298 298 '''recover last transaction and apply remaining changesets'''
299 299 if os.path.exists(os.path.join(self.path, 'journal')):
300 n, node = self.recover(repo, opts)
300 n, node = self.recover(repo, source, opts)
301 301 self.ui.status(_('%s transplanted as %s\n') % (short(node),
302 302 short(n)))
303 303 seriespath = os.path.join(self.path, 'series')
304 304 if not os.path.exists(seriespath):
305 305 self.transplants.write()
306 306 return
307 307 nodes, merges = self.readseries()
308 308 revmap = {}
309 309 for n in nodes:
310 310 revmap[source.changelog.rev(n)] = n
311 311 os.unlink(seriespath)
312 312
313 313 self.apply(repo, source, revmap, merges, opts)
314 314
315 def recover(self, repo, opts):
315 def recover(self, repo, source, opts):
316 316 '''commit working directory using journal metadata'''
317 317 node, user, date, message, parents = self.readlog()
318 318 merge = False
319 319
320 320 if not user or not date or not message or not parents[0]:
321 321 raise util.Abort(_('transplant log file is corrupt'))
322 322
323 323 parent = parents[0]
324 324 if len(parents) > 1:
325 325 if opts.get('parent'):
326 326 parent = source.lookup(opts['parent'])
327 327 if parent not in parents:
328 328 raise util.Abort(_('%s is not a parent of %s') %
329 329 (short(parent), short(node)))
330 330 else:
331 331 merge = True
332 332
333 333 extra = {'transplant_source': node}
334 334 wlock = repo.wlock()
335 335 try:
336 336 p1, p2 = repo.dirstate.parents()
337 337 if p1 != parent:
338 338 raise util.Abort(
339 339 _('working dir not at transplant parent %s') %
340 340 revlog.hex(parent))
341 341 if merge:
342 342 repo.setparents(p1, parents[1])
343 343 n = repo.commit(message, user, date, extra=extra,
344 344 editor=self.editor)
345 345 if not n:
346 346 raise util.Abort(_('commit failed'))
347 347 if not merge:
348 348 self.transplants.set(n, node)
349 349 self.unlog()
350 350
351 351 return n, node
352 352 finally:
353 353 wlock.release()
354 354
355 355 def readseries(self):
356 356 nodes = []
357 357 merges = []
358 358 cur = nodes
359 359 for line in self.opener.read('series').splitlines():
360 360 if line.startswith('# Merges'):
361 361 cur = merges
362 362 continue
363 363 cur.append(revlog.bin(line))
364 364
365 365 return (nodes, merges)
366 366
367 367 def saveseries(self, revmap, merges):
368 368 if not revmap:
369 369 return
370 370
371 371 if not os.path.isdir(self.path):
372 372 os.mkdir(self.path)
373 373 series = self.opener('series', 'w')
374 374 for rev in sorted(revmap):
375 375 series.write(revlog.hex(revmap[rev]) + '\n')
376 376 if merges:
377 377 series.write('# Merges\n')
378 378 for m in merges:
379 379 series.write(revlog.hex(m) + '\n')
380 380 series.close()
381 381
382 382 def parselog(self, fp):
383 383 parents = []
384 384 message = []
385 385 node = revlog.nullid
386 386 inmsg = False
387 387 user = None
388 388 date = None
389 389 for line in fp.read().splitlines():
390 390 if inmsg:
391 391 message.append(line)
392 392 elif line.startswith('# User '):
393 393 user = line[7:]
394 394 elif line.startswith('# Date '):
395 395 date = line[7:]
396 396 elif line.startswith('# Node ID '):
397 397 node = revlog.bin(line[10:])
398 398 elif line.startswith('# Parent '):
399 399 parents.append(revlog.bin(line[9:]))
400 400 elif not line.startswith('# '):
401 401 inmsg = True
402 402 message.append(line)
403 403 if None in (user, date):
404 404 raise util.Abort(_("filter corrupted changeset (no user or date)"))
405 405 return (node, user, date, '\n'.join(message), parents)
406 406
407 407 def log(self, user, date, message, p1, p2, merge=False):
408 408 '''journal changelog metadata for later recover'''
409 409
410 410 if not os.path.isdir(self.path):
411 411 os.mkdir(self.path)
412 412 fp = self.opener('journal', 'w')
413 413 fp.write('# User %s\n' % user)
414 414 fp.write('# Date %s\n' % date)
415 415 fp.write('# Node ID %s\n' % revlog.hex(p2))
416 416 fp.write('# Parent ' + revlog.hex(p1) + '\n')
417 417 if merge:
418 418 fp.write('# Parent ' + revlog.hex(p2) + '\n')
419 419 fp.write(message.rstrip() + '\n')
420 420 fp.close()
421 421
422 422 def readlog(self):
423 423 return self.parselog(self.opener('journal'))
424 424
425 425 def unlog(self):
426 426 '''remove changelog journal'''
427 427 absdst = os.path.join(self.path, 'journal')
428 428 if os.path.exists(absdst):
429 429 os.unlink(absdst)
430 430
431 431 def transplantfilter(self, repo, source, root):
432 432 def matchfn(node):
433 433 if self.applied(repo, node, root):
434 434 return False
435 435 if source.changelog.parents(node)[1] != revlog.nullid:
436 436 return False
437 437 extra = source.changelog.read(node)[5]
438 438 cnode = extra.get('transplant_source')
439 439 if cnode and self.applied(repo, cnode, root):
440 440 return False
441 441 return True
442 442
443 443 return matchfn
444 444
445 445 def hasnode(repo, node):
446 446 try:
447 447 return repo.changelog.rev(node) is not None
448 448 except error.RevlogError:
449 449 return False
450 450
451 451 def browserevs(ui, repo, nodes, opts):
452 452 '''interactively transplant changesets'''
453 453 def browsehelp(ui):
454 454 ui.write(_('y: transplant this changeset\n'
455 455 'n: skip this changeset\n'
456 456 'm: merge at this changeset\n'
457 457 'p: show patch\n'
458 458 'c: commit selected changesets\n'
459 459 'q: cancel transplant\n'
460 460 '?: show this help\n'))
461 461
462 462 displayer = cmdutil.show_changeset(ui, repo, opts)
463 463 transplants = []
464 464 merges = []
465 465 for node in nodes:
466 466 displayer.show(repo[node])
467 467 action = None
468 468 while not action:
469 469 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
470 470 if action == '?':
471 471 browsehelp(ui)
472 472 action = None
473 473 elif action == 'p':
474 474 parent = repo.changelog.parents(node)[0]
475 475 for chunk in patch.diff(repo, parent, node):
476 476 ui.write(chunk)
477 477 action = None
478 478 elif action not in ('y', 'n', 'm', 'c', 'q'):
479 479 ui.write(_('no such option\n'))
480 480 action = None
481 481 if action == 'y':
482 482 transplants.append(node)
483 483 elif action == 'm':
484 484 merges.append(node)
485 485 elif action == 'c':
486 486 break
487 487 elif action == 'q':
488 488 transplants = ()
489 489 merges = ()
490 490 break
491 491 displayer.close()
492 492 return (transplants, merges)
493 493
494 494 @command('transplant',
495 495 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
496 496 ('b', 'branch', [],
497 497 _('pull patches from branch BRANCH'), _('BRANCH')),
498 498 ('a', 'all', None, _('pull all changesets up to BRANCH')),
499 499 ('p', 'prune', [], _('skip over REV'), _('REV')),
500 500 ('m', 'merge', [], _('merge at REV'), _('REV')),
501 501 ('', 'parent', '',
502 502 _('parent to choose when transplanting merge'), _('REV')),
503 503 ('e', 'edit', False, _('invoke editor on commit messages')),
504 504 ('', 'log', None, _('append transplant info to log message')),
505 505 ('c', 'continue', None, _('continue last transplant session '
506 506 'after repair')),
507 507 ('', 'filter', '',
508 508 _('filter changesets through command'), _('CMD'))],
509 509 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
510 510 '[-m REV] [REV]...'))
511 511 def transplant(ui, repo, *revs, **opts):
512 512 '''transplant changesets from another branch
513 513
514 514 Selected changesets will be applied on top of the current working
515 515 directory with the log of the original changeset. The changesets
516 516 are copied and will thus appear twice in the history. Use the
517 517 rebase extension instead if you want to move a whole branch of
518 518 unpublished changesets.
519 519
520 520 If --log is specified, log messages will have a comment appended
521 521 of the form::
522 522
523 523 (transplanted from CHANGESETHASH)
524 524
525 525 You can rewrite the changelog message with the --filter option.
526 526 Its argument will be invoked with the current changelog message as
527 527 $1 and the patch as $2.
528 528
529 529 If --source/-s is specified, selects changesets from the named
530 530 repository. If --branch/-b is specified, selects changesets from
531 531 the branch holding the named revision, up to that revision. If
532 532 --all/-a is specified, all changesets on the branch will be
533 533 transplanted, otherwise you will be prompted to select the
534 534 changesets you want.
535 535
536 536 :hg:`transplant --branch REV --all` will transplant the
537 537 selected branch (up to the named revision) onto your current
538 538 working directory.
539 539
540 540 You can optionally mark selected transplanted changesets as merge
541 541 changesets. You will not be prompted to transplant any ancestors
542 542 of a merged transplant, and you can merge descendants of them
543 543 normally instead of transplanting them.
544 544
545 545 Merge changesets may be transplanted directly by specifying the
546 546 proper parent changeset by calling :hg:`transplant --parent`.
547 547
548 548 If no merges or revisions are provided, :hg:`transplant` will
549 549 start an interactive changeset browser.
550 550
551 551 If a changeset application fails, you can fix the merge by hand
552 552 and then resume where you left off by calling :hg:`transplant
553 553 --continue/-c`.
554 554 '''
555 555 def incwalk(repo, csets, match=util.always):
556 556 for node in csets:
557 557 if match(node):
558 558 yield node
559 559
560 560 def transplantwalk(repo, root, branches, match=util.always):
561 561 if not branches:
562 562 branches = repo.heads()
563 563 ancestors = []
564 564 for branch in branches:
565 565 ancestors.append(repo.changelog.ancestor(root, branch))
566 566 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
567 567 if match(node):
568 568 yield node
569 569
570 570 def checkopts(opts, revs):
571 571 if opts.get('continue'):
572 572 if opts.get('branch') or opts.get('all') or opts.get('merge'):
573 573 raise util.Abort(_('--continue is incompatible with '
574 574 'branch, all or merge'))
575 575 return
576 576 if not (opts.get('source') or revs or
577 577 opts.get('merge') or opts.get('branch')):
578 578 raise util.Abort(_('no source URL, branch tag or revision '
579 579 'list provided'))
580 580 if opts.get('all'):
581 581 if not opts.get('branch'):
582 582 raise util.Abort(_('--all requires a branch revision'))
583 583 if revs:
584 584 raise util.Abort(_('--all is incompatible with a '
585 585 'revision list'))
586 586
587 587 checkopts(opts, revs)
588 588
589 589 if not opts.get('log'):
590 590 opts['log'] = ui.config('transplant', 'log')
591 591 if not opts.get('filter'):
592 592 opts['filter'] = ui.config('transplant', 'filter')
593 593
594 594 tp = transplanter(ui, repo)
595 595 if opts.get('edit'):
596 596 tp.editor = cmdutil.commitforceeditor
597 597
598 598 p1, p2 = repo.dirstate.parents()
599 599 if len(repo) > 0 and p1 == revlog.nullid:
600 600 raise util.Abort(_('no revision checked out'))
601 601 if not opts.get('continue'):
602 602 if p2 != revlog.nullid:
603 603 raise util.Abort(_('outstanding uncommitted merges'))
604 604 m, a, r, d = repo.status()[:4]
605 605 if m or a or r or d:
606 606 raise util.Abort(_('outstanding local changes'))
607 607
608 608 sourcerepo = opts.get('source')
609 609 if sourcerepo:
610 610 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
611 611 branches = map(peer.lookup, opts.get('branch', ()))
612 612 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
613 613 onlyheads=branches, force=True)
614 614 else:
615 615 source = repo
616 616 branches = map(source.lookup, opts.get('branch', ()))
617 617 cleanupfn = None
618 618
619 619 try:
620 620 if opts.get('continue'):
621 621 tp.resume(repo, source, opts)
622 622 return
623 623
624 624 tf = tp.transplantfilter(repo, source, p1)
625 625 if opts.get('prune'):
626 626 prune = [source.lookup(r)
627 627 for r in scmutil.revrange(source, opts.get('prune'))]
628 628 matchfn = lambda x: tf(x) and x not in prune
629 629 else:
630 630 matchfn = tf
631 631 merges = map(source.lookup, opts.get('merge', ()))
632 632 revmap = {}
633 633 if revs:
634 634 for r in scmutil.revrange(source, revs):
635 635 revmap[int(r)] = source.lookup(r)
636 636 elif opts.get('all') or not merges:
637 637 if source != repo:
638 638 alltransplants = incwalk(source, csets, match=matchfn)
639 639 else:
640 640 alltransplants = transplantwalk(source, p1, branches,
641 641 match=matchfn)
642 642 if opts.get('all'):
643 643 revs = alltransplants
644 644 else:
645 645 revs, newmerges = browserevs(ui, source, alltransplants, opts)
646 646 merges.extend(newmerges)
647 647 for r in revs:
648 648 revmap[source.changelog.rev(r)] = r
649 649 for r in merges:
650 650 revmap[source.changelog.rev(r)] = r
651 651
652 652 tp.apply(repo, source, revmap, merges, opts)
653 653 finally:
654 654 if cleanupfn:
655 655 cleanupfn()
656 656
657 657 def revsettransplanted(repo, subset, x):
658 658 """``transplanted([set])``
659 659 Transplanted changesets in set, or all transplanted changesets.
660 660 """
661 661 if x:
662 662 s = revset.getset(repo, subset, x)
663 663 else:
664 664 s = subset
665 665 return [r for r in s if repo[r].extra().get('transplant_source')]
666 666
667 667 def kwtransplanted(repo, ctx, **args):
668 668 """:transplanted: String. The node identifier of the transplanted
669 669 changeset if any."""
670 670 n = ctx.extra().get('transplant_source')
671 671 return n and revlog.hex(n) or ''
672 672
673 673 def extsetup(ui):
674 674 revset.symbols['transplanted'] = revsettransplanted
675 675 templatekw.keywords['transplanted'] = kwtransplanted
676 676
677 677 # tell hggettext to extract docstrings from these functions:
678 678 i18nfunctions = [revsettransplanted, kwtransplanted]
General Comments 0
You need to be logged in to leave comments. Login now