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