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