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