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