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