177 urlreq = util.urlreq |
177 urlreq = util.urlreq |
178 |
178 |
179 _pack = struct.pack |
179 _pack = struct.pack |
180 _unpack = struct.unpack |
180 _unpack = struct.unpack |
181 |
181 |
182 _fstreamparamsize = '>i' |
182 _fstreamparamsize = b'>i' |
183 _fpartheadersize = '>i' |
183 _fpartheadersize = b'>i' |
184 _fparttypesize = '>B' |
184 _fparttypesize = b'>B' |
185 _fpartid = '>I' |
185 _fpartid = b'>I' |
186 _fpayloadsize = '>i' |
186 _fpayloadsize = b'>i' |
187 _fpartparamcount = '>BB' |
187 _fpartparamcount = b'>BB' |
188 |
188 |
189 preferedchunksize = 32768 |
189 preferedchunksize = 32768 |
190 |
190 |
191 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]') |
191 _parttypeforbidden = re.compile(b'[^a-zA-Z0-9_:-]') |
192 |
192 |
193 |
193 |
194 def outdebug(ui, message): |
194 def outdebug(ui, message): |
195 """debug regarding output stream (bundling)""" |
195 """debug regarding output stream (bundling)""" |
196 if ui.configbool('devel', 'bundle2.debug'): |
196 if ui.configbool(b'devel', b'bundle2.debug'): |
197 ui.debug('bundle2-output: %s\n' % message) |
197 ui.debug(b'bundle2-output: %s\n' % message) |
198 |
198 |
199 |
199 |
200 def indebug(ui, message): |
200 def indebug(ui, message): |
201 """debug on input stream (unbundling)""" |
201 """debug on input stream (unbundling)""" |
202 if ui.configbool('devel', 'bundle2.debug'): |
202 if ui.configbool(b'devel', b'bundle2.debug'): |
203 ui.debug('bundle2-input: %s\n' % message) |
203 ui.debug(b'bundle2-input: %s\n' % message) |
204 |
204 |
205 |
205 |
206 def validateparttype(parttype): |
206 def validateparttype(parttype): |
207 """raise ValueError if a parttype contains invalid character""" |
207 """raise ValueError if a parttype contains invalid character""" |
208 if _parttypeforbidden.search(parttype): |
208 if _parttypeforbidden.search(parttype): |
356 |
356 |
357 |
357 |
358 def applybundle(repo, unbundler, tr, source, url=None, **kwargs): |
358 def applybundle(repo, unbundler, tr, source, url=None, **kwargs): |
359 # transform me into unbundler.apply() as soon as the freeze is lifted |
359 # transform me into unbundler.apply() as soon as the freeze is lifted |
360 if isinstance(unbundler, unbundle20): |
360 if isinstance(unbundler, unbundle20): |
361 tr.hookargs['bundle2'] = '1' |
361 tr.hookargs[b'bundle2'] = b'1' |
362 if source is not None and 'source' not in tr.hookargs: |
362 if source is not None and b'source' not in tr.hookargs: |
363 tr.hookargs['source'] = source |
363 tr.hookargs[b'source'] = source |
364 if url is not None and 'url' not in tr.hookargs: |
364 if url is not None and b'url' not in tr.hookargs: |
365 tr.hookargs['url'] = url |
365 tr.hookargs[b'url'] = url |
366 return processbundle(repo, unbundler, lambda: tr, source=source) |
366 return processbundle(repo, unbundler, lambda: tr, source=source) |
367 else: |
367 else: |
368 # the transactiongetter won't be used, but we might as well set it |
368 # the transactiongetter won't be used, but we might as well set it |
369 op = bundleoperation(repo, lambda: tr, source=source) |
369 op = bundleoperation(repo, lambda: tr, source=source) |
370 _processchangegroup(op, unbundler, tr, source, url, **kwargs) |
370 _processchangegroup(op, unbundler, tr, source, url, **kwargs) |
485 _processpart(op, part) |
485 _processpart(op, part) |
486 |
486 |
487 |
487 |
488 def _processchangegroup(op, cg, tr, source, url, **kwargs): |
488 def _processchangegroup(op, cg, tr, source, url, **kwargs): |
489 ret = cg.apply(op.repo, tr, source, url, **kwargs) |
489 ret = cg.apply(op.repo, tr, source, url, **kwargs) |
490 op.records.add('changegroup', {'return': ret,}) |
490 op.records.add(b'changegroup', {b'return': ret,}) |
491 return ret |
491 return ret |
492 |
492 |
493 |
493 |
494 def _gethandler(op, part): |
494 def _gethandler(op, part): |
495 status = 'unknown' # used by debug output |
495 status = b'unknown' # used by debug output |
496 try: |
496 try: |
497 handler = parthandlermapping.get(part.type) |
497 handler = parthandlermapping.get(part.type) |
498 if handler is None: |
498 if handler is None: |
499 status = 'unsupported-type' |
499 status = b'unsupported-type' |
500 raise error.BundleUnknownFeatureError(parttype=part.type) |
500 raise error.BundleUnknownFeatureError(parttype=part.type) |
501 indebug(op.ui, 'found a handler for part %s' % part.type) |
501 indebug(op.ui, b'found a handler for part %s' % part.type) |
502 unknownparams = part.mandatorykeys - handler.params |
502 unknownparams = part.mandatorykeys - handler.params |
503 if unknownparams: |
503 if unknownparams: |
504 unknownparams = list(unknownparams) |
504 unknownparams = list(unknownparams) |
505 unknownparams.sort() |
505 unknownparams.sort() |
506 status = 'unsupported-params (%s)' % ', '.join(unknownparams) |
506 status = b'unsupported-params (%s)' % b', '.join(unknownparams) |
507 raise error.BundleUnknownFeatureError( |
507 raise error.BundleUnknownFeatureError( |
508 parttype=part.type, params=unknownparams |
508 parttype=part.type, params=unknownparams |
509 ) |
509 ) |
510 status = 'supported' |
510 status = b'supported' |
511 except error.BundleUnknownFeatureError as exc: |
511 except error.BundleUnknownFeatureError as exc: |
512 if part.mandatory: # mandatory parts |
512 if part.mandatory: # mandatory parts |
513 raise |
513 raise |
514 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc) |
514 indebug(op.ui, b'ignoring unsupported advisory part %s' % exc) |
515 return # skip to part processing |
515 return # skip to part processing |
516 finally: |
516 finally: |
517 if op.ui.debugflag: |
517 if op.ui.debugflag: |
518 msg = ['bundle2-input-part: "%s"' % part.type] |
518 msg = [b'bundle2-input-part: "%s"' % part.type] |
519 if not part.mandatory: |
519 if not part.mandatory: |
520 msg.append(' (advisory)') |
520 msg.append(b' (advisory)') |
521 nbmp = len(part.mandatorykeys) |
521 nbmp = len(part.mandatorykeys) |
522 nbap = len(part.params) - nbmp |
522 nbap = len(part.params) - nbmp |
523 if nbmp or nbap: |
523 if nbmp or nbap: |
524 msg.append(' (params:') |
524 msg.append(b' (params:') |
525 if nbmp: |
525 if nbmp: |
526 msg.append(' %i mandatory' % nbmp) |
526 msg.append(b' %i mandatory' % nbmp) |
527 if nbap: |
527 if nbap: |
528 msg.append(' %i advisory' % nbmp) |
528 msg.append(b' %i advisory' % nbmp) |
529 msg.append(')') |
529 msg.append(b')') |
530 msg.append(' %s\n' % status) |
530 msg.append(b' %s\n' % status) |
531 op.ui.debug(''.join(msg)) |
531 op.ui.debug(b''.join(msg)) |
532 |
532 |
533 return handler |
533 return handler |
534 |
534 |
535 |
535 |
536 def _processpart(op, part): |
536 def _processpart(op, part): |
592 for ca in sorted(caps): |
592 for ca in sorted(caps): |
593 vals = caps[ca] |
593 vals = caps[ca] |
594 ca = urlreq.quote(ca) |
594 ca = urlreq.quote(ca) |
595 vals = [urlreq.quote(v) for v in vals] |
595 vals = [urlreq.quote(v) for v in vals] |
596 if vals: |
596 if vals: |
597 ca = "%s=%s" % (ca, ','.join(vals)) |
597 ca = b"%s=%s" % (ca, b','.join(vals)) |
598 chunks.append(ca) |
598 chunks.append(ca) |
599 return '\n'.join(chunks) |
599 return b'\n'.join(chunks) |
600 |
600 |
601 |
601 |
602 bundletypes = { |
602 bundletypes = { |
603 "": ("", 'UN'), # only when using unbundle on ssh and old http servers |
603 b"": (b"", b'UN'), # only when using unbundle on ssh and old http servers |
604 # since the unification ssh accepts a header but there |
604 # since the unification ssh accepts a header but there |
605 # is no capability signaling it. |
605 # is no capability signaling it. |
606 "HG20": (), # special-cased below |
606 b"HG20": (), # special-cased below |
607 "HG10UN": ("HG10UN", 'UN'), |
607 b"HG10UN": (b"HG10UN", b'UN'), |
608 "HG10BZ": ("HG10", 'BZ'), |
608 b"HG10BZ": (b"HG10", b'BZ'), |
609 "HG10GZ": ("HG10GZ", 'GZ'), |
609 b"HG10GZ": (b"HG10GZ", b'GZ'), |
610 } |
610 } |
611 |
611 |
612 # hgweb uses this list to communicate its preferred type |
612 # hgweb uses this list to communicate its preferred type |
613 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN'] |
613 bundlepriority = [b'HG10GZ', b'HG10BZ', b'HG10UN'] |
614 |
614 |
615 |
615 |
616 class bundle20(object): |
616 class bundle20(object): |
617 """represent an outgoing bundle2 container |
617 """represent an outgoing bundle2 container |
618 |
618 |
619 Use the `addparam` method to add stream level parameter. and `newpart` to |
619 Use the `addparam` method to add stream level parameter. and `newpart` to |
620 populate it. Then call `getchunks` to retrieve all the binary chunks of |
620 populate it. Then call `getchunks` to retrieve all the binary chunks of |
621 data that compose the bundle2 container.""" |
621 data that compose the bundle2 container.""" |
622 |
622 |
623 _magicstring = 'HG20' |
623 _magicstring = b'HG20' |
624 |
624 |
625 def __init__(self, ui, capabilities=()): |
625 def __init__(self, ui, capabilities=()): |
626 self.ui = ui |
626 self.ui = ui |
627 self._params = [] |
627 self._params = [] |
628 self._parts = [] |
628 self._parts = [] |
629 self.capabilities = dict(capabilities) |
629 self.capabilities = dict(capabilities) |
630 self._compengine = util.compengines.forbundletype('UN') |
630 self._compengine = util.compengines.forbundletype(b'UN') |
631 self._compopts = None |
631 self._compopts = None |
632 # If compression is being handled by a consumer of the raw |
632 # If compression is being handled by a consumer of the raw |
633 # data (e.g. the wire protocol), unsetting this flag tells |
633 # data (e.g. the wire protocol), unsetting this flag tells |
634 # consumers that the bundle is best left uncompressed. |
634 # consumers that the bundle is best left uncompressed. |
635 self.prefercompressed = True |
635 self.prefercompressed = True |
636 |
636 |
637 def setcompression(self, alg, compopts=None): |
637 def setcompression(self, alg, compopts=None): |
638 """setup core part compression to <alg>""" |
638 """setup core part compression to <alg>""" |
639 if alg in (None, 'UN'): |
639 if alg in (None, b'UN'): |
640 return |
640 return |
641 assert not any(n.lower() == 'compression' for n, v in self._params) |
641 assert not any(n.lower() == b'compression' for n, v in self._params) |
642 self.addparam('Compression', alg) |
642 self.addparam(b'Compression', alg) |
643 self._compengine = util.compengines.forbundletype(alg) |
643 self._compengine = util.compengines.forbundletype(alg) |
644 self._compopts = compopts |
644 self._compopts = compopts |
645 |
645 |
646 @property |
646 @property |
647 def nbparts(self): |
647 def nbparts(self): |
705 blocks = [] |
705 blocks = [] |
706 for par, value in self._params: |
706 for par, value in self._params: |
707 par = urlreq.quote(par) |
707 par = urlreq.quote(par) |
708 if value is not None: |
708 if value is not None: |
709 value = urlreq.quote(value) |
709 value = urlreq.quote(value) |
710 par = '%s=%s' % (par, value) |
710 par = b'%s=%s' % (par, value) |
711 blocks.append(par) |
711 blocks.append(par) |
712 return ' '.join(blocks) |
712 return b' '.join(blocks) |
713 |
713 |
714 def _getcorechunk(self): |
714 def _getcorechunk(self): |
715 """yield chunk for the core part of the bundle |
715 """yield chunk for the core part of the bundle |
716 |
716 |
717 (all but headers and parameters)""" |
717 (all but headers and parameters)""" |
718 outdebug(self.ui, 'start of parts') |
718 outdebug(self.ui, b'start of parts') |
719 for part in self._parts: |
719 for part in self._parts: |
720 outdebug(self.ui, 'bundle part: "%s"' % part.type) |
720 outdebug(self.ui, b'bundle part: "%s"' % part.type) |
721 for chunk in part.getchunks(ui=self.ui): |
721 for chunk in part.getchunks(ui=self.ui): |
722 yield chunk |
722 yield chunk |
723 outdebug(self.ui, 'end of bundle') |
723 outdebug(self.ui, b'end of bundle') |
724 yield _pack(_fpartheadersize, 0) |
724 yield _pack(_fpartheadersize, 0) |
725 |
725 |
726 def salvageoutput(self): |
726 def salvageoutput(self): |
727 """return a list with a copy of all output parts in the bundle |
727 """return a list with a copy of all output parts in the bundle |
728 |
728 |
729 This is meant to be used during error handling to make sure we preserve |
729 This is meant to be used during error handling to make sure we preserve |
730 server output""" |
730 server output""" |
731 salvaged = [] |
731 salvaged = [] |
732 for part in self._parts: |
732 for part in self._parts: |
733 if part.type.startswith('output'): |
733 if part.type.startswith(b'output'): |
734 salvaged.append(part.copy()) |
734 salvaged.append(part.copy()) |
735 return salvaged |
735 return salvaged |
736 |
736 |
737 |
737 |
738 class unpackermixin(object): |
738 class unpackermixin(object): |
766 def getunbundler(ui, fp, magicstring=None): |
766 def getunbundler(ui, fp, magicstring=None): |
767 """return a valid unbundler object for a given magicstring""" |
767 """return a valid unbundler object for a given magicstring""" |
768 if magicstring is None: |
768 if magicstring is None: |
769 magicstring = changegroup.readexactly(fp, 4) |
769 magicstring = changegroup.readexactly(fp, 4) |
770 magic, version = magicstring[0:2], magicstring[2:4] |
770 magic, version = magicstring[0:2], magicstring[2:4] |
771 if magic != 'HG': |
771 if magic != b'HG': |
772 ui.debug( |
772 ui.debug( |
773 "error: invalid magic: %r (version %r), should be 'HG'\n" |
773 b"error: invalid magic: %r (version %r), should be 'HG'\n" |
774 % (magic, version) |
774 % (magic, version) |
775 ) |
775 ) |
776 raise error.Abort(_('not a Mercurial bundle')) |
776 raise error.Abort(_(b'not a Mercurial bundle')) |
777 unbundlerclass = formatmap.get(version) |
777 unbundlerclass = formatmap.get(version) |
778 if unbundlerclass is None: |
778 if unbundlerclass is None: |
779 raise error.Abort(_('unknown bundle version %s') % version) |
779 raise error.Abort(_(b'unknown bundle version %s') % version) |
780 unbundler = unbundlerclass(ui, fp) |
780 unbundler = unbundlerclass(ui, fp) |
781 indebug(ui, 'start processing of %s stream' % magicstring) |
781 indebug(ui, b'start processing of %s stream' % magicstring) |
782 return unbundler |
782 return unbundler |
783 |
783 |
784 |
784 |
785 class unbundle20(unpackermixin): |
785 class unbundle20(unpackermixin): |
786 """interpret a bundle2 stream |
786 """interpret a bundle2 stream |
787 |
787 |
788 This class is fed with a binary stream and yields parts through its |
788 This class is fed with a binary stream and yields parts through its |
789 `iterparts` methods.""" |
789 `iterparts` methods.""" |
790 |
790 |
791 _magicstring = 'HG20' |
791 _magicstring = b'HG20' |
792 |
792 |
793 def __init__(self, ui, fp): |
793 def __init__(self, ui, fp): |
794 """If header is specified, we do not read it out of the stream.""" |
794 """If header is specified, we do not read it out of the stream.""" |
795 self.ui = ui |
795 self.ui = ui |
796 self._compengine = util.compengines.forbundletype('UN') |
796 self._compengine = util.compengines.forbundletype(b'UN') |
797 self._compressed = None |
797 self._compressed = None |
798 super(unbundle20, self).__init__(fp) |
798 super(unbundle20, self).__init__(fp) |
799 |
799 |
800 @util.propertycache |
800 @util.propertycache |
801 def params(self): |
801 def params(self): |
802 """dictionary of stream level parameters""" |
802 """dictionary of stream level parameters""" |
803 indebug(self.ui, 'reading bundle2 stream parameters') |
803 indebug(self.ui, b'reading bundle2 stream parameters') |
804 params = {} |
804 params = {} |
805 paramssize = self._unpack(_fstreamparamsize)[0] |
805 paramssize = self._unpack(_fstreamparamsize)[0] |
806 if paramssize < 0: |
806 if paramssize < 0: |
807 raise error.BundleValueError( |
807 raise error.BundleValueError( |
808 'negative bundle param size: %i' % paramssize |
808 b'negative bundle param size: %i' % paramssize |
809 ) |
809 ) |
810 if paramssize: |
810 if paramssize: |
811 params = self._readexact(paramssize) |
811 params = self._readexact(paramssize) |
812 params = self._processallparams(params) |
812 params = self._processallparams(params) |
813 return params |
813 return params |
814 |
814 |
815 def _processallparams(self, paramsblock): |
815 def _processallparams(self, paramsblock): |
816 """""" |
816 """""" |
817 params = util.sortdict() |
817 params = util.sortdict() |
818 for p in paramsblock.split(' '): |
818 for p in paramsblock.split(b' '): |
819 p = p.split('=', 1) |
819 p = p.split(b'=', 1) |
820 p = [urlreq.unquote(i) for i in p] |
820 p = [urlreq.unquote(i) for i in p] |
821 if len(p) < 2: |
821 if len(p) < 2: |
822 p.append(None) |
822 p.append(None) |
823 self._processparam(*p) |
823 self._processparam(*p) |
824 params[p[0]] = p[1] |
824 params[p[0]] = p[1] |
855 have no way to know then the reply end, relying on the bundle to be |
855 have no way to know then the reply end, relying on the bundle to be |
856 interpreted to know its end. This is terrible and we are sorry, but we |
856 interpreted to know its end. This is terrible and we are sorry, but we |
857 needed to move forward to get general delta enabled. |
857 needed to move forward to get general delta enabled. |
858 """ |
858 """ |
859 yield self._magicstring |
859 yield self._magicstring |
860 assert 'params' not in vars(self) |
860 assert b'params' not in vars(self) |
861 paramssize = self._unpack(_fstreamparamsize)[0] |
861 paramssize = self._unpack(_fstreamparamsize)[0] |
862 if paramssize < 0: |
862 if paramssize < 0: |
863 raise error.BundleValueError( |
863 raise error.BundleValueError( |
864 'negative bundle param size: %i' % paramssize |
864 b'negative bundle param size: %i' % paramssize |
865 ) |
865 ) |
866 if paramssize: |
866 if paramssize: |
867 params = self._readexact(paramssize) |
867 params = self._readexact(paramssize) |
868 self._processallparams(params) |
868 self._processallparams(params) |
869 # The payload itself is decompressed below, so drop |
869 # The payload itself is decompressed below, so drop |
870 # the compression parameter passed down to compensate. |
870 # the compression parameter passed down to compensate. |
871 outparams = [] |
871 outparams = [] |
872 for p in params.split(' '): |
872 for p in params.split(b' '): |
873 k, v = p.split('=', 1) |
873 k, v = p.split(b'=', 1) |
874 if k.lower() != 'compression': |
874 if k.lower() != b'compression': |
875 outparams.append(p) |
875 outparams.append(p) |
876 outparams = ' '.join(outparams) |
876 outparams = b' '.join(outparams) |
877 yield _pack(_fstreamparamsize, len(outparams)) |
877 yield _pack(_fstreamparamsize, len(outparams)) |
878 yield outparams |
878 yield outparams |
879 else: |
879 else: |
880 yield _pack(_fstreamparamsize, paramssize) |
880 yield _pack(_fstreamparamsize, paramssize) |
881 # From there, payload might need to be decompressed |
881 # From there, payload might need to be decompressed |
892 emptycount += 1 |
892 emptycount += 1 |
893 continue |
893 continue |
894 if size == flaginterrupt: |
894 if size == flaginterrupt: |
895 continue |
895 continue |
896 elif size < 0: |
896 elif size < 0: |
897 raise error.BundleValueError('negative chunk size: %i') |
897 raise error.BundleValueError(b'negative chunk size: %i') |
898 yield self._readexact(size) |
898 yield self._readexact(size) |
899 |
899 |
900 def iterparts(self, seekable=False): |
900 def iterparts(self, seekable=False): |
901 """yield all parts contained in the stream""" |
901 """yield all parts contained in the stream""" |
902 cls = seekableunbundlepart if seekable else unbundlepart |
902 cls = seekableunbundlepart if seekable else unbundlepart |
903 # make sure param have been loaded |
903 # make sure param have been loaded |
904 self.params |
904 self.params |
905 # From there, payload need to be decompressed |
905 # From there, payload need to be decompressed |
906 self._fp = self._compengine.decompressorreader(self._fp) |
906 self._fp = self._compengine.decompressorreader(self._fp) |
907 indebug(self.ui, 'start extraction of bundle2 parts') |
907 indebug(self.ui, b'start extraction of bundle2 parts') |
908 headerblock = self._readpartheader() |
908 headerblock = self._readpartheader() |
909 while headerblock is not None: |
909 while headerblock is not None: |
910 part = cls(self.ui, headerblock, self._fp) |
910 part = cls(self.ui, headerblock, self._fp) |
911 yield part |
911 yield part |
912 # Ensure part is fully consumed so we can start reading the next |
912 # Ensure part is fully consumed so we can start reading the next |
913 # part. |
913 # part. |
914 part.consume() |
914 part.consume() |
915 |
915 |
916 headerblock = self._readpartheader() |
916 headerblock = self._readpartheader() |
917 indebug(self.ui, 'end of bundle2 stream') |
917 indebug(self.ui, b'end of bundle2 stream') |
918 |
918 |
919 def _readpartheader(self): |
919 def _readpartheader(self): |
920 """reads a part header size and return the bytes blob |
920 """reads a part header size and return the bytes blob |
921 |
921 |
922 returns None if empty""" |
922 returns None if empty""" |
923 headersize = self._unpack(_fpartheadersize)[0] |
923 headersize = self._unpack(_fpartheadersize)[0] |
924 if headersize < 0: |
924 if headersize < 0: |
925 raise error.BundleValueError( |
925 raise error.BundleValueError( |
926 'negative part header size: %i' % headersize |
926 b'negative part header size: %i' % headersize |
927 ) |
927 ) |
928 indebug(self.ui, 'part header size: %i' % headersize) |
928 indebug(self.ui, b'part header size: %i' % headersize) |
929 if headersize: |
929 if headersize: |
930 return self._readexact(headersize) |
930 return self._readexact(headersize) |
931 return None |
931 return None |
932 |
932 |
933 def compressed(self): |
933 def compressed(self): |
934 self.params # load params |
934 self.params # load params |
935 return self._compressed |
935 return self._compressed |
936 |
936 |
937 def close(self): |
937 def close(self): |
938 """close underlying file""" |
938 """close underlying file""" |
939 if util.safehasattr(self._fp, 'close'): |
939 if util.safehasattr(self._fp, b'close'): |
940 return self._fp.close() |
940 return self._fp.close() |
941 |
941 |
942 |
942 |
943 formatmap = {'20': unbundle20} |
943 formatmap = {b'20': unbundle20} |
944 |
944 |
945 b2streamparamsmap = {} |
945 b2streamparamsmap = {} |
946 |
946 |
947 |
947 |
948 def b2streamparamhandler(name): |
948 def b2streamparamhandler(name): |
1052 @property |
1052 @property |
1053 def advisoryparams(self): |
1053 def advisoryparams(self): |
1054 # make it an immutable tuple to force people through ``addparam`` |
1054 # make it an immutable tuple to force people through ``addparam`` |
1055 return tuple(self._advisoryparams) |
1055 return tuple(self._advisoryparams) |
1056 |
1056 |
1057 def addparam(self, name, value='', mandatory=True): |
1057 def addparam(self, name, value=b'', mandatory=True): |
1058 """add a parameter to the part |
1058 """add a parameter to the part |
1059 |
1059 |
1060 If 'mandatory' is set to True, the remote handler must claim support |
1060 If 'mandatory' is set to True, the remote handler must claim support |
1061 for this parameter or the unbundling will be aborted. |
1061 for this parameter or the unbundling will be aborted. |
1062 |
1062 |
1063 The 'name' and 'value' cannot exceed 255 bytes each. |
1063 The 'name' and 'value' cannot exceed 255 bytes each. |
1064 """ |
1064 """ |
1065 if self._generated is not None: |
1065 if self._generated is not None: |
1066 raise error.ReadOnlyPartError('part is being generated') |
1066 raise error.ReadOnlyPartError(b'part is being generated') |
1067 if name in self._seenparams: |
1067 if name in self._seenparams: |
1068 raise ValueError('duplicated params: %s' % name) |
1068 raise ValueError(b'duplicated params: %s' % name) |
1069 self._seenparams.add(name) |
1069 self._seenparams.add(name) |
1070 params = self._advisoryparams |
1070 params = self._advisoryparams |
1071 if mandatory: |
1071 if mandatory: |
1072 params = self._mandatoryparams |
1072 params = self._mandatoryparams |
1073 params.append((name, value)) |
1073 params.append((name, value)) |
1074 |
1074 |
1075 # methods used to generates the bundle2 stream |
1075 # methods used to generates the bundle2 stream |
1076 def getchunks(self, ui): |
1076 def getchunks(self, ui): |
1077 if self._generated is not None: |
1077 if self._generated is not None: |
1078 raise error.ProgrammingError('part can only be consumed once') |
1078 raise error.ProgrammingError(b'part can only be consumed once') |
1079 self._generated = False |
1079 self._generated = False |
1080 |
1080 |
1081 if ui.debugflag: |
1081 if ui.debugflag: |
1082 msg = ['bundle2-output-part: "%s"' % self.type] |
1082 msg = [b'bundle2-output-part: "%s"' % self.type] |
1083 if not self.mandatory: |
1083 if not self.mandatory: |
1084 msg.append(' (advisory)') |
1084 msg.append(b' (advisory)') |
1085 nbmp = len(self.mandatoryparams) |
1085 nbmp = len(self.mandatoryparams) |
1086 nbap = len(self.advisoryparams) |
1086 nbap = len(self.advisoryparams) |
1087 if nbmp or nbap: |
1087 if nbmp or nbap: |
1088 msg.append(' (params:') |
1088 msg.append(b' (params:') |
1089 if nbmp: |
1089 if nbmp: |
1090 msg.append(' %i mandatory' % nbmp) |
1090 msg.append(b' %i mandatory' % nbmp) |
1091 if nbap: |
1091 if nbap: |
1092 msg.append(' %i advisory' % nbmp) |
1092 msg.append(b' %i advisory' % nbmp) |
1093 msg.append(')') |
1093 msg.append(b')') |
1094 if not self.data: |
1094 if not self.data: |
1095 msg.append(' empty payload') |
1095 msg.append(b' empty payload') |
1096 elif util.safehasattr(self.data, 'next') or util.safehasattr( |
1096 elif util.safehasattr(self.data, b'next') or util.safehasattr( |
1097 self.data, '__next__' |
1097 self.data, b'__next__' |
1098 ): |
1098 ): |
1099 msg.append(' streamed payload') |
1099 msg.append(b' streamed payload') |
1100 else: |
1100 else: |
1101 msg.append(' %i bytes payload' % len(self.data)) |
1101 msg.append(b' %i bytes payload' % len(self.data)) |
1102 msg.append('\n') |
1102 msg.append(b'\n') |
1103 ui.debug(''.join(msg)) |
1103 ui.debug(b''.join(msg)) |
1104 |
1104 |
1105 #### header |
1105 #### header |
1106 if self.mandatory: |
1106 if self.mandatory: |
1107 parttype = self.type.upper() |
1107 parttype = self.type.upper() |
1108 else: |
1108 else: |
1109 parttype = self.type.lower() |
1109 parttype = self.type.lower() |
1110 outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype)) |
1110 outdebug(ui, b'part %s: "%s"' % (pycompat.bytestr(self.id), parttype)) |
1111 ## parttype |
1111 ## parttype |
1112 header = [ |
1112 header = [ |
1113 _pack(_fparttypesize, len(parttype)), |
1113 _pack(_fparttypesize, len(parttype)), |
1114 parttype, |
1114 parttype, |
1115 _pack(_fpartid, self.id), |
1115 _pack(_fpartid, self.id), |
1136 for key, value in advpar: |
1136 for key, value in advpar: |
1137 header.append(key) |
1137 header.append(key) |
1138 header.append(value) |
1138 header.append(value) |
1139 ## finalize header |
1139 ## finalize header |
1140 try: |
1140 try: |
1141 headerchunk = ''.join(header) |
1141 headerchunk = b''.join(header) |
1142 except TypeError: |
1142 except TypeError: |
1143 raise TypeError( |
1143 raise TypeError( |
1144 r'Found a non-bytes trying to ' |
1144 r'Found a non-bytes trying to ' |
1145 r'build bundle part header: %r' % header |
1145 r'build bundle part header: %r' % header |
1146 ) |
1146 ) |
1147 outdebug(ui, 'header chunk size: %i' % len(headerchunk)) |
1147 outdebug(ui, b'header chunk size: %i' % len(headerchunk)) |
1148 yield _pack(_fpartheadersize, len(headerchunk)) |
1148 yield _pack(_fpartheadersize, len(headerchunk)) |
1149 yield headerchunk |
1149 yield headerchunk |
1150 ## payload |
1150 ## payload |
1151 try: |
1151 try: |
1152 for chunk in self._payloadchunks(): |
1152 for chunk in self._payloadchunks(): |
1153 outdebug(ui, 'payload chunk size: %i' % len(chunk)) |
1153 outdebug(ui, b'payload chunk size: %i' % len(chunk)) |
1154 yield _pack(_fpayloadsize, len(chunk)) |
1154 yield _pack(_fpayloadsize, len(chunk)) |
1155 yield chunk |
1155 yield chunk |
1156 except GeneratorExit: |
1156 except GeneratorExit: |
1157 # GeneratorExit means that nobody is listening for our |
1157 # GeneratorExit means that nobody is listening for our |
1158 # results anyway, so just bail quickly rather than trying |
1158 # results anyway, so just bail quickly rather than trying |
1159 # to produce an error part. |
1159 # to produce an error part. |
1160 ui.debug('bundle2-generatorexit\n') |
1160 ui.debug(b'bundle2-generatorexit\n') |
1161 raise |
1161 raise |
1162 except BaseException as exc: |
1162 except BaseException as exc: |
1163 bexc = stringutil.forcebytestr(exc) |
1163 bexc = stringutil.forcebytestr(exc) |
1164 # backup exception data for later |
1164 # backup exception data for later |
1165 ui.debug( |
1165 ui.debug( |
1166 'bundle2-input-stream-interrupt: encoding exception %s' % bexc |
1166 b'bundle2-input-stream-interrupt: encoding exception %s' % bexc |
1167 ) |
1167 ) |
1168 tb = sys.exc_info()[2] |
1168 tb = sys.exc_info()[2] |
1169 msg = 'unexpected error: %s' % bexc |
1169 msg = b'unexpected error: %s' % bexc |
1170 interpart = bundlepart( |
1170 interpart = bundlepart( |
1171 'error:abort', [('message', msg)], mandatory=False |
1171 b'error:abort', [(b'message', msg)], mandatory=False |
1172 ) |
1172 ) |
1173 interpart.id = 0 |
1173 interpart.id = 0 |
1174 yield _pack(_fpayloadsize, -1) |
1174 yield _pack(_fpayloadsize, -1) |
1175 for chunk in interpart.getchunks(ui=ui): |
1175 for chunk in interpart.getchunks(ui=ui): |
1176 yield chunk |
1176 yield chunk |
1177 outdebug(ui, 'closing payload chunk') |
1177 outdebug(ui, b'closing payload chunk') |
1178 # abort current part payload |
1178 # abort current part payload |
1179 yield _pack(_fpayloadsize, 0) |
1179 yield _pack(_fpayloadsize, 0) |
1180 pycompat.raisewithtb(exc, tb) |
1180 pycompat.raisewithtb(exc, tb) |
1181 # end of payload |
1181 # end of payload |
1182 outdebug(ui, 'closing payload chunk') |
1182 outdebug(ui, b'closing payload chunk') |
1183 yield _pack(_fpayloadsize, 0) |
1183 yield _pack(_fpayloadsize, 0) |
1184 self._generated = True |
1184 self._generated = True |
1185 |
1185 |
1186 def _payloadchunks(self): |
1186 def _payloadchunks(self): |
1187 """yield chunks of a the part payload |
1187 """yield chunks of a the part payload |
1188 |
1188 |
1189 Exists to handle the different methods to provide data to a part.""" |
1189 Exists to handle the different methods to provide data to a part.""" |
1190 # we only support fixed size data now. |
1190 # we only support fixed size data now. |
1191 # This will be improved in the future. |
1191 # This will be improved in the future. |
1192 if util.safehasattr(self.data, 'next') or util.safehasattr( |
1192 if util.safehasattr(self.data, b'next') or util.safehasattr( |
1193 self.data, '__next__' |
1193 self.data, b'__next__' |
1194 ): |
1194 ): |
1195 buff = util.chunkbuffer(self.data) |
1195 buff = util.chunkbuffer(self.data) |
1196 chunk = buff.read(preferedchunksize) |
1196 chunk = buff.read(preferedchunksize) |
1197 while chunk: |
1197 while chunk: |
1198 yield chunk |
1198 yield chunk |
1267 self.reply = None |
1267 self.reply = None |
1268 self.captureoutput = False |
1268 self.captureoutput = False |
1269 |
1269 |
1270 @property |
1270 @property |
1271 def repo(self): |
1271 def repo(self): |
1272 raise error.ProgrammingError('no repo access from stream interruption') |
1272 raise error.ProgrammingError(b'no repo access from stream interruption') |
1273 |
1273 |
1274 def gettransaction(self): |
1274 def gettransaction(self): |
1275 raise TransactionUnavailable('no repo access from stream interruption') |
1275 raise TransactionUnavailable(b'no repo access from stream interruption') |
1276 |
1276 |
1277 |
1277 |
1278 def decodepayloadchunks(ui, fh): |
1278 def decodepayloadchunks(ui, fh): |
1279 """Reads bundle2 part payload data into chunks. |
1279 """Reads bundle2 part payload data into chunks. |
1280 |
1280 |
1281 Part payload data consists of framed chunks. This function takes |
1281 Part payload data consists of framed chunks. This function takes |
1282 a file handle and emits those chunks. |
1282 a file handle and emits those chunks. |
1283 """ |
1283 """ |
1284 dolog = ui.configbool('devel', 'bundle2.debug') |
1284 dolog = ui.configbool(b'devel', b'bundle2.debug') |
1285 debug = ui.debug |
1285 debug = ui.debug |
1286 |
1286 |
1287 headerstruct = struct.Struct(_fpayloadsize) |
1287 headerstruct = struct.Struct(_fpayloadsize) |
1288 headersize = headerstruct.size |
1288 headersize = headerstruct.size |
1289 unpack = headerstruct.unpack |
1289 unpack = headerstruct.unpack |
1290 |
1290 |
1291 readexactly = changegroup.readexactly |
1291 readexactly = changegroup.readexactly |
1292 read = fh.read |
1292 read = fh.read |
1293 |
1293 |
1294 chunksize = unpack(readexactly(fh, headersize))[0] |
1294 chunksize = unpack(readexactly(fh, headersize))[0] |
1295 indebug(ui, 'payload chunk size: %i' % chunksize) |
1295 indebug(ui, b'payload chunk size: %i' % chunksize) |
1296 |
1296 |
1297 # changegroup.readexactly() is inlined below for performance. |
1297 # changegroup.readexactly() is inlined below for performance. |
1298 while chunksize: |
1298 while chunksize: |
1299 if chunksize >= 0: |
1299 if chunksize >= 0: |
1300 s = read(chunksize) |
1300 s = read(chunksize) |
1301 if len(s) < chunksize: |
1301 if len(s) < chunksize: |
1302 raise error.Abort( |
1302 raise error.Abort( |
1303 _( |
1303 _( |
1304 'stream ended unexpectedly ' |
1304 b'stream ended unexpectedly ' |
1305 ' (got %d bytes, expected %d)' |
1305 b' (got %d bytes, expected %d)' |
1306 ) |
1306 ) |
1307 % (len(s), chunksize) |
1307 % (len(s), chunksize) |
1308 ) |
1308 ) |
1309 |
1309 |
1310 yield s |
1310 yield s |
1312 # Interrupt "signal" detected. The regular stream is interrupted |
1312 # Interrupt "signal" detected. The regular stream is interrupted |
1313 # and a bundle2 part follows. Consume it. |
1313 # and a bundle2 part follows. Consume it. |
1314 interrupthandler(ui, fh)() |
1314 interrupthandler(ui, fh)() |
1315 else: |
1315 else: |
1316 raise error.BundleValueError( |
1316 raise error.BundleValueError( |
1317 'negative payload chunk size: %s' % chunksize |
1317 b'negative payload chunk size: %s' % chunksize |
1318 ) |
1318 ) |
1319 |
1319 |
1320 s = read(headersize) |
1320 s = read(headersize) |
1321 if len(s) < headersize: |
1321 if len(s) < headersize: |
1322 raise error.Abort( |
1322 raise error.Abort( |
1323 _('stream ended unexpectedly ' ' (got %d bytes, expected %d)') |
1323 _(b'stream ended unexpectedly ' b' (got %d bytes, expected %d)') |
1324 % (len(s), chunksize) |
1324 % (len(s), chunksize) |
1325 ) |
1325 ) |
1326 |
1326 |
1327 chunksize = unpack(s)[0] |
1327 chunksize = unpack(s)[0] |
1328 |
1328 |
1329 # indebug() inlined for performance. |
1329 # indebug() inlined for performance. |
1330 if dolog: |
1330 if dolog: |
1331 debug('bundle2-input: payload chunk size: %i\n' % chunksize) |
1331 debug(b'bundle2-input: payload chunk size: %i\n' % chunksize) |
1332 |
1332 |
1333 |
1333 |
1334 class unbundlepart(unpackermixin): |
1334 class unbundlepart(unpackermixin): |
1335 """a bundle part read from a bundle""" |
1335 """a bundle part read from a bundle""" |
1336 |
1336 |
1337 def __init__(self, ui, header, fp): |
1337 def __init__(self, ui, header, fp): |
1338 super(unbundlepart, self).__init__(fp) |
1338 super(unbundlepart, self).__init__(fp) |
1339 self._seekable = util.safehasattr(fp, 'seek') and util.safehasattr( |
1339 self._seekable = util.safehasattr(fp, b'seek') and util.safehasattr( |
1340 fp, 'tell' |
1340 fp, b'tell' |
1341 ) |
1341 ) |
1342 self.ui = ui |
1342 self.ui = ui |
1343 # unbundle state attr |
1343 # unbundle state attr |
1344 self._headerdata = header |
1344 self._headerdata = header |
1345 self._headeroffset = 0 |
1345 self._headeroffset = 0 |
1382 |
1382 |
1383 def _readheader(self): |
1383 def _readheader(self): |
1384 """read the header and setup the object""" |
1384 """read the header and setup the object""" |
1385 typesize = self._unpackheader(_fparttypesize)[0] |
1385 typesize = self._unpackheader(_fparttypesize)[0] |
1386 self.type = self._fromheader(typesize) |
1386 self.type = self._fromheader(typesize) |
1387 indebug(self.ui, 'part type: "%s"' % self.type) |
1387 indebug(self.ui, b'part type: "%s"' % self.type) |
1388 self.id = self._unpackheader(_fpartid)[0] |
1388 self.id = self._unpackheader(_fpartid)[0] |
1389 indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id)) |
1389 indebug(self.ui, b'part id: "%s"' % pycompat.bytestr(self.id)) |
1390 # extract mandatory bit from type |
1390 # extract mandatory bit from type |
1391 self.mandatory = self.type != self.type.lower() |
1391 self.mandatory = self.type != self.type.lower() |
1392 self.type = self.type.lower() |
1392 self.type = self.type.lower() |
1393 ## reading parameters |
1393 ## reading parameters |
1394 # param count |
1394 # param count |
1395 mancount, advcount = self._unpackheader(_fpartparamcount) |
1395 mancount, advcount = self._unpackheader(_fpartparamcount) |
1396 indebug(self.ui, 'part parameters: %i' % (mancount + advcount)) |
1396 indebug(self.ui, b'part parameters: %i' % (mancount + advcount)) |
1397 # param size |
1397 # param size |
1398 fparamsizes = _makefpartparamsizes(mancount + advcount) |
1398 fparamsizes = _makefpartparamsizes(mancount + advcount) |
1399 paramsizes = self._unpackheader(fparamsizes) |
1399 paramsizes = self._unpackheader(fparamsizes) |
1400 # make it a list of couple again |
1400 # make it a list of couple again |
1401 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2])) |
1401 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2])) |
1596 |
1596 |
1597 The returned value is used for servers advertising their capabilities as |
1597 The returned value is used for servers advertising their capabilities as |
1598 well as clients advertising their capabilities to servers as part of |
1598 well as clients advertising their capabilities to servers as part of |
1599 bundle2 requests. The ``role`` argument specifies which is which. |
1599 bundle2 requests. The ``role`` argument specifies which is which. |
1600 """ |
1600 """ |
1601 if role not in ('client', 'server'): |
1601 if role not in (b'client', b'server'): |
1602 raise error.ProgrammingError('role argument must be client or server') |
1602 raise error.ProgrammingError(b'role argument must be client or server') |
1603 |
1603 |
1604 caps = capabilities.copy() |
1604 caps = capabilities.copy() |
1605 caps['changegroup'] = tuple( |
1605 caps[b'changegroup'] = tuple( |
1606 sorted(changegroup.supportedincomingversions(repo)) |
1606 sorted(changegroup.supportedincomingversions(repo)) |
1607 ) |
1607 ) |
1608 if obsolete.isenabled(repo, obsolete.exchangeopt): |
1608 if obsolete.isenabled(repo, obsolete.exchangeopt): |
1609 supportedformat = tuple('V%i' % v for v in obsolete.formats) |
1609 supportedformat = tuple(b'V%i' % v for v in obsolete.formats) |
1610 caps['obsmarkers'] = supportedformat |
1610 caps[b'obsmarkers'] = supportedformat |
1611 if allowpushback: |
1611 if allowpushback: |
1612 caps['pushback'] = () |
1612 caps[b'pushback'] = () |
1613 cpmode = repo.ui.config('server', 'concurrent-push-mode') |
1613 cpmode = repo.ui.config(b'server', b'concurrent-push-mode') |
1614 if cpmode == 'check-related': |
1614 if cpmode == b'check-related': |
1615 caps['checkheads'] = ('related',) |
1615 caps[b'checkheads'] = (b'related',) |
1616 if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'): |
1616 if b'phases' in repo.ui.configlist(b'devel', b'legacy.exchange'): |
1617 caps.pop('phases') |
1617 caps.pop(b'phases') |
1618 |
1618 |
1619 # Don't advertise stream clone support in server mode if not configured. |
1619 # Don't advertise stream clone support in server mode if not configured. |
1620 if role == 'server': |
1620 if role == b'server': |
1621 streamsupported = repo.ui.configbool( |
1621 streamsupported = repo.ui.configbool( |
1622 'server', 'uncompressed', untrusted=True |
1622 b'server', b'uncompressed', untrusted=True |
1623 ) |
1623 ) |
1624 featuresupported = repo.ui.configbool('server', 'bundle2.stream') |
1624 featuresupported = repo.ui.configbool(b'server', b'bundle2.stream') |
1625 |
1625 |
1626 if not streamsupported or not featuresupported: |
1626 if not streamsupported or not featuresupported: |
1627 caps.pop('stream') |
1627 caps.pop(b'stream') |
1628 # Else always advertise support on client, because payload support |
1628 # Else always advertise support on client, because payload support |
1629 # should always be advertised. |
1629 # should always be advertised. |
1630 |
1630 |
1631 return caps |
1631 return caps |
1632 |
1632 |
1633 |
1633 |
1634 def bundle2caps(remote): |
1634 def bundle2caps(remote): |
1635 """return the bundle capabilities of a peer as dict""" |
1635 """return the bundle capabilities of a peer as dict""" |
1636 raw = remote.capable('bundle2') |
1636 raw = remote.capable(b'bundle2') |
1637 if not raw and raw != '': |
1637 if not raw and raw != b'': |
1638 return {} |
1638 return {} |
1639 capsblob = urlreq.unquote(remote.capable('bundle2')) |
1639 capsblob = urlreq.unquote(remote.capable(b'bundle2')) |
1640 return decodecaps(capsblob) |
1640 return decodecaps(capsblob) |
1641 |
1641 |
1642 |
1642 |
1643 def obsmarkersversion(caps): |
1643 def obsmarkersversion(caps): |
1644 """extract the list of supported obsmarkers versions from a bundle2caps dict |
1644 """extract the list of supported obsmarkers versions from a bundle2caps dict |
1645 """ |
1645 """ |
1646 obscaps = caps.get('obsmarkers', ()) |
1646 obscaps = caps.get(b'obsmarkers', ()) |
1647 return [int(c[1:]) for c in obscaps if c.startswith('V')] |
1647 return [int(c[1:]) for c in obscaps if c.startswith(b'V')] |
1648 |
1648 |
1649 |
1649 |
1650 def writenewbundle( |
1650 def writenewbundle( |
1651 ui, |
1651 ui, |
1652 repo, |
1652 repo, |
1692 # different right now. So we keep them separated for now for the sake of |
1692 # different right now. So we keep them separated for now for the sake of |
1693 # simplicity. |
1693 # simplicity. |
1694 |
1694 |
1695 # we might not always want a changegroup in such bundle, for example in |
1695 # we might not always want a changegroup in such bundle, for example in |
1696 # stream bundles |
1696 # stream bundles |
1697 if opts.get('changegroup', True): |
1697 if opts.get(b'changegroup', True): |
1698 cgversion = opts.get('cg.version') |
1698 cgversion = opts.get(b'cg.version') |
1699 if cgversion is None: |
1699 if cgversion is None: |
1700 cgversion = changegroup.safeversion(repo) |
1700 cgversion = changegroup.safeversion(repo) |
1701 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source) |
1701 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source) |
1702 part = bundler.newpart('changegroup', data=cg.getchunks()) |
1702 part = bundler.newpart(b'changegroup', data=cg.getchunks()) |
1703 part.addparam('version', cg.version) |
1703 part.addparam(b'version', cg.version) |
1704 if 'clcount' in cg.extras: |
1704 if b'clcount' in cg.extras: |
1705 part.addparam( |
1705 part.addparam( |
1706 'nbchanges', '%d' % cg.extras['clcount'], mandatory=False |
1706 b'nbchanges', b'%d' % cg.extras[b'clcount'], mandatory=False |
1707 ) |
1707 ) |
1708 if opts.get('phases') and repo.revs( |
1708 if opts.get(b'phases') and repo.revs( |
1709 '%ln and secret()', outgoing.missingheads |
1709 b'%ln and secret()', outgoing.missingheads |
1710 ): |
1710 ): |
1711 part.addparam('targetphase', '%d' % phases.secret, mandatory=False) |
1711 part.addparam( |
1712 |
1712 b'targetphase', b'%d' % phases.secret, mandatory=False |
1713 if opts.get('streamv2', False): |
1713 ) |
|
1714 |
|
1715 if opts.get(b'streamv2', False): |
1714 addpartbundlestream2(bundler, repo, stream=True) |
1716 addpartbundlestream2(bundler, repo, stream=True) |
1715 |
1717 |
1716 if opts.get('tagsfnodescache', True): |
1718 if opts.get(b'tagsfnodescache', True): |
1717 addparttagsfnodescache(repo, bundler, outgoing) |
1719 addparttagsfnodescache(repo, bundler, outgoing) |
1718 |
1720 |
1719 if opts.get('revbranchcache', True): |
1721 if opts.get(b'revbranchcache', True): |
1720 addpartrevbranchcache(repo, bundler, outgoing) |
1722 addpartrevbranchcache(repo, bundler, outgoing) |
1721 |
1723 |
1722 if opts.get('obsolescence', False): |
1724 if opts.get(b'obsolescence', False): |
1723 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing) |
1725 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing) |
1724 buildobsmarkerspart(bundler, obsmarkers) |
1726 buildobsmarkerspart(bundler, obsmarkers) |
1725 |
1727 |
1726 if opts.get('phases', False): |
1728 if opts.get(b'phases', False): |
1727 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing) |
1729 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing) |
1728 phasedata = phases.binaryencode(headsbyphase) |
1730 phasedata = phases.binaryencode(headsbyphase) |
1729 bundler.newpart('phase-heads', data=phasedata) |
1731 bundler.newpart(b'phase-heads', data=phasedata) |
1730 |
1732 |
1731 |
1733 |
1732 def addparttagsfnodescache(repo, bundler, outgoing): |
1734 def addparttagsfnodescache(repo, bundler, outgoing): |
1733 # we include the tags fnode cache for the bundle changeset |
1735 # we include the tags fnode cache for the bundle changeset |
1734 # (as an optional parts) |
1736 # (as an optional parts) |
1772 for n in sorted(nodes): |
1774 for n in sorted(nodes): |
1773 yield n |
1775 yield n |
1774 for n in sorted(closed): |
1776 for n in sorted(closed): |
1775 yield n |
1777 yield n |
1776 |
1778 |
1777 bundler.newpart('cache:rev-branch-cache', data=generate(), mandatory=False) |
1779 bundler.newpart(b'cache:rev-branch-cache', data=generate(), mandatory=False) |
1778 |
1780 |
1779 |
1781 |
1780 def _formatrequirementsspec(requirements): |
1782 def _formatrequirementsspec(requirements): |
1781 requirements = [req for req in requirements if req != "shared"] |
1783 requirements = [req for req in requirements if req != b"shared"] |
1782 return urlreq.quote(','.join(sorted(requirements))) |
1784 return urlreq.quote(b','.join(sorted(requirements))) |
1783 |
1785 |
1784 |
1786 |
1785 def _formatrequirementsparams(requirements): |
1787 def _formatrequirementsparams(requirements): |
1786 requirements = _formatrequirementsspec(requirements) |
1788 requirements = _formatrequirementsspec(requirements) |
1787 params = "%s%s" % (urlreq.quote("requirements="), requirements) |
1789 params = b"%s%s" % (urlreq.quote(b"requirements="), requirements) |
1788 return params |
1790 return params |
1789 |
1791 |
1790 |
1792 |
1791 def addpartbundlestream2(bundler, repo, **kwargs): |
1793 def addpartbundlestream2(bundler, repo, **kwargs): |
1792 if not kwargs.get(r'stream', False): |
1794 if not kwargs.get(r'stream', False): |
1793 return |
1795 return |
1794 |
1796 |
1795 if not streamclone.allowservergeneration(repo): |
1797 if not streamclone.allowservergeneration(repo): |
1796 raise error.Abort( |
1798 raise error.Abort( |
1797 _( |
1799 _( |
1798 'stream data requested but server does not allow ' |
1800 b'stream data requested but server does not allow ' |
1799 'this feature' |
1801 b'this feature' |
1800 ), |
1802 ), |
1801 hint=_( |
1803 hint=_( |
1802 'well-behaved clients should not be ' |
1804 b'well-behaved clients should not be ' |
1803 'requesting stream data from servers not ' |
1805 b'requesting stream data from servers not ' |
1804 'advertising it; the client may be buggy' |
1806 b'advertising it; the client may be buggy' |
1805 ), |
1807 ), |
1806 ) |
1808 ) |
1807 |
1809 |
1808 # Stream clones don't compress well. And compression undermines a |
1810 # Stream clones don't compress well. And compression undermines a |
1809 # goal of stream clones, which is to be fast. Communicate the desire |
1811 # goal of stream clones, which is to be fast. Communicate the desire |
1813 # get the includes and excludes |
1815 # get the includes and excludes |
1814 includepats = kwargs.get(r'includepats') |
1816 includepats = kwargs.get(r'includepats') |
1815 excludepats = kwargs.get(r'excludepats') |
1817 excludepats = kwargs.get(r'excludepats') |
1816 |
1818 |
1817 narrowstream = repo.ui.configbool( |
1819 narrowstream = repo.ui.configbool( |
1818 'experimental', 'server.stream-narrow-clones' |
1820 b'experimental', b'server.stream-narrow-clones' |
1819 ) |
1821 ) |
1820 |
1822 |
1821 if (includepats or excludepats) and not narrowstream: |
1823 if (includepats or excludepats) and not narrowstream: |
1822 raise error.Abort(_('server does not support narrow stream clones')) |
1824 raise error.Abort(_(b'server does not support narrow stream clones')) |
1823 |
1825 |
1824 includeobsmarkers = False |
1826 includeobsmarkers = False |
1825 if repo.obsstore: |
1827 if repo.obsstore: |
1826 remoteversions = obsmarkersversion(bundler.capabilities) |
1828 remoteversions = obsmarkersversion(bundler.capabilities) |
1827 if not remoteversions: |
1829 if not remoteversions: |
1828 raise error.Abort( |
1830 raise error.Abort( |
1829 _( |
1831 _( |
1830 'server has obsolescence markers, but client ' |
1832 b'server has obsolescence markers, but client ' |
1831 'cannot receive them via stream clone' |
1833 b'cannot receive them via stream clone' |
1832 ) |
1834 ) |
1833 ) |
1835 ) |
1834 elif repo.obsstore._version in remoteversions: |
1836 elif repo.obsstore._version in remoteversions: |
1835 includeobsmarkers = True |
1837 includeobsmarkers = True |
1836 |
1838 |
1837 filecount, bytecount, it = streamclone.generatev2( |
1839 filecount, bytecount, it = streamclone.generatev2( |
1838 repo, includepats, excludepats, includeobsmarkers |
1840 repo, includepats, excludepats, includeobsmarkers |
1839 ) |
1841 ) |
1840 requirements = _formatrequirementsspec(repo.requirements) |
1842 requirements = _formatrequirementsspec(repo.requirements) |
1841 part = bundler.newpart('stream2', data=it) |
1843 part = bundler.newpart(b'stream2', data=it) |
1842 part.addparam('bytecount', '%d' % bytecount, mandatory=True) |
1844 part.addparam(b'bytecount', b'%d' % bytecount, mandatory=True) |
1843 part.addparam('filecount', '%d' % filecount, mandatory=True) |
1845 part.addparam(b'filecount', b'%d' % filecount, mandatory=True) |
1844 part.addparam('requirements', requirements, mandatory=True) |
1846 part.addparam(b'requirements', requirements, mandatory=True) |
1845 |
1847 |
1846 |
1848 |
1847 def buildobsmarkerspart(bundler, markers): |
1849 def buildobsmarkerspart(bundler, markers): |
1848 """add an obsmarker part to the bundler with <markers> |
1850 """add an obsmarker part to the bundler with <markers> |
1849 |
1851 |
1870 If no filename is specified, a temporary file is created. |
1872 If no filename is specified, a temporary file is created. |
1871 bz2 compression can be turned off. |
1873 bz2 compression can be turned off. |
1872 The bundle file will be deleted in case of errors. |
1874 The bundle file will be deleted in case of errors. |
1873 """ |
1875 """ |
1874 |
1876 |
1875 if bundletype == "HG20": |
1877 if bundletype == b"HG20": |
1876 bundle = bundle20(ui) |
1878 bundle = bundle20(ui) |
1877 bundle.setcompression(compression, compopts) |
1879 bundle.setcompression(compression, compopts) |
1878 part = bundle.newpart('changegroup', data=cg.getchunks()) |
1880 part = bundle.newpart(b'changegroup', data=cg.getchunks()) |
1879 part.addparam('version', cg.version) |
1881 part.addparam(b'version', cg.version) |
1880 if 'clcount' in cg.extras: |
1882 if b'clcount' in cg.extras: |
1881 part.addparam( |
1883 part.addparam( |
1882 'nbchanges', '%d' % cg.extras['clcount'], mandatory=False |
1884 b'nbchanges', b'%d' % cg.extras[b'clcount'], mandatory=False |
1883 ) |
1885 ) |
1884 chunkiter = bundle.getchunks() |
1886 chunkiter = bundle.getchunks() |
1885 else: |
1887 else: |
1886 # compression argument is only for the bundle2 case |
1888 # compression argument is only for the bundle2 case |
1887 assert compression is None |
1889 assert compression is None |
1888 if cg.version != '01': |
1890 if cg.version != b'01': |
1889 raise error.Abort( |
1891 raise error.Abort( |
1890 _('old bundle types only supports v1 ' 'changegroups') |
1892 _(b'old bundle types only supports v1 ' b'changegroups') |
1891 ) |
1893 ) |
1892 header, comp = bundletypes[bundletype] |
1894 header, comp = bundletypes[bundletype] |
1893 if comp not in util.compengines.supportedbundletypes: |
1895 if comp not in util.compengines.supportedbundletypes: |
1894 raise error.Abort(_('unknown stream compression type: %s') % comp) |
1896 raise error.Abort(_(b'unknown stream compression type: %s') % comp) |
1895 compengine = util.compengines.forbundletype(comp) |
1897 compengine = util.compengines.forbundletype(comp) |
1896 |
1898 |
1897 def chunkiter(): |
1899 def chunkiter(): |
1898 yield header |
1900 yield header |
1899 for chunk in compengine.compressstream(cg.getchunks(), compopts): |
1901 for chunk in compengine.compressstream(cg.getchunks(), compopts): |
1926 result = -1 + changedheads |
1928 result = -1 + changedheads |
1927 return result |
1929 return result |
1928 |
1930 |
1929 |
1931 |
1930 @parthandler( |
1932 @parthandler( |
1931 'changegroup', ('version', 'nbchanges', 'treemanifest', 'targetphase') |
1933 b'changegroup', (b'version', b'nbchanges', b'treemanifest', b'targetphase') |
1932 ) |
1934 ) |
1933 def handlechangegroup(op, inpart): |
1935 def handlechangegroup(op, inpart): |
1934 """apply a changegroup part on the repo |
1936 """apply a changegroup part on the repo |
1935 |
1937 |
1936 This is a very early implementation that will massive rework before being |
1938 This is a very early implementation that will massive rework before being |
1937 inflicted to any end-user. |
1939 inflicted to any end-user. |
1938 """ |
1940 """ |
1939 from . import localrepo |
1941 from . import localrepo |
1940 |
1942 |
1941 tr = op.gettransaction() |
1943 tr = op.gettransaction() |
1942 unpackerversion = inpart.params.get('version', '01') |
1944 unpackerversion = inpart.params.get(b'version', b'01') |
1943 # We should raise an appropriate exception here |
1945 # We should raise an appropriate exception here |
1944 cg = changegroup.getunbundler(unpackerversion, inpart, None) |
1946 cg = changegroup.getunbundler(unpackerversion, inpart, None) |
1945 # the source and url passed here are overwritten by the one contained in |
1947 # the source and url passed here are overwritten by the one contained in |
1946 # the transaction.hookargs argument. So 'bundle2' is a placeholder |
1948 # the transaction.hookargs argument. So 'bundle2' is a placeholder |
1947 nbchangesets = None |
1949 nbchangesets = None |
1948 if 'nbchanges' in inpart.params: |
1950 if b'nbchanges' in inpart.params: |
1949 nbchangesets = int(inpart.params.get('nbchanges')) |
1951 nbchangesets = int(inpart.params.get(b'nbchanges')) |
1950 if ( |
1952 if ( |
1951 'treemanifest' in inpart.params |
1953 b'treemanifest' in inpart.params |
1952 and 'treemanifest' not in op.repo.requirements |
1954 and b'treemanifest' not in op.repo.requirements |
1953 ): |
1955 ): |
1954 if len(op.repo.changelog) != 0: |
1956 if len(op.repo.changelog) != 0: |
1955 raise error.Abort( |
1957 raise error.Abort( |
1956 _( |
1958 _( |
1957 "bundle contains tree manifests, but local repo is " |
1959 b"bundle contains tree manifests, but local repo is " |
1958 "non-empty and does not use tree manifests" |
1960 b"non-empty and does not use tree manifests" |
1959 ) |
1961 ) |
1960 ) |
1962 ) |
1961 op.repo.requirements.add('treemanifest') |
1963 op.repo.requirements.add(b'treemanifest') |
1962 op.repo.svfs.options = localrepo.resolvestorevfsoptions( |
1964 op.repo.svfs.options = localrepo.resolvestorevfsoptions( |
1963 op.repo.ui, op.repo.requirements, op.repo.features |
1965 op.repo.ui, op.repo.requirements, op.repo.features |
1964 ) |
1966 ) |
1965 op.repo._writerequirements() |
1967 op.repo._writerequirements() |
1966 extrakwargs = {} |
1968 extrakwargs = {} |
1967 targetphase = inpart.params.get('targetphase') |
1969 targetphase = inpart.params.get(b'targetphase') |
1968 if targetphase is not None: |
1970 if targetphase is not None: |
1969 extrakwargs[r'targetphase'] = int(targetphase) |
1971 extrakwargs[r'targetphase'] = int(targetphase) |
1970 ret = _processchangegroup( |
1972 ret = _processchangegroup( |
1971 op, |
1973 op, |
1972 cg, |
1974 cg, |
1973 tr, |
1975 tr, |
1974 'bundle2', |
1976 b'bundle2', |
1975 'bundle2', |
1977 b'bundle2', |
1976 expectedtotal=nbchangesets, |
1978 expectedtotal=nbchangesets, |
1977 **extrakwargs |
1979 **extrakwargs |
1978 ) |
1980 ) |
1979 if op.reply is not None: |
1981 if op.reply is not None: |
1980 # This is definitely not the final form of this |
1982 # This is definitely not the final form of this |
1981 # return. But one need to start somewhere. |
1983 # return. But one need to start somewhere. |
1982 part = op.reply.newpart('reply:changegroup', mandatory=False) |
1984 part = op.reply.newpart(b'reply:changegroup', mandatory=False) |
1983 part.addparam( |
1985 part.addparam( |
1984 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
1986 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
1985 ) |
1987 ) |
1986 part.addparam('return', '%i' % ret, mandatory=False) |
1988 part.addparam(b'return', b'%i' % ret, mandatory=False) |
1987 assert not inpart.read() |
1989 assert not inpart.read() |
1988 |
1990 |
1989 |
1991 |
1990 _remotechangegroupparams = tuple( |
1992 _remotechangegroupparams = tuple( |
1991 ['url', 'size', 'digests'] + ['digest:%s' % k for k in util.DIGESTS.keys()] |
1993 [b'url', b'size', b'digests'] |
|
1994 + [b'digest:%s' % k for k in util.DIGESTS.keys()] |
1992 ) |
1995 ) |
1993 |
1996 |
1994 |
1997 |
1995 @parthandler('remote-changegroup', _remotechangegroupparams) |
1998 @parthandler(b'remote-changegroup', _remotechangegroupparams) |
1996 def handleremotechangegroup(op, inpart): |
1999 def handleremotechangegroup(op, inpart): |
1997 """apply a bundle10 on the repo, given an url and validation information |
2000 """apply a bundle10 on the repo, given an url and validation information |
1998 |
2001 |
1999 All the information about the remote bundle to import are given as |
2002 All the information about the remote bundle to import are given as |
2000 parameters. The parameters include: |
2003 parameters. The parameters include: |
2045 from . import exchange |
2051 from . import exchange |
2046 |
2052 |
2047 cg = exchange.readbundle(op.repo.ui, real_part, raw_url) |
2053 cg = exchange.readbundle(op.repo.ui, real_part, raw_url) |
2048 if not isinstance(cg, changegroup.cg1unpacker): |
2054 if not isinstance(cg, changegroup.cg1unpacker): |
2049 raise error.Abort( |
2055 raise error.Abort( |
2050 _('%s: not a bundle version 1.0') % util.hidepassword(raw_url) |
2056 _(b'%s: not a bundle version 1.0') % util.hidepassword(raw_url) |
2051 ) |
2057 ) |
2052 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2') |
2058 ret = _processchangegroup(op, cg, tr, b'bundle2', b'bundle2') |
2053 if op.reply is not None: |
2059 if op.reply is not None: |
2054 # This is definitely not the final form of this |
2060 # This is definitely not the final form of this |
2055 # return. But one need to start somewhere. |
2061 # return. But one need to start somewhere. |
2056 part = op.reply.newpart('reply:changegroup') |
2062 part = op.reply.newpart(b'reply:changegroup') |
2057 part.addparam( |
2063 part.addparam( |
2058 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
2064 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
2059 ) |
2065 ) |
2060 part.addparam('return', '%i' % ret, mandatory=False) |
2066 part.addparam(b'return', b'%i' % ret, mandatory=False) |
2061 try: |
2067 try: |
2062 real_part.validate() |
2068 real_part.validate() |
2063 except error.Abort as e: |
2069 except error.Abort as e: |
2064 raise error.Abort( |
2070 raise error.Abort( |
2065 _('bundle at %s is corrupted:\n%s') |
2071 _(b'bundle at %s is corrupted:\n%s') |
2066 % (util.hidepassword(raw_url), bytes(e)) |
2072 % (util.hidepassword(raw_url), bytes(e)) |
2067 ) |
2073 ) |
2068 assert not inpart.read() |
2074 assert not inpart.read() |
2069 |
2075 |
2070 |
2076 |
2071 @parthandler('reply:changegroup', ('return', 'in-reply-to')) |
2077 @parthandler(b'reply:changegroup', (b'return', b'in-reply-to')) |
2072 def handlereplychangegroup(op, inpart): |
2078 def handlereplychangegroup(op, inpart): |
2073 ret = int(inpart.params['return']) |
2079 ret = int(inpart.params[b'return']) |
2074 replyto = int(inpart.params['in-reply-to']) |
2080 replyto = int(inpart.params[b'in-reply-to']) |
2075 op.records.add('changegroup', {'return': ret}, replyto) |
2081 op.records.add(b'changegroup', {b'return': ret}, replyto) |
2076 |
2082 |
2077 |
2083 |
2078 @parthandler('check:bookmarks') |
2084 @parthandler(b'check:bookmarks') |
2079 def handlecheckbookmarks(op, inpart): |
2085 def handlecheckbookmarks(op, inpart): |
2080 """check location of bookmarks |
2086 """check location of bookmarks |
2081 |
2087 |
2082 This part is to be used to detect push race regarding bookmark, it |
2088 This part is to be used to detect push race regarding bookmark, it |
2083 contains binary encoded (bookmark, node) tuple. If the local state does |
2089 contains binary encoded (bookmark, node) tuple. If the local state does |
2084 not marks the one in the part, a PushRaced exception is raised |
2090 not marks the one in the part, a PushRaced exception is raised |
2085 """ |
2091 """ |
2086 bookdata = bookmarks.binarydecode(inpart) |
2092 bookdata = bookmarks.binarydecode(inpart) |
2087 |
2093 |
2088 msgstandard = ( |
2094 msgstandard = ( |
2089 'remote repository changed while pushing - please try again ' |
2095 b'remote repository changed while pushing - please try again ' |
2090 '(bookmark "%s" move from %s to %s)' |
2096 b'(bookmark "%s" move from %s to %s)' |
2091 ) |
2097 ) |
2092 msgmissing = ( |
2098 msgmissing = ( |
2093 'remote repository changed while pushing - please try again ' |
2099 b'remote repository changed while pushing - please try again ' |
2094 '(bookmark "%s" is missing, expected %s)' |
2100 b'(bookmark "%s" is missing, expected %s)' |
2095 ) |
2101 ) |
2096 msgexist = ( |
2102 msgexist = ( |
2097 'remote repository changed while pushing - please try again ' |
2103 b'remote repository changed while pushing - please try again ' |
2098 '(bookmark "%s" set on %s, expected missing)' |
2104 b'(bookmark "%s" set on %s, expected missing)' |
2099 ) |
2105 ) |
2100 for book, node in bookdata: |
2106 for book, node in bookdata: |
2101 currentnode = op.repo._bookmarks.get(book) |
2107 currentnode = op.repo._bookmarks.get(book) |
2102 if currentnode != node: |
2108 if currentnode != node: |
2103 if node is None: |
2109 if node is None: |
2149 while len(h) == 20: |
2155 while len(h) == 20: |
2150 heads.append(h) |
2156 heads.append(h) |
2151 h = inpart.read(20) |
2157 h = inpart.read(20) |
2152 assert not h |
2158 assert not h |
2153 # trigger a transaction so that we are guaranteed to have the lock now. |
2159 # trigger a transaction so that we are guaranteed to have the lock now. |
2154 if op.ui.configbool('experimental', 'bundle2lazylocking'): |
2160 if op.ui.configbool(b'experimental', b'bundle2lazylocking'): |
2155 op.gettransaction() |
2161 op.gettransaction() |
2156 |
2162 |
2157 currentheads = set() |
2163 currentheads = set() |
2158 for ls in op.repo.branchmap().iterheads(): |
2164 for ls in op.repo.branchmap().iterheads(): |
2159 currentheads.update(ls) |
2165 currentheads.update(ls) |
2160 |
2166 |
2161 for h in heads: |
2167 for h in heads: |
2162 if h not in currentheads: |
2168 if h not in currentheads: |
2163 raise error.PushRaced( |
2169 raise error.PushRaced( |
2164 'remote repository changed while pushing - ' 'please try again' |
2170 b'remote repository changed while pushing - ' |
|
2171 b'please try again' |
2165 ) |
2172 ) |
2166 |
2173 |
2167 |
2174 |
2168 @parthandler('check:phases') |
2175 @parthandler(b'check:phases') |
2169 def handlecheckphases(op, inpart): |
2176 def handlecheckphases(op, inpart): |
2170 """check that phase boundaries of the repository did not change |
2177 """check that phase boundaries of the repository did not change |
2171 |
2178 |
2172 This is used to detect a push race. |
2179 This is used to detect a push race. |
2173 """ |
2180 """ |
2174 phasetonodes = phases.binarydecode(inpart) |
2181 phasetonodes = phases.binarydecode(inpart) |
2175 unfi = op.repo.unfiltered() |
2182 unfi = op.repo.unfiltered() |
2176 cl = unfi.changelog |
2183 cl = unfi.changelog |
2177 phasecache = unfi._phasecache |
2184 phasecache = unfi._phasecache |
2178 msg = ( |
2185 msg = ( |
2179 'remote repository changed while pushing - please try again ' |
2186 b'remote repository changed while pushing - please try again ' |
2180 '(%s is %s expected %s)' |
2187 b'(%s is %s expected %s)' |
2181 ) |
2188 ) |
2182 for expectedphase, nodes in enumerate(phasetonodes): |
2189 for expectedphase, nodes in enumerate(phasetonodes): |
2183 for n in nodes: |
2190 for n in nodes: |
2184 actualphase = phasecache.phase(unfi, cl.rev(n)) |
2191 actualphase = phasecache.phase(unfi, cl.rev(n)) |
2185 if actualphase != expectedphase: |
2192 if actualphase != expectedphase: |
2210 |
2217 |
2211 class AbortFromPart(error.Abort): |
2218 class AbortFromPart(error.Abort): |
2212 """Sub-class of Abort that denotes an error from a bundle2 part.""" |
2219 """Sub-class of Abort that denotes an error from a bundle2 part.""" |
2213 |
2220 |
2214 |
2221 |
2215 @parthandler('error:abort', ('message', 'hint')) |
2222 @parthandler(b'error:abort', (b'message', b'hint')) |
2216 def handleerrorabort(op, inpart): |
2223 def handleerrorabort(op, inpart): |
2217 """Used to transmit abort error over the wire""" |
2224 """Used to transmit abort error over the wire""" |
2218 raise AbortFromPart( |
2225 raise AbortFromPart( |
2219 inpart.params['message'], hint=inpart.params.get('hint') |
2226 inpart.params[b'message'], hint=inpart.params.get(b'hint') |
2220 ) |
2227 ) |
2221 |
2228 |
2222 |
2229 |
2223 @parthandler( |
2230 @parthandler( |
2224 'error:pushkey', ('namespace', 'key', 'new', 'old', 'ret', 'in-reply-to') |
2231 b'error:pushkey', |
|
2232 (b'namespace', b'key', b'new', b'old', b'ret', b'in-reply-to'), |
2225 ) |
2233 ) |
2226 def handleerrorpushkey(op, inpart): |
2234 def handleerrorpushkey(op, inpart): |
2227 """Used to transmit failure of a mandatory pushkey over the wire""" |
2235 """Used to transmit failure of a mandatory pushkey over the wire""" |
2228 kwargs = {} |
2236 kwargs = {} |
2229 for name in ('namespace', 'key', 'new', 'old', 'ret'): |
2237 for name in (b'namespace', b'key', b'new', b'old', b'ret'): |
2230 value = inpart.params.get(name) |
2238 value = inpart.params.get(name) |
2231 if value is not None: |
2239 if value is not None: |
2232 kwargs[name] = value |
2240 kwargs[name] = value |
2233 raise error.PushkeyFailed( |
2241 raise error.PushkeyFailed( |
2234 inpart.params['in-reply-to'], **pycompat.strkwargs(kwargs) |
2242 inpart.params[b'in-reply-to'], **pycompat.strkwargs(kwargs) |
2235 ) |
2243 ) |
2236 |
2244 |
2237 |
2245 |
2238 @parthandler('error:unsupportedcontent', ('parttype', 'params')) |
2246 @parthandler(b'error:unsupportedcontent', (b'parttype', b'params')) |
2239 def handleerrorunsupportedcontent(op, inpart): |
2247 def handleerrorunsupportedcontent(op, inpart): |
2240 """Used to transmit unknown content error over the wire""" |
2248 """Used to transmit unknown content error over the wire""" |
2241 kwargs = {} |
2249 kwargs = {} |
2242 parttype = inpart.params.get('parttype') |
2250 parttype = inpart.params.get(b'parttype') |
2243 if parttype is not None: |
2251 if parttype is not None: |
2244 kwargs['parttype'] = parttype |
2252 kwargs[b'parttype'] = parttype |
2245 params = inpart.params.get('params') |
2253 params = inpart.params.get(b'params') |
2246 if params is not None: |
2254 if params is not None: |
2247 kwargs['params'] = params.split('\0') |
2255 kwargs[b'params'] = params.split(b'\0') |
2248 |
2256 |
2249 raise error.BundleUnknownFeatureError(**pycompat.strkwargs(kwargs)) |
2257 raise error.BundleUnknownFeatureError(**pycompat.strkwargs(kwargs)) |
2250 |
2258 |
2251 |
2259 |
2252 @parthandler('error:pushraced', ('message',)) |
2260 @parthandler(b'error:pushraced', (b'message',)) |
2253 def handleerrorpushraced(op, inpart): |
2261 def handleerrorpushraced(op, inpart): |
2254 """Used to transmit push race error over the wire""" |
2262 """Used to transmit push race error over the wire""" |
2255 raise error.ResponseError(_('push failed:'), inpart.params['message']) |
2263 raise error.ResponseError(_(b'push failed:'), inpart.params[b'message']) |
2256 |
2264 |
2257 |
2265 |
2258 @parthandler('listkeys', ('namespace',)) |
2266 @parthandler(b'listkeys', (b'namespace',)) |
2259 def handlelistkeys(op, inpart): |
2267 def handlelistkeys(op, inpart): |
2260 """retrieve pushkey namespace content stored in a bundle2""" |
2268 """retrieve pushkey namespace content stored in a bundle2""" |
2261 namespace = inpart.params['namespace'] |
2269 namespace = inpart.params[b'namespace'] |
2262 r = pushkey.decodekeys(inpart.read()) |
2270 r = pushkey.decodekeys(inpart.read()) |
2263 op.records.add('listkeys', (namespace, r)) |
2271 op.records.add(b'listkeys', (namespace, r)) |
2264 |
2272 |
2265 |
2273 |
2266 @parthandler('pushkey', ('namespace', 'key', 'old', 'new')) |
2274 @parthandler(b'pushkey', (b'namespace', b'key', b'old', b'new')) |
2267 def handlepushkey(op, inpart): |
2275 def handlepushkey(op, inpart): |
2268 """process a pushkey request""" |
2276 """process a pushkey request""" |
2269 dec = pushkey.decode |
2277 dec = pushkey.decode |
2270 namespace = dec(inpart.params['namespace']) |
2278 namespace = dec(inpart.params[b'namespace']) |
2271 key = dec(inpart.params['key']) |
2279 key = dec(inpart.params[b'key']) |
2272 old = dec(inpart.params['old']) |
2280 old = dec(inpart.params[b'old']) |
2273 new = dec(inpart.params['new']) |
2281 new = dec(inpart.params[b'new']) |
2274 # Grab the transaction to ensure that we have the lock before performing the |
2282 # Grab the transaction to ensure that we have the lock before performing the |
2275 # pushkey. |
2283 # pushkey. |
2276 if op.ui.configbool('experimental', 'bundle2lazylocking'): |
2284 if op.ui.configbool(b'experimental', b'bundle2lazylocking'): |
2277 op.gettransaction() |
2285 op.gettransaction() |
2278 ret = op.repo.pushkey(namespace, key, old, new) |
2286 ret = op.repo.pushkey(namespace, key, old, new) |
2279 record = {'namespace': namespace, 'key': key, 'old': old, 'new': new} |
2287 record = {b'namespace': namespace, b'key': key, b'old': old, b'new': new} |
2280 op.records.add('pushkey', record) |
2288 op.records.add(b'pushkey', record) |
2281 if op.reply is not None: |
2289 if op.reply is not None: |
2282 rpart = op.reply.newpart('reply:pushkey') |
2290 rpart = op.reply.newpart(b'reply:pushkey') |
2283 rpart.addparam( |
2291 rpart.addparam( |
2284 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
2292 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
2285 ) |
2293 ) |
2286 rpart.addparam('return', '%i' % ret, mandatory=False) |
2294 rpart.addparam(b'return', b'%i' % ret, mandatory=False) |
2287 if inpart.mandatory and not ret: |
2295 if inpart.mandatory and not ret: |
2288 kwargs = {} |
2296 kwargs = {} |
2289 for key in ('namespace', 'key', 'new', 'old', 'ret'): |
2297 for key in (b'namespace', b'key', b'new', b'old', b'ret'): |
2290 if key in inpart.params: |
2298 if key in inpart.params: |
2291 kwargs[key] = inpart.params[key] |
2299 kwargs[key] = inpart.params[key] |
2292 raise error.PushkeyFailed( |
2300 raise error.PushkeyFailed( |
2293 partid='%d' % inpart.id, **pycompat.strkwargs(kwargs) |
2301 partid=b'%d' % inpart.id, **pycompat.strkwargs(kwargs) |
2294 ) |
2302 ) |
2295 |
2303 |
2296 |
2304 |
2297 @parthandler('bookmarks') |
2305 @parthandler(b'bookmarks') |
2298 def handlebookmark(op, inpart): |
2306 def handlebookmark(op, inpart): |
2299 """transmit bookmark information |
2307 """transmit bookmark information |
2300 |
2308 |
2301 The part contains binary encoded bookmark information. |
2309 The part contains binary encoded bookmark information. |
2302 |
2310 |
2311 When mode is 'records', the information is recorded into the 'bookmarks' |
2319 When mode is 'records', the information is recorded into the 'bookmarks' |
2312 records of the bundle operation. This behavior is suitable for pulling. |
2320 records of the bundle operation. This behavior is suitable for pulling. |
2313 """ |
2321 """ |
2314 changes = bookmarks.binarydecode(inpart) |
2322 changes = bookmarks.binarydecode(inpart) |
2315 |
2323 |
2316 pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat') |
2324 pushkeycompat = op.repo.ui.configbool( |
2317 bookmarksmode = op.modes.get('bookmarks', 'apply') |
2325 b'server', b'bookmarks-pushkey-compat' |
2318 |
2326 ) |
2319 if bookmarksmode == 'apply': |
2327 bookmarksmode = op.modes.get(b'bookmarks', b'apply') |
|
2328 |
|
2329 if bookmarksmode == b'apply': |
2320 tr = op.gettransaction() |
2330 tr = op.gettransaction() |
2321 bookstore = op.repo._bookmarks |
2331 bookstore = op.repo._bookmarks |
2322 if pushkeycompat: |
2332 if pushkeycompat: |
2323 allhooks = [] |
2333 allhooks = [] |
2324 for book, node in changes: |
2334 for book, node in changes: |
2325 hookargs = tr.hookargs.copy() |
2335 hookargs = tr.hookargs.copy() |
2326 hookargs['pushkeycompat'] = '1' |
2336 hookargs[b'pushkeycompat'] = b'1' |
2327 hookargs['namespace'] = 'bookmarks' |
2337 hookargs[b'namespace'] = b'bookmarks' |
2328 hookargs['key'] = book |
2338 hookargs[b'key'] = book |
2329 hookargs['old'] = nodemod.hex(bookstore.get(book, '')) |
2339 hookargs[b'old'] = nodemod.hex(bookstore.get(book, b'')) |
2330 hookargs['new'] = nodemod.hex(node if node is not None else '') |
2340 hookargs[b'new'] = nodemod.hex( |
|
2341 node if node is not None else b'' |
|
2342 ) |
2331 allhooks.append(hookargs) |
2343 allhooks.append(hookargs) |
2332 |
2344 |
2333 for hookargs in allhooks: |
2345 for hookargs in allhooks: |
2334 op.repo.hook( |
2346 op.repo.hook( |
2335 'prepushkey', throw=True, **pycompat.strkwargs(hookargs) |
2347 b'prepushkey', throw=True, **pycompat.strkwargs(hookargs) |
2336 ) |
2348 ) |
2337 |
2349 |
2338 bookstore.applychanges(op.repo, op.gettransaction(), changes) |
2350 bookstore.applychanges(op.repo, op.gettransaction(), changes) |
2339 |
2351 |
2340 if pushkeycompat: |
2352 if pushkeycompat: |
2341 |
2353 |
2342 def runhook(): |
2354 def runhook(): |
2343 for hookargs in allhooks: |
2355 for hookargs in allhooks: |
2344 op.repo.hook('pushkey', **pycompat.strkwargs(hookargs)) |
2356 op.repo.hook(b'pushkey', **pycompat.strkwargs(hookargs)) |
2345 |
2357 |
2346 op.repo._afterlock(runhook) |
2358 op.repo._afterlock(runhook) |
2347 |
2359 |
2348 elif bookmarksmode == 'records': |
2360 elif bookmarksmode == b'records': |
2349 for book, node in changes: |
2361 for book, node in changes: |
2350 record = {'bookmark': book, 'node': node} |
2362 record = {b'bookmark': book, b'node': node} |
2351 op.records.add('bookmarks', record) |
2363 op.records.add(b'bookmarks', record) |
2352 else: |
2364 else: |
2353 raise error.ProgrammingError('unkown bookmark mode: %s' % bookmarksmode) |
2365 raise error.ProgrammingError( |
2354 |
2366 b'unkown bookmark mode: %s' % bookmarksmode |
2355 |
2367 ) |
2356 @parthandler('phase-heads') |
2368 |
|
2369 |
|
2370 @parthandler(b'phase-heads') |
2357 def handlephases(op, inpart): |
2371 def handlephases(op, inpart): |
2358 """apply phases from bundle part to repo""" |
2372 """apply phases from bundle part to repo""" |
2359 headsbyphase = phases.binarydecode(inpart) |
2373 headsbyphase = phases.binarydecode(inpart) |
2360 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase) |
2374 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase) |
2361 |
2375 |
2362 |
2376 |
2363 @parthandler('reply:pushkey', ('return', 'in-reply-to')) |
2377 @parthandler(b'reply:pushkey', (b'return', b'in-reply-to')) |
2364 def handlepushkeyreply(op, inpart): |
2378 def handlepushkeyreply(op, inpart): |
2365 """retrieve the result of a pushkey request""" |
2379 """retrieve the result of a pushkey request""" |
2366 ret = int(inpart.params['return']) |
2380 ret = int(inpart.params[b'return']) |
2367 partid = int(inpart.params['in-reply-to']) |
2381 partid = int(inpart.params[b'in-reply-to']) |
2368 op.records.add('pushkey', {'return': ret}, partid) |
2382 op.records.add(b'pushkey', {b'return': ret}, partid) |
2369 |
2383 |
2370 |
2384 |
2371 @parthandler('obsmarkers') |
2385 @parthandler(b'obsmarkers') |
2372 def handleobsmarker(op, inpart): |
2386 def handleobsmarker(op, inpart): |
2373 """add a stream of obsmarkers to the repo""" |
2387 """add a stream of obsmarkers to the repo""" |
2374 tr = op.gettransaction() |
2388 tr = op.gettransaction() |
2375 markerdata = inpart.read() |
2389 markerdata = inpart.read() |
2376 if op.ui.config('experimental', 'obsmarkers-exchange-debug'): |
2390 if op.ui.config(b'experimental', b'obsmarkers-exchange-debug'): |
2377 op.ui.write('obsmarker-exchange: %i bytes received\n' % len(markerdata)) |
2391 op.ui.write( |
|
2392 b'obsmarker-exchange: %i bytes received\n' % len(markerdata) |
|
2393 ) |
2378 # The mergemarkers call will crash if marker creation is not enabled. |
2394 # The mergemarkers call will crash if marker creation is not enabled. |
2379 # we want to avoid this if the part is advisory. |
2395 # we want to avoid this if the part is advisory. |
2380 if not inpart.mandatory and op.repo.obsstore.readonly: |
2396 if not inpart.mandatory and op.repo.obsstore.readonly: |
2381 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled\n') |
2397 op.repo.ui.debug( |
|
2398 b'ignoring obsolescence markers, feature not enabled\n' |
|
2399 ) |
2382 return |
2400 return |
2383 new = op.repo.obsstore.mergemarkers(tr, markerdata) |
2401 new = op.repo.obsstore.mergemarkers(tr, markerdata) |
2384 op.repo.invalidatevolatilesets() |
2402 op.repo.invalidatevolatilesets() |
2385 op.records.add('obsmarkers', {'new': new}) |
2403 op.records.add(b'obsmarkers', {b'new': new}) |
2386 if op.reply is not None: |
2404 if op.reply is not None: |
2387 rpart = op.reply.newpart('reply:obsmarkers') |
2405 rpart = op.reply.newpart(b'reply:obsmarkers') |
2388 rpart.addparam( |
2406 rpart.addparam( |
2389 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
2407 b'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False |
2390 ) |
2408 ) |
2391 rpart.addparam('new', '%i' % new, mandatory=False) |
2409 rpart.addparam(b'new', b'%i' % new, mandatory=False) |
2392 |
2410 |
2393 |
2411 |
2394 @parthandler('reply:obsmarkers', ('new', 'in-reply-to')) |
2412 @parthandler(b'reply:obsmarkers', (b'new', b'in-reply-to')) |
2395 def handleobsmarkerreply(op, inpart): |
2413 def handleobsmarkerreply(op, inpart): |
2396 """retrieve the result of a pushkey request""" |
2414 """retrieve the result of a pushkey request""" |
2397 ret = int(inpart.params['new']) |
2415 ret = int(inpart.params[b'new']) |
2398 partid = int(inpart.params['in-reply-to']) |
2416 partid = int(inpart.params[b'in-reply-to']) |
2399 op.records.add('obsmarkers', {'new': ret}, partid) |
2417 op.records.add(b'obsmarkers', {b'new': ret}, partid) |
2400 |
2418 |
2401 |
2419 |
2402 @parthandler('hgtagsfnodes') |
2420 @parthandler(b'hgtagsfnodes') |
2403 def handlehgtagsfnodes(op, inpart): |
2421 def handlehgtagsfnodes(op, inpart): |
2404 """Applies .hgtags fnodes cache entries to the local repo. |
2422 """Applies .hgtags fnodes cache entries to the local repo. |
2405 |
2423 |
2406 Payload is pairs of 20 byte changeset nodes and filenodes. |
2424 Payload is pairs of 20 byte changeset nodes and filenodes. |
2407 """ |
2425 """ |
2408 # Grab the transaction so we ensure that we have the lock at this point. |
2426 # Grab the transaction so we ensure that we have the lock at this point. |
2409 if op.ui.configbool('experimental', 'bundle2lazylocking'): |
2427 if op.ui.configbool(b'experimental', b'bundle2lazylocking'): |
2410 op.gettransaction() |
2428 op.gettransaction() |
2411 cache = tags.hgtagsfnodescache(op.repo.unfiltered()) |
2429 cache = tags.hgtagsfnodescache(op.repo.unfiltered()) |
2412 |
2430 |
2413 count = 0 |
2431 count = 0 |
2414 while True: |
2432 while True: |
2415 node = inpart.read(20) |
2433 node = inpart.read(20) |
2416 fnode = inpart.read(20) |
2434 fnode = inpart.read(20) |
2417 if len(node) < 20 or len(fnode) < 20: |
2435 if len(node) < 20 or len(fnode) < 20: |
2418 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n') |
2436 op.ui.debug(b'ignoring incomplete received .hgtags fnodes data\n') |
2419 break |
2437 break |
2420 cache.setfnode(node, fnode) |
2438 cache.setfnode(node, fnode) |
2421 count += 1 |
2439 count += 1 |
2422 |
2440 |
2423 cache.write() |
2441 cache.write() |
2424 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count) |
2442 op.ui.debug(b'applied %i hgtags fnodes cache entries\n' % count) |
2425 |
2443 |
2426 |
2444 |
2427 rbcstruct = struct.Struct('>III') |
2445 rbcstruct = struct.Struct(b'>III') |
2428 |
2446 |
2429 |
2447 |
2430 @parthandler('cache:rev-branch-cache') |
2448 @parthandler(b'cache:rev-branch-cache') |
2431 def handlerbc(op, inpart): |
2449 def handlerbc(op, inpart): |
2432 """receive a rev-branch-cache payload and update the local cache |
2450 """receive a rev-branch-cache payload and update the local cache |
2433 |
2451 |
2434 The payload is a series of data related to each branch |
2452 The payload is a series of data related to each branch |
2435 |
2453 |
2458 cache.setdata(branch, rev, node, True) |
2476 cache.setdata(branch, rev, node, True) |
2459 rawheader = inpart.read(rbcstruct.size) |
2477 rawheader = inpart.read(rbcstruct.size) |
2460 cache.write() |
2478 cache.write() |
2461 |
2479 |
2462 |
2480 |
2463 @parthandler('pushvars') |
2481 @parthandler(b'pushvars') |
2464 def bundle2getvars(op, part): |
2482 def bundle2getvars(op, part): |
2465 '''unbundle a bundle2 containing shellvars on the server''' |
2483 '''unbundle a bundle2 containing shellvars on the server''' |
2466 # An option to disable unbundling on server-side for security reasons |
2484 # An option to disable unbundling on server-side for security reasons |
2467 if op.ui.configbool('push', 'pushvars.server'): |
2485 if op.ui.configbool(b'push', b'pushvars.server'): |
2468 hookargs = {} |
2486 hookargs = {} |
2469 for key, value in part.advisoryparams: |
2487 for key, value in part.advisoryparams: |
2470 key = key.upper() |
2488 key = key.upper() |
2471 # We want pushed variables to have USERVAR_ prepended so we know |
2489 # We want pushed variables to have USERVAR_ prepended so we know |
2472 # they came from the --pushvar flag. |
2490 # they came from the --pushvar flag. |
2473 key = "USERVAR_" + key |
2491 key = b"USERVAR_" + key |
2474 hookargs[key] = value |
2492 hookargs[key] = value |
2475 op.addhookargs(hookargs) |
2493 op.addhookargs(hookargs) |
2476 |
2494 |
2477 |
2495 |
2478 @parthandler('stream2', ('requirements', 'filecount', 'bytecount')) |
2496 @parthandler(b'stream2', (b'requirements', b'filecount', b'bytecount')) |
2479 def handlestreamv2bundle(op, part): |
2497 def handlestreamv2bundle(op, part): |
2480 |
2498 |
2481 requirements = urlreq.unquote(part.params['requirements']).split(',') |
2499 requirements = urlreq.unquote(part.params[b'requirements']).split(b',') |
2482 filecount = int(part.params['filecount']) |
2500 filecount = int(part.params[b'filecount']) |
2483 bytecount = int(part.params['bytecount']) |
2501 bytecount = int(part.params[b'bytecount']) |
2484 |
2502 |
2485 repo = op.repo |
2503 repo = op.repo |
2486 if len(repo): |
2504 if len(repo): |
2487 msg = _('cannot apply stream clone to non empty repository') |
2505 msg = _(b'cannot apply stream clone to non empty repository') |
2488 raise error.Abort(msg) |
2506 raise error.Abort(msg) |
2489 |
2507 |
2490 repo.ui.debug('applying stream bundle\n') |
2508 repo.ui.debug(b'applying stream bundle\n') |
2491 streamclone.applybundlev2(repo, part, filecount, bytecount, requirements) |
2509 streamclone.applybundlev2(repo, part, filecount, bytecount, requirements) |
2492 |
2510 |
2493 |
2511 |
2494 def widen_bundle( |
2512 def widen_bundle( |
2495 bundler, repo, oldmatcher, newmatcher, common, known, cgversion, ellipses |
2513 bundler, repo, oldmatcher, newmatcher, common, known, cgversion, ellipses |