53 command = registrar.command(cmdtable) |
53 command = registrar.command(cmdtable) |
54 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
54 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
56 # be specifying the version(s) of Mercurial they are tested with, or |
56 # be specifying the version(s) of Mercurial they are tested with, or |
57 # leave the attribute unspecified. |
57 # leave the attribute unspecified. |
58 testedwith = 'ships-with-hg-core' |
58 testedwith = b'ships-with-hg-core' |
59 |
59 |
60 configtable = {} |
60 configtable = {} |
61 configitem = registrar.configitem(configtable) |
61 configitem = registrar.configitem(configtable) |
62 |
62 |
63 configitem( |
63 configitem( |
64 'transplant', 'filter', default=None, |
64 b'transplant', b'filter', default=None, |
65 ) |
65 ) |
66 configitem( |
66 configitem( |
67 'transplant', 'log', default=None, |
67 b'transplant', b'log', default=None, |
68 ) |
68 ) |
69 |
69 |
70 |
70 |
71 class transplantentry(object): |
71 class transplantentry(object): |
72 def __init__(self, lnode, rnode): |
72 def __init__(self, lnode, rnode): |
88 |
88 |
89 def read(self): |
89 def read(self): |
90 abspath = os.path.join(self.path, self.transplantfile) |
90 abspath = os.path.join(self.path, self.transplantfile) |
91 if self.transplantfile and os.path.exists(abspath): |
91 if self.transplantfile and os.path.exists(abspath): |
92 for line in self.opener.read(self.transplantfile).splitlines(): |
92 for line in self.opener.read(self.transplantfile).splitlines(): |
93 lnode, rnode = map(revlog.bin, line.split(':')) |
93 lnode, rnode = map(revlog.bin, line.split(b':')) |
94 list = self.transplants.setdefault(rnode, []) |
94 list = self.transplants.setdefault(rnode, []) |
95 list.append(transplantentry(lnode, rnode)) |
95 list.append(transplantentry(lnode, rnode)) |
96 |
96 |
97 def write(self): |
97 def write(self): |
98 if self.dirty and self.transplantfile: |
98 if self.dirty and self.transplantfile: |
99 if not os.path.isdir(self.path): |
99 if not os.path.isdir(self.path): |
100 os.mkdir(self.path) |
100 os.mkdir(self.path) |
101 fp = self.opener(self.transplantfile, 'w') |
101 fp = self.opener(self.transplantfile, b'w') |
102 for list in self.transplants.itervalues(): |
102 for list in self.transplants.itervalues(): |
103 for t in list: |
103 for t in list: |
104 l, r = map(nodemod.hex, (t.lnode, t.rnode)) |
104 l, r = map(nodemod.hex, (t.lnode, t.rnode)) |
105 fp.write(l + ':' + r + '\n') |
105 fp.write(l + b':' + r + b'\n') |
106 fp.close() |
106 fp.close() |
107 self.dirty = False |
107 self.dirty = False |
108 |
108 |
109 def get(self, rnode): |
109 def get(self, rnode): |
110 return self.transplants.get(rnode) or [] |
110 return self.transplants.get(rnode) or [] |
122 |
122 |
123 |
123 |
124 class transplanter(object): |
124 class transplanter(object): |
125 def __init__(self, ui, repo, opts): |
125 def __init__(self, ui, repo, opts): |
126 self.ui = ui |
126 self.ui = ui |
127 self.path = repo.vfs.join('transplant') |
127 self.path = repo.vfs.join(b'transplant') |
128 self.opener = vfsmod.vfs(self.path) |
128 self.opener = vfsmod.vfs(self.path) |
129 self.transplants = transplants( |
129 self.transplants = transplants( |
130 self.path, 'transplants', opener=self.opener |
130 self.path, b'transplants', opener=self.opener |
131 ) |
131 ) |
132 |
132 |
133 def getcommiteditor(): |
133 def getcommiteditor(): |
134 editform = cmdutil.mergeeditform(repo[None], 'transplant') |
134 editform = cmdutil.mergeeditform(repo[None], b'transplant') |
135 return cmdutil.getcommiteditor( |
135 return cmdutil.getcommiteditor( |
136 editform=editform, **pycompat.strkwargs(opts) |
136 editform=editform, **pycompat.strkwargs(opts) |
137 ) |
137 ) |
138 |
138 |
139 self.getcommiteditor = getcommiteditor |
139 self.getcommiteditor = getcommiteditor |
212 if not hasnode(repo, node): |
212 if not hasnode(repo, node): |
213 exchange.pull(repo, source.peer(), heads=[node]) |
213 exchange.pull(repo, source.peer(), heads=[node]) |
214 |
214 |
215 skipmerge = False |
215 skipmerge = False |
216 if parents[1] != revlog.nullid: |
216 if parents[1] != revlog.nullid: |
217 if not opts.get('parent'): |
217 if not opts.get(b'parent'): |
218 self.ui.note( |
218 self.ui.note( |
219 _('skipping merge changeset %d:%s\n') |
219 _(b'skipping merge changeset %d:%s\n') |
220 % (rev, nodemod.short(node)) |
220 % (rev, nodemod.short(node)) |
221 ) |
221 ) |
222 skipmerge = True |
222 skipmerge = True |
223 else: |
223 else: |
224 parent = source.lookup(opts['parent']) |
224 parent = source.lookup(opts[b'parent']) |
225 if parent not in parents: |
225 if parent not in parents: |
226 raise error.Abort( |
226 raise error.Abort( |
227 _('%s is not a parent of %s') |
227 _(b'%s is not a parent of %s') |
228 % (nodemod.short(parent), nodemod.short(node)) |
228 % (nodemod.short(parent), nodemod.short(node)) |
229 ) |
229 ) |
230 else: |
230 else: |
231 parent = parents[0] |
231 parent = parents[0] |
232 |
232 |
233 if skipmerge: |
233 if skipmerge: |
234 patchfile = None |
234 patchfile = None |
235 else: |
235 else: |
236 fd, patchfile = pycompat.mkstemp(prefix='hg-transplant-') |
236 fd, patchfile = pycompat.mkstemp(prefix=b'hg-transplant-') |
237 fp = os.fdopen(fd, r'wb') |
237 fp = os.fdopen(fd, r'wb') |
238 gen = patch.diff(source, parent, node, opts=diffopts) |
238 gen = patch.diff(source, parent, node, opts=diffopts) |
239 for chunk in gen: |
239 for chunk in gen: |
240 fp.write(chunk) |
240 fp.write(chunk) |
241 fp.close() |
241 fp.close() |
284 lock.release() |
284 lock.release() |
285 |
285 |
286 def filter(self, filter, node, changelog, patchfile): |
286 def filter(self, filter, node, changelog, patchfile): |
287 '''arbitrarily rewrite changeset before applying it''' |
287 '''arbitrarily rewrite changeset before applying it''' |
288 |
288 |
289 self.ui.status(_('filtering %s\n') % patchfile) |
289 self.ui.status(_(b'filtering %s\n') % patchfile) |
290 user, date, msg = (changelog[1], changelog[2], changelog[4]) |
290 user, date, msg = (changelog[1], changelog[2], changelog[4]) |
291 fd, headerfile = pycompat.mkstemp(prefix='hg-transplant-') |
291 fd, headerfile = pycompat.mkstemp(prefix=b'hg-transplant-') |
292 fp = os.fdopen(fd, r'wb') |
292 fp = os.fdopen(fd, r'wb') |
293 fp.write("# HG changeset patch\n") |
293 fp.write(b"# HG changeset patch\n") |
294 fp.write("# User %s\n" % user) |
294 fp.write(b"# User %s\n" % user) |
295 fp.write("# Date %d %d\n" % date) |
295 fp.write(b"# Date %d %d\n" % date) |
296 fp.write(msg + '\n') |
296 fp.write(msg + b'\n') |
297 fp.close() |
297 fp.close() |
298 |
298 |
299 try: |
299 try: |
300 self.ui.system( |
300 self.ui.system( |
301 '%s %s %s' |
301 b'%s %s %s' |
302 % ( |
302 % ( |
303 filter, |
303 filter, |
304 procutil.shellquote(headerfile), |
304 procutil.shellquote(headerfile), |
305 procutil.shellquote(patchfile), |
305 procutil.shellquote(patchfile), |
306 ), |
306 ), |
307 environ={ |
307 environ={ |
308 'HGUSER': changelog[1], |
308 b'HGUSER': changelog[1], |
309 'HGREVISION': nodemod.hex(node), |
309 b'HGREVISION': nodemod.hex(node), |
310 }, |
310 }, |
311 onerr=error.Abort, |
311 onerr=error.Abort, |
312 errprefix=_('filter failed'), |
312 errprefix=_(b'filter failed'), |
313 blockedtag='transplant_filter', |
313 blockedtag=b'transplant_filter', |
314 ) |
314 ) |
315 user, date, msg = self.parselog(open(headerfile, 'rb'))[1:4] |
315 user, date, msg = self.parselog(open(headerfile, b'rb'))[1:4] |
316 finally: |
316 finally: |
317 os.unlink(headerfile) |
317 os.unlink(headerfile) |
318 |
318 |
319 return (user, date, msg) |
319 return (user, date, msg) |
320 |
320 |
321 def applyone( |
321 def applyone( |
322 self, repo, node, cl, patchfile, merge=False, log=False, filter=None |
322 self, repo, node, cl, patchfile, merge=False, log=False, filter=None |
323 ): |
323 ): |
324 '''apply the patch in patchfile to the repository as a transplant''' |
324 '''apply the patch in patchfile to the repository as a transplant''' |
325 (manifest, user, (time, timezone), files, message) = cl[:5] |
325 (manifest, user, (time, timezone), files, message) = cl[:5] |
326 date = "%d %d" % (time, timezone) |
326 date = b"%d %d" % (time, timezone) |
327 extra = {'transplant_source': node} |
327 extra = {b'transplant_source': node} |
328 if filter: |
328 if filter: |
329 (user, date, message) = self.filter(filter, node, cl, patchfile) |
329 (user, date, message) = self.filter(filter, node, cl, patchfile) |
330 |
330 |
331 if log: |
331 if log: |
332 # we don't translate messages inserted into commits |
332 # we don't translate messages inserted into commits |
333 message += '\n(transplanted from %s)' % nodemod.hex(node) |
333 message += b'\n(transplanted from %s)' % nodemod.hex(node) |
334 |
334 |
335 self.ui.status(_('applying %s\n') % nodemod.short(node)) |
335 self.ui.status(_(b'applying %s\n') % nodemod.short(node)) |
336 self.ui.note('%s %s\n%s\n' % (user, date, message)) |
336 self.ui.note(b'%s %s\n%s\n' % (user, date, message)) |
337 |
337 |
338 if not patchfile and not merge: |
338 if not patchfile and not merge: |
339 raise error.Abort(_('can only omit patchfile if merging')) |
339 raise error.Abort(_(b'can only omit patchfile if merging')) |
340 if patchfile: |
340 if patchfile: |
341 try: |
341 try: |
342 files = set() |
342 files = set() |
343 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None) |
343 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None) |
344 files = list(files) |
344 files = list(files) |
345 except Exception as inst: |
345 except Exception as inst: |
346 seriespath = os.path.join(self.path, 'series') |
346 seriespath = os.path.join(self.path, b'series') |
347 if os.path.exists(seriespath): |
347 if os.path.exists(seriespath): |
348 os.unlink(seriespath) |
348 os.unlink(seriespath) |
349 p1 = repo.dirstate.p1() |
349 p1 = repo.dirstate.p1() |
350 p2 = node |
350 p2 = node |
351 self.log(user, date, message, p1, p2, merge=merge) |
351 self.log(user, date, message, p1, p2, merge=merge) |
352 self.ui.write(stringutil.forcebytestr(inst) + '\n') |
352 self.ui.write(stringutil.forcebytestr(inst) + b'\n') |
353 raise TransplantError( |
353 raise TransplantError( |
354 _( |
354 _( |
355 'fix up the working directory and run ' |
355 b'fix up the working directory and run ' |
356 'hg transplant --continue' |
356 b'hg transplant --continue' |
357 ) |
357 ) |
358 ) |
358 ) |
359 else: |
359 else: |
360 files = None |
360 files = None |
361 if merge: |
361 if merge: |
373 match=m, |
373 match=m, |
374 editor=self.getcommiteditor(), |
374 editor=self.getcommiteditor(), |
375 ) |
375 ) |
376 if not n: |
376 if not n: |
377 self.ui.warn( |
377 self.ui.warn( |
378 _('skipping emptied changeset %s\n') % nodemod.short(node) |
378 _(b'skipping emptied changeset %s\n') % nodemod.short(node) |
379 ) |
379 ) |
380 return None |
380 return None |
381 if not merge: |
381 if not merge: |
382 self.transplants.set(n, node) |
382 self.transplants.set(n, node) |
383 |
383 |
384 return n |
384 return n |
385 |
385 |
386 def canresume(self): |
386 def canresume(self): |
387 return os.path.exists(os.path.join(self.path, 'journal')) |
387 return os.path.exists(os.path.join(self.path, b'journal')) |
388 |
388 |
389 def resume(self, repo, source, opts): |
389 def resume(self, repo, source, opts): |
390 '''recover last transaction and apply remaining changesets''' |
390 '''recover last transaction and apply remaining changesets''' |
391 if os.path.exists(os.path.join(self.path, 'journal')): |
391 if os.path.exists(os.path.join(self.path, b'journal')): |
392 n, node = self.recover(repo, source, opts) |
392 n, node = self.recover(repo, source, opts) |
393 if n: |
393 if n: |
394 self.ui.status( |
394 self.ui.status( |
395 _('%s transplanted as %s\n') |
395 _(b'%s transplanted as %s\n') |
396 % (nodemod.short(node), nodemod.short(n)) |
396 % (nodemod.short(node), nodemod.short(n)) |
397 ) |
397 ) |
398 else: |
398 else: |
399 self.ui.status( |
399 self.ui.status( |
400 _('%s skipped due to empty diff\n') % (nodemod.short(node),) |
400 _(b'%s skipped due to empty diff\n') |
|
401 % (nodemod.short(node),) |
401 ) |
402 ) |
402 seriespath = os.path.join(self.path, 'series') |
403 seriespath = os.path.join(self.path, b'series') |
403 if not os.path.exists(seriespath): |
404 if not os.path.exists(seriespath): |
404 self.transplants.write() |
405 self.transplants.write() |
405 return |
406 return |
406 nodes, merges = self.readseries() |
407 nodes, merges = self.readseries() |
407 revmap = {} |
408 revmap = {} |
415 '''commit working directory using journal metadata''' |
416 '''commit working directory using journal metadata''' |
416 node, user, date, message, parents = self.readlog() |
417 node, user, date, message, parents = self.readlog() |
417 merge = False |
418 merge = False |
418 |
419 |
419 if not user or not date or not message or not parents[0]: |
420 if not user or not date or not message or not parents[0]: |
420 raise error.Abort(_('transplant log file is corrupt')) |
421 raise error.Abort(_(b'transplant log file is corrupt')) |
421 |
422 |
422 parent = parents[0] |
423 parent = parents[0] |
423 if len(parents) > 1: |
424 if len(parents) > 1: |
424 if opts.get('parent'): |
425 if opts.get(b'parent'): |
425 parent = source.lookup(opts['parent']) |
426 parent = source.lookup(opts[b'parent']) |
426 if parent not in parents: |
427 if parent not in parents: |
427 raise error.Abort( |
428 raise error.Abort( |
428 _('%s is not a parent of %s') |
429 _(b'%s is not a parent of %s') |
429 % (nodemod.short(parent), nodemod.short(node)) |
430 % (nodemod.short(parent), nodemod.short(node)) |
430 ) |
431 ) |
431 else: |
432 else: |
432 merge = True |
433 merge = True |
433 |
434 |
434 extra = {'transplant_source': node} |
435 extra = {b'transplant_source': node} |
435 try: |
436 try: |
436 p1 = repo.dirstate.p1() |
437 p1 = repo.dirstate.p1() |
437 if p1 != parent: |
438 if p1 != parent: |
438 raise error.Abort( |
439 raise error.Abort( |
439 _('working directory not at transplant ' 'parent %s') |
440 _(b'working directory not at transplant ' b'parent %s') |
440 % nodemod.hex(parent) |
441 % nodemod.hex(parent) |
441 ) |
442 ) |
442 if merge: |
443 if merge: |
443 repo.setparents(p1, parents[1]) |
444 repo.setparents(p1, parents[1]) |
444 modified, added, removed, deleted = repo.status()[:4] |
445 modified, added, removed, deleted = repo.status()[:4] |
511 user = None |
512 user = None |
512 date = None |
513 date = None |
513 for line in fp.read().splitlines(): |
514 for line in fp.read().splitlines(): |
514 if inmsg: |
515 if inmsg: |
515 message.append(line) |
516 message.append(line) |
516 elif line.startswith('# User '): |
517 elif line.startswith(b'# User '): |
517 user = line[7:] |
518 user = line[7:] |
518 elif line.startswith('# Date '): |
519 elif line.startswith(b'# Date '): |
519 date = line[7:] |
520 date = line[7:] |
520 elif line.startswith('# Node ID '): |
521 elif line.startswith(b'# Node ID '): |
521 node = revlog.bin(line[10:]) |
522 node = revlog.bin(line[10:]) |
522 elif line.startswith('# Parent '): |
523 elif line.startswith(b'# Parent '): |
523 parents.append(revlog.bin(line[9:])) |
524 parents.append(revlog.bin(line[9:])) |
524 elif not line.startswith('# '): |
525 elif not line.startswith(b'# '): |
525 inmsg = True |
526 inmsg = True |
526 message.append(line) |
527 message.append(line) |
527 if None in (user, date): |
528 if None in (user, date): |
528 raise error.Abort(_("filter corrupted changeset (no user or date)")) |
529 raise error.Abort( |
529 return (node, user, date, '\n'.join(message), parents) |
530 _(b"filter corrupted changeset (no user or date)") |
|
531 ) |
|
532 return (node, user, date, b'\n'.join(message), parents) |
530 |
533 |
531 def log(self, user, date, message, p1, p2, merge=False): |
534 def log(self, user, date, message, p1, p2, merge=False): |
532 '''journal changelog metadata for later recover''' |
535 '''journal changelog metadata for later recover''' |
533 |
536 |
534 if not os.path.isdir(self.path): |
537 if not os.path.isdir(self.path): |
535 os.mkdir(self.path) |
538 os.mkdir(self.path) |
536 fp = self.opener('journal', 'w') |
539 fp = self.opener(b'journal', b'w') |
537 fp.write('# User %s\n' % user) |
540 fp.write(b'# User %s\n' % user) |
538 fp.write('# Date %s\n' % date) |
541 fp.write(b'# Date %s\n' % date) |
539 fp.write('# Node ID %s\n' % nodemod.hex(p2)) |
542 fp.write(b'# Node ID %s\n' % nodemod.hex(p2)) |
540 fp.write('# Parent ' + nodemod.hex(p1) + '\n') |
543 fp.write(b'# Parent ' + nodemod.hex(p1) + b'\n') |
541 if merge: |
544 if merge: |
542 fp.write('# Parent ' + nodemod.hex(p2) + '\n') |
545 fp.write(b'# Parent ' + nodemod.hex(p2) + b'\n') |
543 fp.write(message.rstrip() + '\n') |
546 fp.write(message.rstrip() + b'\n') |
544 fp.close() |
547 fp.close() |
545 |
548 |
546 def readlog(self): |
549 def readlog(self): |
547 return self.parselog(self.opener('journal')) |
550 return self.parselog(self.opener(b'journal')) |
548 |
551 |
549 def unlog(self): |
552 def unlog(self): |
550 '''remove changelog journal''' |
553 '''remove changelog journal''' |
551 absdst = os.path.join(self.path, 'journal') |
554 absdst = os.path.join(self.path, b'journal') |
552 if os.path.exists(absdst): |
555 if os.path.exists(absdst): |
553 os.unlink(absdst) |
556 os.unlink(absdst) |
554 |
557 |
555 def transplantfilter(self, repo, source, root): |
558 def transplantfilter(self, repo, source, root): |
556 def matchfn(node): |
559 def matchfn(node): |
557 if self.applied(repo, node, root): |
560 if self.applied(repo, node, root): |
558 return False |
561 return False |
559 if source.changelog.parents(node)[1] != revlog.nullid: |
562 if source.changelog.parents(node)[1] != revlog.nullid: |
560 return False |
563 return False |
561 extra = source.changelog.read(node)[5] |
564 extra = source.changelog.read(node)[5] |
562 cnode = extra.get('transplant_source') |
565 cnode = extra.get(b'transplant_source') |
563 if cnode and self.applied(repo, cnode, root): |
566 if cnode and self.applied(repo, cnode, root): |
564 return False |
567 return False |
565 return True |
568 return True |
566 |
569 |
567 return matchfn |
570 return matchfn |
578 '''interactively transplant changesets''' |
581 '''interactively transplant changesets''' |
579 displayer = logcmdutil.changesetdisplayer(ui, repo, opts) |
582 displayer = logcmdutil.changesetdisplayer(ui, repo, opts) |
580 transplants = [] |
583 transplants = [] |
581 merges = [] |
584 merges = [] |
582 prompt = _( |
585 prompt = _( |
583 'apply changeset? [ynmpcq?]:' |
586 b'apply changeset? [ynmpcq?]:' |
584 '$$ &yes, transplant this changeset' |
587 b'$$ &yes, transplant this changeset' |
585 '$$ &no, skip this changeset' |
588 b'$$ &no, skip this changeset' |
586 '$$ &merge at this changeset' |
589 b'$$ &merge at this changeset' |
587 '$$ show &patch' |
590 b'$$ show &patch' |
588 '$$ &commit selected changesets' |
591 b'$$ &commit selected changesets' |
589 '$$ &quit and cancel transplant' |
592 b'$$ &quit and cancel transplant' |
590 '$$ &? (show this help)' |
593 b'$$ &? (show this help)' |
591 ) |
594 ) |
592 for node in nodes: |
595 for node in nodes: |
593 displayer.show(repo[node]) |
596 displayer.show(repo[node]) |
594 action = None |
597 action = None |
595 while not action: |
598 while not action: |
596 choice = ui.promptchoice(prompt) |
599 choice = ui.promptchoice(prompt) |
597 action = 'ynmpcq?'[choice : choice + 1] |
600 action = b'ynmpcq?'[choice : choice + 1] |
598 if action == '?': |
601 if action == b'?': |
599 for c, t in ui.extractchoices(prompt)[1]: |
602 for c, t in ui.extractchoices(prompt)[1]: |
600 ui.write('%s: %s\n' % (c, t)) |
603 ui.write(b'%s: %s\n' % (c, t)) |
601 action = None |
604 action = None |
602 elif action == 'p': |
605 elif action == b'p': |
603 parent = repo.changelog.parents(node)[0] |
606 parent = repo.changelog.parents(node)[0] |
604 for chunk in patch.diff(repo, parent, node): |
607 for chunk in patch.diff(repo, parent, node): |
605 ui.write(chunk) |
608 ui.write(chunk) |
606 action = None |
609 action = None |
607 if action == 'y': |
610 if action == b'y': |
608 transplants.append(node) |
611 transplants.append(node) |
609 elif action == 'm': |
612 elif action == b'm': |
610 merges.append(node) |
613 merges.append(node) |
611 elif action == 'c': |
614 elif action == b'c': |
612 break |
615 break |
613 elif action == 'q': |
616 elif action == b'q': |
614 transplants = () |
617 transplants = () |
615 merges = () |
618 merges = () |
616 break |
619 break |
617 displayer.close() |
620 displayer.close() |
618 return (transplants, merges) |
621 return (transplants, merges) |
619 |
622 |
620 |
623 |
621 @command( |
624 @command( |
622 'transplant', |
625 b'transplant', |
623 [ |
626 [ |
624 ('s', 'source', '', _('transplant changesets from REPO'), _('REPO')), |
|
625 ('b', 'branch', [], _('use this source changeset as head'), _('REV')), |
|
626 ( |
627 ( |
627 'a', |
628 b's', |
628 'all', |
629 b'source', |
|
630 b'', |
|
631 _(b'transplant changesets from REPO'), |
|
632 _(b'REPO'), |
|
633 ), |
|
634 ( |
|
635 b'b', |
|
636 b'branch', |
|
637 [], |
|
638 _(b'use this source changeset as head'), |
|
639 _(b'REV'), |
|
640 ), |
|
641 ( |
|
642 b'a', |
|
643 b'all', |
629 None, |
644 None, |
630 _('pull all changesets up to the --branch revisions'), |
645 _(b'pull all changesets up to the --branch revisions'), |
631 ), |
646 ), |
632 ('p', 'prune', [], _('skip over REV'), _('REV')), |
647 (b'p', b'prune', [], _(b'skip over REV'), _(b'REV')), |
633 ('m', 'merge', [], _('merge at REV'), _('REV')), |
648 (b'm', b'merge', [], _(b'merge at REV'), _(b'REV')), |
634 ( |
649 ( |
635 '', |
650 b'', |
636 'parent', |
651 b'parent', |
637 '', |
652 b'', |
638 _('parent to choose when transplanting merge'), |
653 _(b'parent to choose when transplanting merge'), |
639 _('REV'), |
654 _(b'REV'), |
640 ), |
655 ), |
641 ('e', 'edit', False, _('invoke editor on commit messages')), |
656 (b'e', b'edit', False, _(b'invoke editor on commit messages')), |
642 ('', 'log', None, _('append transplant info to log message')), |
657 (b'', b'log', None, _(b'append transplant info to log message')), |
643 ('', 'stop', False, _('stop interrupted transplant')), |
658 (b'', b'stop', False, _(b'stop interrupted transplant')), |
644 ( |
659 ( |
645 'c', |
660 b'c', |
646 'continue', |
661 b'continue', |
647 None, |
662 None, |
648 _('continue last transplant session ' 'after fixing conflicts'), |
663 _(b'continue last transplant session ' b'after fixing conflicts'), |
649 ), |
664 ), |
650 ('', 'filter', '', _('filter changesets through command'), _('CMD')), |
665 ( |
|
666 b'', |
|
667 b'filter', |
|
668 b'', |
|
669 _(b'filter changesets through command'), |
|
670 _(b'CMD'), |
|
671 ), |
651 ], |
672 ], |
652 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] ' '[-m REV] [REV]...'), |
673 _( |
|
674 b'hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] ' |
|
675 b'[-m REV] [REV]...' |
|
676 ), |
653 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
677 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
654 ) |
678 ) |
655 def transplant(ui, repo, *revs, **opts): |
679 def transplant(ui, repo, *revs, **opts): |
656 '''transplant changesets from another branch |
680 '''transplant changesets from another branch |
657 |
681 |
726 for node in repo.changelog.nodesbetween(ancestors, heads)[0]: |
750 for node in repo.changelog.nodesbetween(ancestors, heads)[0]: |
727 if match(node): |
751 if match(node): |
728 yield node |
752 yield node |
729 |
753 |
730 def checkopts(opts, revs): |
754 def checkopts(opts, revs): |
731 if opts.get('continue'): |
755 if opts.get(b'continue'): |
732 if opts.get('branch') or opts.get('all') or opts.get('merge'): |
756 if opts.get(b'branch') or opts.get(b'all') or opts.get(b'merge'): |
733 raise error.Abort( |
757 raise error.Abort( |
734 _( |
758 _( |
735 '--continue is incompatible with ' |
759 b'--continue is incompatible with ' |
736 '--branch, --all and --merge' |
760 b'--branch, --all and --merge' |
737 ) |
761 ) |
738 ) |
762 ) |
739 return |
763 return |
740 if opts.get('stop'): |
764 if opts.get(b'stop'): |
741 if opts.get('branch') or opts.get('all') or opts.get('merge'): |
765 if opts.get(b'branch') or opts.get(b'all') or opts.get(b'merge'): |
742 raise error.Abort( |
766 raise error.Abort( |
743 _( |
767 _( |
744 '--stop is incompatible with ' |
768 b'--stop is incompatible with ' |
745 '--branch, --all and --merge' |
769 b'--branch, --all and --merge' |
746 ) |
770 ) |
747 ) |
771 ) |
748 return |
772 return |
749 if not ( |
773 if not ( |
750 opts.get('source') |
774 opts.get(b'source') |
751 or revs |
775 or revs |
752 or opts.get('merge') |
776 or opts.get(b'merge') |
753 or opts.get('branch') |
777 or opts.get(b'branch') |
754 ): |
778 ): |
755 raise error.Abort( |
779 raise error.Abort( |
756 _( |
780 _( |
757 'no source URL, branch revision, or revision ' |
781 b'no source URL, branch revision, or revision ' |
758 'list provided' |
782 b'list provided' |
759 ) |
783 ) |
760 ) |
784 ) |
761 if opts.get('all'): |
785 if opts.get(b'all'): |
762 if not opts.get('branch'): |
786 if not opts.get(b'branch'): |
763 raise error.Abort(_('--all requires a branch revision')) |
787 raise error.Abort(_(b'--all requires a branch revision')) |
764 if revs: |
788 if revs: |
765 raise error.Abort( |
789 raise error.Abort( |
766 _('--all is incompatible with a ' 'revision list') |
790 _(b'--all is incompatible with a ' b'revision list') |
767 ) |
791 ) |
768 |
792 |
769 opts = pycompat.byteskwargs(opts) |
793 opts = pycompat.byteskwargs(opts) |
770 checkopts(opts, revs) |
794 checkopts(opts, revs) |
771 |
795 |
772 if not opts.get('log'): |
796 if not opts.get(b'log'): |
773 # deprecated config: transplant.log |
797 # deprecated config: transplant.log |
774 opts['log'] = ui.config('transplant', 'log') |
798 opts[b'log'] = ui.config(b'transplant', b'log') |
775 if not opts.get('filter'): |
799 if not opts.get(b'filter'): |
776 # deprecated config: transplant.filter |
800 # deprecated config: transplant.filter |
777 opts['filter'] = ui.config('transplant', 'filter') |
801 opts[b'filter'] = ui.config(b'transplant', b'filter') |
778 |
802 |
779 tp = transplanter(ui, repo, opts) |
803 tp = transplanter(ui, repo, opts) |
780 |
804 |
781 p1 = repo.dirstate.p1() |
805 p1 = repo.dirstate.p1() |
782 if len(repo) > 0 and p1 == revlog.nullid: |
806 if len(repo) > 0 and p1 == revlog.nullid: |
783 raise error.Abort(_('no revision checked out')) |
807 raise error.Abort(_(b'no revision checked out')) |
784 if opts.get('continue'): |
808 if opts.get(b'continue'): |
785 if not tp.canresume(): |
809 if not tp.canresume(): |
786 raise error.Abort(_('no transplant to continue')) |
810 raise error.Abort(_(b'no transplant to continue')) |
787 elif opts.get('stop'): |
811 elif opts.get(b'stop'): |
788 if not tp.canresume(): |
812 if not tp.canresume(): |
789 raise error.Abort(_('no interrupted transplant found')) |
813 raise error.Abort(_(b'no interrupted transplant found')) |
790 return tp.stop(ui, repo) |
814 return tp.stop(ui, repo) |
791 else: |
815 else: |
792 cmdutil.checkunfinished(repo) |
816 cmdutil.checkunfinished(repo) |
793 cmdutil.bailifchanged(repo) |
817 cmdutil.bailifchanged(repo) |
794 |
818 |
795 sourcerepo = opts.get('source') |
819 sourcerepo = opts.get(b'source') |
796 if sourcerepo: |
820 if sourcerepo: |
797 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo)) |
821 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo)) |
798 heads = pycompat.maplist(peer.lookup, opts.get('branch', ())) |
822 heads = pycompat.maplist(peer.lookup, opts.get(b'branch', ())) |
799 target = set(heads) |
823 target = set(heads) |
800 for r in revs: |
824 for r in revs: |
801 try: |
825 try: |
802 target.add(peer.lookup(r)) |
826 target.add(peer.lookup(r)) |
803 except error.RepoError: |
827 except error.RepoError: |
805 source, csets, cleanupfn = bundlerepo.getremotechanges( |
829 source, csets, cleanupfn = bundlerepo.getremotechanges( |
806 ui, repo, peer, onlyheads=sorted(target), force=True |
830 ui, repo, peer, onlyheads=sorted(target), force=True |
807 ) |
831 ) |
808 else: |
832 else: |
809 source = repo |
833 source = repo |
810 heads = pycompat.maplist(source.lookup, opts.get('branch', ())) |
834 heads = pycompat.maplist(source.lookup, opts.get(b'branch', ())) |
811 cleanupfn = None |
835 cleanupfn = None |
812 |
836 |
813 try: |
837 try: |
814 if opts.get('continue'): |
838 if opts.get(b'continue'): |
815 tp.resume(repo, source, opts) |
839 tp.resume(repo, source, opts) |
816 return |
840 return |
817 |
841 |
818 tf = tp.transplantfilter(repo, source, p1) |
842 tf = tp.transplantfilter(repo, source, p1) |
819 if opts.get('prune'): |
843 if opts.get(b'prune'): |
820 prune = set( |
844 prune = set( |
821 source[r].node() |
845 source[r].node() |
822 for r in scmutil.revrange(source, opts.get('prune')) |
846 for r in scmutil.revrange(source, opts.get(b'prune')) |
823 ) |
847 ) |
824 matchfn = lambda x: tf(x) and x not in prune |
848 matchfn = lambda x: tf(x) and x not in prune |
825 else: |
849 else: |
826 matchfn = tf |
850 matchfn = tf |
827 merges = pycompat.maplist(source.lookup, opts.get('merge', ())) |
851 merges = pycompat.maplist(source.lookup, opts.get(b'merge', ())) |
828 revmap = {} |
852 revmap = {} |
829 if revs: |
853 if revs: |
830 for r in scmutil.revrange(source, revs): |
854 for r in scmutil.revrange(source, revs): |
831 revmap[int(r)] = source[r].node() |
855 revmap[int(r)] = source[r].node() |
832 elif opts.get('all') or not merges: |
856 elif opts.get(b'all') or not merges: |
833 if source != repo: |
857 if source != repo: |
834 alltransplants = incwalk(source, csets, match=matchfn) |
858 alltransplants = incwalk(source, csets, match=matchfn) |
835 else: |
859 else: |
836 alltransplants = transplantwalk( |
860 alltransplants = transplantwalk( |
837 source, p1, heads, match=matchfn |
861 source, p1, heads, match=matchfn |
838 ) |
862 ) |
839 if opts.get('all'): |
863 if opts.get(b'all'): |
840 revs = alltransplants |
864 revs = alltransplants |
841 else: |
865 else: |
842 revs, newmerges = browserevs(ui, source, alltransplants, opts) |
866 revs, newmerges = browserevs(ui, source, alltransplants, opts) |
843 merges.extend(newmerges) |
867 merges.extend(newmerges) |
844 for r in revs: |
868 for r in revs: |
861 |
885 |
862 |
886 |
863 revsetpredicate = registrar.revsetpredicate() |
887 revsetpredicate = registrar.revsetpredicate() |
864 |
888 |
865 |
889 |
866 @revsetpredicate('transplanted([set])') |
890 @revsetpredicate(b'transplanted([set])') |
867 def revsettransplanted(repo, subset, x): |
891 def revsettransplanted(repo, subset, x): |
868 """Transplanted changesets in set, or all transplanted changesets. |
892 """Transplanted changesets in set, or all transplanted changesets. |
869 """ |
893 """ |
870 if x: |
894 if x: |
871 s = revset.getset(repo, subset, x) |
895 s = revset.getset(repo, subset, x) |
872 else: |
896 else: |
873 s = subset |
897 s = subset |
874 return smartset.baseset( |
898 return smartset.baseset( |
875 [r for r in s if repo[r].extra().get('transplant_source')] |
899 [r for r in s if repo[r].extra().get(b'transplant_source')] |
876 ) |
900 ) |
877 |
901 |
878 |
902 |
879 templatekeyword = registrar.templatekeyword() |
903 templatekeyword = registrar.templatekeyword() |
880 |
904 |
881 |
905 |
882 @templatekeyword('transplanted', requires={'ctx'}) |
906 @templatekeyword(b'transplanted', requires={b'ctx'}) |
883 def kwtransplanted(context, mapping): |
907 def kwtransplanted(context, mapping): |
884 """String. The node identifier of the transplanted |
908 """String. The node identifier of the transplanted |
885 changeset if any.""" |
909 changeset if any.""" |
886 ctx = context.resource(mapping, 'ctx') |
910 ctx = context.resource(mapping, b'ctx') |
887 n = ctx.extra().get('transplant_source') |
911 n = ctx.extra().get(b'transplant_source') |
888 return n and nodemod.hex(n) or '' |
912 return n and nodemod.hex(n) or b'' |
889 |
913 |
890 |
914 |
891 def extsetup(ui): |
915 def extsetup(ui): |
892 statemod.addunfinished( |
916 statemod.addunfinished( |
893 'transplant', |
917 b'transplant', |
894 fname='transplant/journal', |
918 fname=b'transplant/journal', |
895 clearable=True, |
919 clearable=True, |
896 continuefunc=continuecmd, |
920 continuefunc=continuecmd, |
897 statushint=_( |
921 statushint=_( |
898 'To continue: hg transplant --continue\n' |
922 b'To continue: hg transplant --continue\n' |
899 'To stop: hg transplant --stop' |
923 b'To stop: hg transplant --stop' |
900 ), |
924 ), |
901 cmdhint=_("use 'hg transplant --continue' or 'hg transplant --stop'"), |
925 cmdhint=_(b"use 'hg transplant --continue' or 'hg transplant --stop'"), |
902 ) |
926 ) |
903 |
927 |
904 |
928 |
905 # tell hggettext to extract docstrings from these functions: |
929 # tell hggettext to extract docstrings from these functions: |
906 i18nfunctions = [revsettransplanted, kwtransplanted] |
930 i18nfunctions = [revsettransplanted, kwtransplanted] |