Mercurial > hg > mercurial-crew-with-dirclash
annotate hgext/transplant.py @ 3755:05120e210c65
Use unsigned version format.
This way can use one additional bit, and when encountering invalid revlogs
with the first bit set don't produce python warnings or strange error messages.
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 01 Dec 2006 23:27:53 +0100 |
parents | 752884db5037 |
children | f902f409cd81 |
rev | line source |
---|---|
3714 | 1 # Patch transplanting extension for Mercurial |
2 # | |
3 # Copyright 2006 Brendan Cully <brendan@kublai.com> | |
4 # | |
5 # This software may be used and distributed according to the terms | |
6 # of the GNU General Public License, incorporated herein by reference. | |
7 | |
8 from mercurial.demandload import * | |
9 from mercurial.i18n import gettext as _ | |
10 demandload(globals(), 'os tempfile') | |
11 demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch') | |
12 demandload(globals(), 'mercurial:revlog,util') | |
13 | |
14 '''patch transplanting tool | |
15 | |
16 This extension allows you to transplant patches from another branch. | |
17 | |
18 Transplanted patches are recorded in .hg/transplant/transplants, as a map | |
19 from a changeset hash to its hash in the source repository. | |
20 ''' | |
21 | |
22 class transplantentry: | |
23 def __init__(self, lnode, rnode): | |
24 self.lnode = lnode | |
25 self.rnode = rnode | |
26 | |
27 class transplants: | |
28 def __init__(self, path=None, transplantfile=None, opener=None): | |
29 self.path = path | |
30 self.transplantfile = transplantfile | |
31 self.opener = opener | |
32 | |
33 if not opener: | |
34 self.opener = util.opener(self.path) | |
35 self.transplants = [] | |
36 self.dirty = False | |
37 self.read() | |
38 | |
39 def read(self): | |
40 abspath = os.path.join(self.path, self.transplantfile) | |
41 if self.transplantfile and os.path.exists(abspath): | |
42 for line in self.opener(self.transplantfile).read().splitlines(): | |
43 lnode, rnode = map(revlog.bin, line.split(':')) | |
44 self.transplants.append(transplantentry(lnode, rnode)) | |
45 | |
46 def write(self): | |
47 if self.dirty and self.transplantfile: | |
48 if not os.path.isdir(self.path): | |
49 os.mkdir(self.path) | |
50 fp = self.opener(self.transplantfile, 'w') | |
51 for c in self.transplants: | |
52 l, r = map(revlog.hex, (c.lnode, c.rnode)) | |
53 fp.write(l + ':' + r + '\n') | |
54 fp.close() | |
55 self.dirty = False | |
56 | |
57 def get(self, rnode): | |
58 return [t for t in self.transplants if t.rnode == rnode] | |
59 | |
60 def set(self, lnode, rnode): | |
61 self.transplants.append(transplantentry(lnode, rnode)) | |
62 self.dirty = True | |
63 | |
64 def remove(self, transplant): | |
65 del self.transplants[self.transplants.index(transplant)] | |
66 self.dirty = True | |
67 | |
68 class transplanter: | |
69 def __init__(self, ui, repo): | |
70 self.ui = ui | |
71 self.path = repo.join('transplant') | |
72 self.opener = util.opener(self.path) | |
73 self.transplants = transplants(self.path, 'transplants', opener=self.opener) | |
74 | |
75 def applied(self, repo, node, parent): | |
76 '''returns True if a node is already an ancestor of parent | |
77 or has already been transplanted''' | |
78 if hasnode(repo, node): | |
79 if node in repo.changelog.reachable(parent, stop=node): | |
80 return True | |
81 for t in self.transplants.get(node): | |
82 # it might have been stripped | |
83 if not hasnode(repo, t.lnode): | |
84 self.transplants.remove(t) | |
85 return False | |
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode): | |
87 return True | |
88 return False | |
89 | |
90 def apply(self, repo, source, revmap, merges, opts={}): | |
91 '''apply the revisions in revmap one by one in revision order''' | |
92 revs = revmap.keys() | |
93 revs.sort() | |
94 | |
95 p1, p2 = repo.dirstate.parents() | |
96 pulls = [] | |
97 diffopts = patch.diffopts(self.ui, opts) | |
98 diffopts.git = True | |
99 | |
100 lock = repo.lock() | |
101 wlock = repo.wlock() | |
102 try: | |
103 for rev in revs: | |
104 node = revmap[rev] | |
105 revstr = '%s:%s' % (rev, revlog.short(node)) | |
106 | |
107 if self.applied(repo, node, p1): | |
108 self.ui.warn(_('skipping already applied revision %s\n') % | |
109 revstr) | |
110 continue | |
111 | |
112 parents = source.changelog.parents(node) | |
113 if not opts.get('filter'): | |
114 # If the changeset parent is the same as the wdir's parent, | |
115 # just pull it. | |
116 if parents[0] == p1: | |
117 pulls.append(node) | |
118 p1 = node | |
119 continue | |
120 if pulls: | |
121 if source != repo: | |
122 repo.pull(source, heads=pulls, lock=lock) | |
123 merge.update(repo, pulls[-1], wlock=wlock) | |
124 p1, p2 = repo.dirstate.parents() | |
125 pulls = [] | |
126 | |
127 domerge = False | |
128 if node in merges: | |
129 # pulling all the merge revs at once would mean we couldn't | |
130 # transplant after the latest even if transplants before them | |
131 # fail. | |
132 domerge = True | |
133 if not hasnode(repo, node): | |
134 repo.pull(source, heads=[node], lock=lock) | |
135 | |
136 if parents[1] != revlog.nullid: | |
137 self.ui.note(_('skipping merge changeset %s:%s\n') | |
138 % (rev, revlog.short(node))) | |
139 patchfile = None | |
140 else: | |
141 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-') | |
142 fp = os.fdopen(fd, 'w') | |
143 patch.export(source, [node], fp=fp, opts=diffopts) | |
144 fp.close() | |
145 | |
146 del revmap[rev] | |
147 if patchfile or domerge: | |
148 try: | |
149 n = self.applyone(repo, node, source.changelog.read(node), | |
150 patchfile, merge=domerge, | |
151 log=opts.get('log'), | |
152 filter=opts.get('filter'), | |
153 lock=lock, wlock=wlock) | |
154 if domerge: | |
155 self.ui.status(_('%s merged at %s\n') % (revstr, | |
156 revlog.short(n))) | |
157 else: | |
158 self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node), | |
159 revlog.short(n))) | |
160 finally: | |
161 if patchfile: | |
162 os.unlink(patchfile) | |
163 if pulls: | |
164 repo.pull(source, heads=pulls, lock=lock) | |
165 merge.update(repo, pulls[-1], wlock=wlock) | |
166 finally: | |
167 self.saveseries(revmap, merges) | |
168 self.transplants.write() | |
169 | |
170 def filter(self, filter, changelog, patchfile): | |
171 '''arbitrarily rewrite changeset before applying it''' | |
172 | |
173 self.ui.status('filtering %s' % patchfile) | |
174 util.system('%s %s' % (filter, util.shellquote(patchfile)), | |
175 environ={'HGUSER': changelog[1]}, | |
176 onerr=util.Abort, errprefix=_('filter failed')) | |
177 | |
178 def applyone(self, repo, node, cl, patchfile, merge=False, log=False, | |
179 filter=None, lock=None, wlock=None): | |
180 '''apply the patch in patchfile to the repository as a transplant''' | |
181 (manifest, user, (time, timezone), files, message) = cl[:5] | |
182 date = "%d %d" % (time, timezone) | |
183 extra = {'transplant_source': node} | |
184 if filter: | |
185 self.filter(filter, cl, patchfile) | |
186 patchfile, message, user, date = patch.extract(self.ui, file(patchfile)) | |
187 | |
188 if log: | |
189 message += '\n(transplanted from %s)' % revlog.hex(node) | |
190 | |
191 self.ui.status(_('applying %s\n') % revlog.short(node)) | |
192 self.ui.note('%s %s\n%s\n' % (user, date, message)) | |
193 | |
194 if not patchfile and not merge: | |
195 raise util.Abort(_('can only omit patchfile if merging')) | |
196 if patchfile: | |
197 try: | |
198 files = {} | |
3726
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
199 try: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
200 fuzz = patch.patch(patchfile, self.ui, cwd=repo.root, |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
201 files=files) |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
202 if not files: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
203 self.ui.warn(_('%s: empty changeset') % revlog.hex(node)) |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
204 return |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
205 finally: |
752884db5037
transplant: recover added/removed files after failed application
Brendan Cully <brendan@kublai.com>
parents:
3725
diff
changeset
|
206 files = patch.updatedir(self.ui, repo, files, wlock=wlock) |
3714 | 207 if filter: |
208 os.unlink(patchfile) | |
209 except Exception, inst: | |
210 if filter: | |
211 os.unlink(patchfile) | |
212 p1 = repo.dirstate.parents()[0] | |
213 p2 = node | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
214 self.log(user, date, message, p1, p2, merge=merge) |
3714 | 215 self.ui.write(str(inst) + '\n') |
216 raise util.Abort(_('Fix up the merge and run hg transplant --continue')) | |
217 else: | |
218 files = None | |
219 if merge: | |
220 p1, p2 = repo.dirstate.parents() | |
221 repo.dirstate.setparents(p1, node) | |
222 | |
223 n = repo.commit(files, message, user, date, lock=lock, wlock=wlock, | |
224 extra=extra) | |
225 if not merge: | |
226 self.transplants.set(n, node) | |
227 | |
228 return n | |
229 | |
230 def resume(self, repo, source, opts=None): | |
231 '''recover last transaction and apply remaining changesets''' | |
232 if os.path.exists(os.path.join(self.path, 'journal')): | |
233 n, node = self.recover(repo) | |
3724
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
234 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node), |
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
235 revlog.short(n))) |
3714 | 236 seriespath = os.path.join(self.path, 'series') |
237 if not os.path.exists(seriespath): | |
238 return | |
239 nodes, merges = self.readseries() | |
240 revmap = {} | |
241 for n in nodes: | |
242 revmap[source.changelog.rev(n)] = n | |
243 os.unlink(seriespath) | |
244 | |
245 self.apply(repo, source, revmap, merges, opts) | |
246 | |
247 def recover(self, repo): | |
248 '''commit working directory using journal metadata''' | |
249 node, user, date, message, parents = self.readlog() | |
250 merge = len(parents) == 2 | |
251 | |
252 if not user or not date or not message or not parents[0]: | |
253 raise util.Abort(_('transplant log file is corrupt')) | |
254 | |
255 wlock = repo.wlock() | |
256 p1, p2 = repo.dirstate.parents() | |
257 if p1 != parents[0]: | |
258 raise util.Abort(_('working dir not at transplant parent %s') % | |
259 revlog.hex(parents[0])) | |
260 if merge: | |
261 repo.dirstate.setparents(p1, parents[1]) | |
262 n = repo.commit(None, message, user, date, wlock=wlock) | |
263 if not n: | |
264 raise util.Abort(_('commit failed')) | |
265 if not merge: | |
266 self.transplants.set(n, node) | |
267 self.unlog() | |
268 | |
269 return n, node | |
270 | |
271 def readseries(self): | |
272 nodes = [] | |
273 merges = [] | |
274 cur = nodes | |
275 for line in self.opener('series').read().splitlines(): | |
276 if line.startswith('# Merges'): | |
277 cur = merges | |
278 continue | |
279 cur.append(revlog.bin(line)) | |
280 | |
281 return (nodes, merges) | |
282 | |
283 def saveseries(self, revmap, merges): | |
284 if not revmap: | |
285 return | |
286 | |
287 if not os.path.isdir(self.path): | |
288 os.mkdir(self.path) | |
289 series = self.opener('series', 'w') | |
290 revs = revmap.keys() | |
291 revs.sort() | |
292 for rev in revs: | |
293 series.write(revlog.hex(revmap[rev]) + '\n') | |
294 if merges: | |
295 series.write('# Merges\n') | |
296 for m in merges: | |
297 series.write(revlog.hex(m) + '\n') | |
298 series.close() | |
299 | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
300 def log(self, user, date, message, p1, p2, merge=False): |
3714 | 301 '''journal changelog metadata for later recover''' |
302 | |
303 if not os.path.isdir(self.path): | |
304 os.mkdir(self.path) | |
305 fp = self.opener('journal', 'w') | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
306 fp.write('# User %s\n' % user) |
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
307 fp.write('# Date %s\n' % date) |
3714 | 308 fp.write('# Node ID %s\n' % revlog.hex(p2)) |
309 fp.write('# Parent ' + revlog.hex(p1) + '\n') | |
310 if merge: | |
311 fp.write('# Parent ' + revlog.hex(p2) + '\n') | |
3725
ccc7a9eb0e5e
transplant: preserve filter changes in --continue log
Brendan Cully <brendan@kublai.com>
parents:
3724
diff
changeset
|
312 fp.write(message.rstrip() + '\n') |
3714 | 313 fp.close() |
314 | |
315 def readlog(self): | |
316 parents = [] | |
317 message = [] | |
318 for line in self.opener('journal').read().splitlines(): | |
319 if line.startswith('# User '): | |
320 user = line[7:] | |
321 elif line.startswith('# Date '): | |
322 date = line[7:] | |
323 elif line.startswith('# Node ID '): | |
324 node = revlog.bin(line[10:]) | |
325 elif line.startswith('# Parent '): | |
326 parents.append(revlog.bin(line[9:])) | |
327 else: | |
328 message.append(line) | |
329 return (node, user, date, '\n'.join(message), parents) | |
330 | |
331 def unlog(self): | |
332 '''remove changelog journal''' | |
333 absdst = os.path.join(self.path, 'journal') | |
334 if os.path.exists(absdst): | |
335 os.unlink(absdst) | |
336 | |
337 def transplantfilter(self, repo, source, root): | |
338 def matchfn(node): | |
339 if self.applied(repo, node, root): | |
340 return False | |
341 if source.changelog.parents(node)[1] != revlog.nullid: | |
342 return False | |
343 extra = source.changelog.read(node)[5] | |
344 cnode = extra.get('transplant_source') | |
345 if cnode and self.applied(repo, cnode, root): | |
346 return False | |
347 return True | |
348 | |
349 return matchfn | |
350 | |
351 def hasnode(repo, node): | |
352 try: | |
353 return repo.changelog.rev(node) != None | |
354 except revlog.RevlogError: | |
355 return False | |
356 | |
357 def browserevs(ui, repo, nodes, opts): | |
358 '''interactively transplant changesets''' | |
359 def browsehelp(ui): | |
360 ui.write('y: transplant this changeset\n' | |
361 'n: skip this changeset\n' | |
362 'm: merge at this changeset\n' | |
363 'p: show patch\n' | |
364 'c: commit selected changesets\n' | |
365 'q: cancel transplant\n' | |
366 '?: show this help\n') | |
367 | |
3723
c828fca6f38a
transplant: show_changeset moved to cmdutil
Brendan Cully <brendan@kublai.com>
parents:
3714
diff
changeset
|
368 displayer = cmdutil.show_changeset(ui, repo, opts) |
3714 | 369 transplants = [] |
370 merges = [] | |
371 for node in nodes: | |
372 displayer.show(changenode=node) | |
373 action = None | |
374 while not action: | |
375 action = ui.prompt(_('apply changeset? [ynmpcq?]:')) | |
376 if action == '?': | |
377 browsehelp(ui) | |
378 action = None | |
379 elif action == 'p': | |
380 parent = repo.changelog.parents(node)[0] | |
381 patch.diff(repo, parent, node) | |
382 action = None | |
383 elif action not in ('y', 'n', 'm', 'c', 'q'): | |
384 ui.write('no such option\n') | |
385 action = None | |
386 if action == 'y': | |
387 transplants.append(node) | |
388 elif action == 'm': | |
389 merges.append(node) | |
390 elif action == 'c': | |
391 break | |
392 elif action == 'q': | |
393 transplants = () | |
394 merges = () | |
395 break | |
396 return (transplants, merges) | |
397 | |
398 def transplant(ui, repo, *revs, **opts): | |
399 '''transplant changesets from another branch | |
400 | |
401 Selected changesets will be applied on top of the current working | |
402 directory with the log of the original changeset. If --log is | |
403 specified, log messages will have a comment appended of the form: | |
404 | |
405 (transplanted from CHANGESETHASH) | |
406 | |
407 You can rewrite the changelog message with the --filter option. | |
408 Its argument will be invoked with the current changelog message | |
409 as $1 and the patch as $2. | |
410 | |
411 If --source is specified, selects changesets from the named | |
412 repository. If --branch is specified, selects changesets from the | |
413 branch holding the named revision, up to that revision. If --all | |
414 is specified, all changesets on the branch will be transplanted, | |
415 otherwise you will be prompted to select the changesets you want. | |
416 | |
417 hg transplant --branch REVISION --all will rebase the selected branch | |
418 (up to the named revision) onto your current working directory. | |
419 | |
420 You can optionally mark selected transplanted changesets as | |
421 merge changesets. You will not be prompted to transplant any | |
422 ancestors of a merged transplant, and you can merge descendants | |
423 of them normally instead of transplanting them. | |
424 | |
425 If no merges or revisions are provided, hg transplant will start | |
426 an interactive changeset browser. | |
427 | |
428 If a changeset application fails, you can fix the merge by hand and | |
429 then resume where you left off by calling hg transplant --continue. | |
430 ''' | |
431 def getoneitem(opts, item, errmsg): | |
432 val = opts.get(item) | |
433 if val: | |
434 if len(val) > 1: | |
435 raise util.Abort(errmsg) | |
436 else: | |
437 return val[0] | |
438 | |
439 def getremotechanges(repo, url): | |
440 sourcerepo = ui.expandpath(url) | |
441 source = hg.repository(ui, sourcerepo) | |
442 incoming = repo.findincoming(source, force=True) | |
443 if not incoming: | |
444 return (source, None, None) | |
445 | |
446 bundle = None | |
447 if not source.local(): | |
448 cg = source.changegroup(incoming, 'incoming') | |
449 bundle = commands.write_bundle(cg, compress=False) | |
450 source = bundlerepo.bundlerepository(ui, repo.root, bundle) | |
451 | |
452 return (source, incoming, bundle) | |
453 | |
454 def incwalk(repo, incoming, branches, match=util.always): | |
455 if not branches: | |
456 branches=None | |
457 for node in repo.changelog.nodesbetween(incoming, branches)[0]: | |
458 if match(node): | |
459 yield node | |
460 | |
461 def transplantwalk(repo, root, branches, match=util.always): | |
462 if not branches: | |
463 branches = repo.heads() | |
464 ancestors = [] | |
465 for branch in branches: | |
466 ancestors.append(repo.changelog.ancestor(root, branch)) | |
467 for node in repo.changelog.nodesbetween(ancestors, branches)[0]: | |
468 if match(node): | |
469 yield node | |
470 | |
471 def checkopts(opts, revs): | |
472 if opts.get('continue'): | |
473 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')): | |
474 raise util.Abort(_('--continue is incompatible with branch, all or merge')) | |
475 return | |
476 if not (opts.get('source') or revs or | |
477 opts.get('merge') or opts.get('branch')): | |
478 raise util.Abort(_('no source URL, branch tag or revision list provided')) | |
479 if opts.get('all'): | |
480 if not opts.get('branch'): | |
481 raise util.Abort(_('--all requires a branch revision')) | |
482 if revs: | |
483 raise util.Abort(_('--all is incompatible with a revision list')) | |
484 | |
485 checkopts(opts, revs) | |
486 | |
487 if not opts.get('log'): | |
488 opts['log'] = ui.config('transplant', 'log') | |
489 if not opts.get('filter'): | |
490 opts['filter'] = ui.config('transplant', 'filter') | |
491 | |
492 tp = transplanter(ui, repo) | |
493 | |
494 p1, p2 = repo.dirstate.parents() | |
495 if p1 == revlog.nullid: | |
496 raise util.Abort(_('no revision checked out')) | |
497 if not opts.get('continue'): | |
498 if p2 != revlog.nullid: | |
499 raise util.Abort(_('outstanding uncommitted merges')) | |
500 m, a, r, d = repo.status()[:4] | |
501 if m or a or r or d: | |
502 raise util.Abort(_('outstanding local changes')) | |
503 | |
504 bundle = None | |
505 source = opts.get('source') | |
506 if source: | |
507 (source, incoming, bundle) = getremotechanges(repo, source) | |
508 else: | |
509 source = repo | |
510 | |
511 try: | |
512 if opts.get('continue'): | |
3724
ea523d6f5f1a
transplant: fix --continue; add --continue test
Brendan Cully <brendan@kublai.com>
parents:
3723
diff
changeset
|
513 tp.resume(repo, source, opts) |
3714 | 514 return |
515 | |
516 tf=tp.transplantfilter(repo, source, p1) | |
517 if opts.get('prune'): | |
518 prune = [source.lookup(r) | |
519 for r in cmdutil.revrange(source, opts.get('prune'))] | |
520 matchfn = lambda x: tf(x) and x not in prune | |
521 else: | |
522 matchfn = tf | |
523 branches = map(source.lookup, opts.get('branch', ())) | |
524 merges = map(source.lookup, opts.get('merge', ())) | |
525 revmap = {} | |
526 if revs: | |
527 for r in cmdutil.revrange(source, revs): | |
528 revmap[int(r)] = source.lookup(r) | |
529 elif opts.get('all') or not merges: | |
530 if source != repo: | |
531 alltransplants = incwalk(source, incoming, branches, match=matchfn) | |
532 else: | |
533 alltransplants = transplantwalk(source, p1, branches, match=matchfn) | |
534 if opts.get('all'): | |
535 revs = alltransplants | |
536 else: | |
537 revs, newmerges = browserevs(ui, source, alltransplants, opts) | |
538 merges.extend(newmerges) | |
539 for r in revs: | |
540 revmap[source.changelog.rev(r)] = r | |
541 for r in merges: | |
542 revmap[source.changelog.rev(r)] = r | |
543 | |
544 revs = revmap.keys() | |
545 revs.sort() | |
546 pulls = [] | |
547 | |
548 tp.apply(repo, source, revmap, merges, opts) | |
549 finally: | |
550 if bundle: | |
551 os.unlink(bundle) | |
552 | |
553 cmdtable = { | |
554 "transplant": | |
555 (transplant, | |
556 [('s', 'source', '', _('pull patches from REPOSITORY')), | |
557 ('b', 'branch', [], _('pull patches from branch BRANCH')), | |
558 ('a', 'all', None, _('pull all changesets up to BRANCH')), | |
559 ('p', 'prune', [], _('skip over REV')), | |
560 ('m', 'merge', [], _('merge at REV')), | |
561 ('', 'log', None, _('append transplant info to log message')), | |
562 ('c', 'continue', None, _('continue last transplant session after repair')), | |
563 ('', 'filter', '', _('filter changesets through FILTER'))], | |
564 _('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...')) | |
565 } |