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