##// END OF EJS Templates
transplant: add checkunfinished (issue3955)...
Matt Mackall -
r19480:7c0bb2b7 stable
parent child Browse files
Show More
@@ -1,688 +1,692
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, 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 def browsehelp(ui):
455 455 ui.write(_('y: transplant this changeset\n'
456 456 'n: skip this changeset\n'
457 457 'm: merge at this changeset\n'
458 458 'p: show patch\n'
459 459 'c: commit selected changesets\n'
460 460 'q: cancel transplant\n'
461 461 '?: show this help\n'))
462 462
463 463 displayer = cmdutil.show_changeset(ui, repo, opts)
464 464 transplants = []
465 465 merges = []
466 466 for node in nodes:
467 467 displayer.show(repo[node])
468 468 action = None
469 469 while not action:
470 470 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
471 471 if action == '?':
472 472 browsehelp(ui)
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 elif action not in ('y', 'n', 'm', 'c', 'q'):
480 480 ui.write(_('no such option\n'))
481 481 action = None
482 482 if action == 'y':
483 483 transplants.append(node)
484 484 elif action == 'm':
485 485 merges.append(node)
486 486 elif action == 'c':
487 487 break
488 488 elif action == 'q':
489 489 transplants = ()
490 490 merges = ()
491 491 break
492 492 displayer.close()
493 493 return (transplants, merges)
494 494
495 495 @command('transplant',
496 496 [('s', 'source', '', _('transplant changesets from REPO'), _('REPO')),
497 497 ('b', 'branch', [], _('use this source changeset as head'), _('REV')),
498 498 ('a', 'all', None, _('pull all changesets up to the --branch revisions')),
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 fixing conflicts')),
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 with different
517 517 identities.
518 518
519 519 Consider using the graft command if everything is inside the same
520 520 repository - it will use merges and will usually give a better result.
521 521 Use the rebase extension if the changesets are unpublished and you want
522 522 to move them instead of copying them.
523 523
524 524 If --log is specified, log messages will have a comment appended
525 525 of the form::
526 526
527 527 (transplanted from CHANGESETHASH)
528 528
529 529 You can rewrite the changelog message with the --filter option.
530 530 Its argument will be invoked with the current changelog message as
531 531 $1 and the patch as $2.
532 532
533 533 --source/-s specifies another repository to use for selecting changesets,
534 534 just as if it temporarily had been pulled.
535 535 If --branch/-b is specified, these revisions will be used as
536 536 heads when deciding which changsets to transplant, just as if only
537 537 these revisions had been pulled.
538 538 If --all/-a is specified, all the revisions up to the heads specified
539 539 with --branch will be transplanted.
540 540
541 541 Example:
542 542
543 543 - transplant all changes up to REV on top of your current revision::
544 544
545 545 hg transplant --branch REV --all
546 546
547 547 You can optionally mark selected transplanted changesets as merge
548 548 changesets. You will not be prompted to transplant any ancestors
549 549 of a merged transplant, and you can merge descendants of them
550 550 normally instead of transplanting them.
551 551
552 552 Merge changesets may be transplanted directly by specifying the
553 553 proper parent changeset by calling :hg:`transplant --parent`.
554 554
555 555 If no merges or revisions are provided, :hg:`transplant` will
556 556 start an interactive changeset browser.
557 557
558 558 If a changeset application fails, you can fix the merge by hand
559 559 and then resume where you left off by calling :hg:`transplant
560 560 --continue/-c`.
561 561 '''
562 562 def incwalk(repo, csets, match=util.always):
563 563 for node in csets:
564 564 if match(node):
565 565 yield node
566 566
567 567 def transplantwalk(repo, dest, heads, match=util.always):
568 568 '''Yield all nodes that are ancestors of a head but not ancestors
569 569 of dest.
570 570 If no heads are specified, the heads of repo will be used.'''
571 571 if not heads:
572 572 heads = repo.heads()
573 573 ancestors = []
574 574 for head in heads:
575 575 ancestors.append(repo.changelog.ancestor(dest, head))
576 576 for node in repo.changelog.nodesbetween(ancestors, heads)[0]:
577 577 if match(node):
578 578 yield node
579 579
580 580 def checkopts(opts, revs):
581 581 if opts.get('continue'):
582 582 if opts.get('branch') or opts.get('all') or opts.get('merge'):
583 583 raise util.Abort(_('--continue is incompatible with '
584 584 '--branch, --all and --merge'))
585 585 return
586 586 if not (opts.get('source') or revs or
587 587 opts.get('merge') or opts.get('branch')):
588 588 raise util.Abort(_('no source URL, branch revision or revision '
589 589 'list provided'))
590 590 if opts.get('all'):
591 591 if not opts.get('branch'):
592 592 raise util.Abort(_('--all requires a branch revision'))
593 593 if revs:
594 594 raise util.Abort(_('--all is incompatible with a '
595 595 'revision list'))
596 596
597 597 checkopts(opts, revs)
598 598
599 599 if not opts.get('log'):
600 600 opts['log'] = ui.config('transplant', 'log')
601 601 if not opts.get('filter'):
602 602 opts['filter'] = ui.config('transplant', 'filter')
603 603
604 604 tp = transplanter(ui, repo)
605 605 if opts.get('edit'):
606 606 tp.editor = cmdutil.commitforceeditor
607 607
608 cmdutil.checkunfinished(repo)
608 609 p1, p2 = repo.dirstate.parents()
609 610 if len(repo) > 0 and p1 == revlog.nullid:
610 611 raise util.Abort(_('no revision checked out'))
611 612 if not opts.get('continue'):
612 613 if p2 != revlog.nullid:
613 614 raise util.Abort(_('outstanding uncommitted merges'))
614 615 m, a, r, d = repo.status()[:4]
615 616 if m or a or r or d:
616 617 raise util.Abort(_('outstanding local changes'))
617 618
618 619 sourcerepo = opts.get('source')
619 620 if sourcerepo:
620 621 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
621 622 heads = map(peer.lookup, opts.get('branch', ()))
622 623 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
623 624 onlyheads=heads, force=True)
624 625 else:
625 626 source = repo
626 627 heads = map(source.lookup, opts.get('branch', ()))
627 628 cleanupfn = None
628 629
629 630 try:
630 631 if opts.get('continue'):
631 632 tp.resume(repo, source, opts)
632 633 return
633 634
634 635 tf = tp.transplantfilter(repo, source, p1)
635 636 if opts.get('prune'):
636 637 prune = set(source.lookup(r)
637 638 for r in scmutil.revrange(source, opts.get('prune')))
638 639 matchfn = lambda x: tf(x) and x not in prune
639 640 else:
640 641 matchfn = tf
641 642 merges = map(source.lookup, opts.get('merge', ()))
642 643 revmap = {}
643 644 if revs:
644 645 for r in scmutil.revrange(source, revs):
645 646 revmap[int(r)] = source.lookup(r)
646 647 elif opts.get('all') or not merges:
647 648 if source != repo:
648 649 alltransplants = incwalk(source, csets, match=matchfn)
649 650 else:
650 651 alltransplants = transplantwalk(source, p1, heads,
651 652 match=matchfn)
652 653 if opts.get('all'):
653 654 revs = alltransplants
654 655 else:
655 656 revs, newmerges = browserevs(ui, source, alltransplants, opts)
656 657 merges.extend(newmerges)
657 658 for r in revs:
658 659 revmap[source.changelog.rev(r)] = r
659 660 for r in merges:
660 661 revmap[source.changelog.rev(r)] = r
661 662
662 663 tp.apply(repo, source, revmap, merges, opts)
663 664 finally:
664 665 if cleanupfn:
665 666 cleanupfn()
666 667
667 668 def revsettransplanted(repo, subset, x):
668 669 """``transplanted([set])``
669 670 Transplanted changesets in set, or all transplanted changesets.
670 671 """
671 672 if x:
672 673 s = revset.getset(repo, subset, x)
673 674 else:
674 675 s = subset
675 676 return [r for r in s if repo[r].extra().get('transplant_source')]
676 677
677 678 def kwtransplanted(repo, ctx, **args):
678 679 """:transplanted: String. The node identifier of the transplanted
679 680 changeset if any."""
680 681 n = ctx.extra().get('transplant_source')
681 682 return n and revlog.hex(n) or ''
682 683
683 684 def extsetup(ui):
684 685 revset.symbols['transplanted'] = revsettransplanted
685 686 templatekw.keywords['transplanted'] = kwtransplanted
687 cmdutil.unfinishedstates.append(
688 ['series', True, _('transplant in progress'),
689 _("use 'hg transplant --continue' or 'hg update' to abort")])
686 690
687 691 # tell hggettext to extract docstrings from these functions:
688 692 i18nfunctions = [revsettransplanted, kwtransplanted]
General Comments 0
You need to be logged in to leave comments. Login now