Show More
The requested changes are too big and content was truncated. Show full diff
@@ -36,8 +36,13 b' def bootstrap():' | |||
|
36 | 36 | pip = venv_bin / 'pip' |
|
37 | 37 | python = venv_bin / 'python' |
|
38 | 38 | |
|
39 | args = [str(pip), 'install', '-r', str(REQUIREMENTS_TXT), | |
|
40 | '--disable-pip-version-check'] | |
|
39 | args = [ | |
|
40 | str(pip), | |
|
41 | 'install', | |
|
42 | '-r', | |
|
43 | str(REQUIREMENTS_TXT), | |
|
44 | '--disable-pip-version-check', | |
|
45 | ] | |
|
41 | 46 | |
|
42 | 47 | if not venv_created: |
|
43 | 48 | args.append('-q') |
@@ -45,8 +50,7 b' def bootstrap():' | |||
|
45 | 50 | subprocess.run(args, check=True) |
|
46 | 51 | |
|
47 | 52 | os.environ['HGAUTOMATION_BOOTSTRAPPED'] = '1' |
|
48 | os.environ['PATH'] = '%s%s%s' % ( | |
|
49 | venv_bin, os.pathsep, os.environ['PATH']) | |
|
53 | os.environ['PATH'] = '%s%s%s' % (venv_bin, os.pathsep, os.environ['PATH']) | |
|
50 | 54 | |
|
51 | 55 | subprocess.run([str(python), __file__] + sys.argv[1:], check=True) |
|
52 | 56 |
@@ -10,9 +10,7 b'' | |||
|
10 | 10 | import pathlib |
|
11 | 11 | import secrets |
|
12 | 12 | |
|
13 |
from .aws import |
|
|
14 | AWSConnection, | |
|
15 | ) | |
|
13 | from .aws import AWSConnection | |
|
16 | 14 | |
|
17 | 15 | |
|
18 | 16 | class HGAutomation: |
@@ -19,9 +19,7 b' import time' | |||
|
19 | 19 | import boto3 |
|
20 | 20 | import botocore.exceptions |
|
21 | 21 | |
|
22 |
from .linux import |
|
|
23 | BOOTSTRAP_DEBIAN, | |
|
24 | ) | |
|
22 | from .linux import BOOTSTRAP_DEBIAN | |
|
25 | 23 | from .ssh import ( |
|
26 | 24 | exec_command as ssh_exec_command, |
|
27 | 25 | wait_for_ssh, |
@@ -32,10 +30,13 b' from .winrm import (' | |||
|
32 | 30 | ) |
|
33 | 31 | |
|
34 | 32 | |
|
35 | SOURCE_ROOT = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent.parent | |
|
33 | SOURCE_ROOT = pathlib.Path( | |
|
34 | os.path.abspath(__file__) | |
|
35 | ).parent.parent.parent.parent | |
|
36 | 36 | |
|
37 |
INSTALL_WINDOWS_DEPENDENCIES = ( |
|
|
38 |
|
|
|
37 | INSTALL_WINDOWS_DEPENDENCIES = ( | |
|
38 | SOURCE_ROOT / 'contrib' / 'install-windows-dependencies.ps1' | |
|
39 | ) | |
|
39 | 40 | |
|
40 | 41 | |
|
41 | 42 | INSTANCE_TYPES_WITH_STORAGE = { |
@@ -107,7 +108,6 b' SECURITY_GROUPS = {' | |||
|
107 | 108 | 'Description': 'RDP from entire Internet', |
|
108 | 109 | }, |
|
109 | 110 | ], |
|
110 | ||
|
111 | 111 | }, |
|
112 | 112 | { |
|
113 | 113 | 'FromPort': 5985, |
@@ -119,7 +119,7 b' SECURITY_GROUPS = {' | |||
|
119 | 119 | 'Description': 'PowerShell Remoting (Windows Remote Management)', |
|
120 | 120 | }, |
|
121 | 121 | ], |
|
122 | } | |
|
122 | }, | |
|
123 | 123 | ], |
|
124 | 124 | }, |
|
125 | 125 | } |
@@ -152,11 +152,7 b" ASSUME_ROLE_POLICY_DOCUMENT = '''" | |||
|
152 | 152 | |
|
153 | 153 | |
|
154 | 154 | IAM_INSTANCE_PROFILES = { |
|
155 | 'ephemeral-ec2-1': { | |
|
156 | 'roles': [ | |
|
157 | 'ephemeral-ec2-role-1', | |
|
158 | ], | |
|
159 | } | |
|
155 | 'ephemeral-ec2-1': {'roles': ['ephemeral-ec2-role-1',],} | |
|
160 | 156 | } |
|
161 | 157 | |
|
162 | 158 | |
@@ -257,10 +253,19 b' def rsa_key_fingerprint(p: pathlib.Path)' | |||
|
257 | 253 | |
|
258 | 254 | # TODO use rsa package. |
|
259 | 255 | res = subprocess.run( |
|
260 | ['openssl', 'pkcs8', '-in', str(p), '-nocrypt', '-topk8', | |
|
261 | '-outform', 'DER'], | |
|
256 | [ | |
|
257 | 'openssl', | |
|
258 | 'pkcs8', | |
|
259 | '-in', | |
|
260 | str(p), | |
|
261 | '-nocrypt', | |
|
262 | '-topk8', | |
|
263 | '-outform', | |
|
264 | 'DER', | |
|
265 | ], | |
|
262 | 266 | capture_output=True, |
|
263 |
check=True |
|
|
267 | check=True, | |
|
268 | ) | |
|
264 | 269 | |
|
265 | 270 | sha1 = hashlib.sha1(res.stdout).hexdigest() |
|
266 | 271 | return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2])) |
@@ -306,8 +311,9 b' def ensure_key_pairs(state_path: pathlib' | |||
|
306 | 311 | data = fh.read() |
|
307 | 312 | |
|
308 | 313 | if not data.startswith('ssh-rsa '): |
|
309 | print('unexpected format for key pair file: %s; removing' % | |
|
310 | pub_full) | |
|
314 | print( | |
|
315 | 'unexpected format for key pair file: %s; removing' % pub_full | |
|
316 | ) | |
|
311 | 317 | pub_full.unlink() |
|
312 | 318 | priv_full.unlink() |
|
313 | 319 | continue |
@@ -327,8 +333,10 b' def ensure_key_pairs(state_path: pathlib' | |||
|
327 | 333 | del local_existing[name] |
|
328 | 334 | |
|
329 | 335 | elif remote_existing[name] != local_existing[name]: |
|
330 | print('key fingerprint mismatch for %s; ' | |
|
331 | 'removing from local and remote' % name) | |
|
336 | print( | |
|
337 | 'key fingerprint mismatch for %s; ' | |
|
338 | 'removing from local and remote' % name | |
|
339 | ) | |
|
332 | 340 | remove_local(name) |
|
333 | 341 | remove_remote('%s%s' % (prefix, name)) |
|
334 | 342 | del local_existing[name] |
@@ -356,15 +364,18 b' def ensure_key_pairs(state_path: pathlib' | |||
|
356 | 364 | subprocess.run( |
|
357 | 365 | ['ssh-keygen', '-y', '-f', str(priv_full)], |
|
358 | 366 | stdout=fh, |
|
359 |
check=True |
|
|
367 | check=True, | |
|
368 | ) | |
|
360 | 369 | |
|
361 | 370 | pub_full.chmod(0o0600) |
|
362 | 371 | |
|
363 | 372 | |
|
364 | 373 | def delete_instance_profile(profile): |
|
365 | 374 | for role in profile.roles: |
|
366 | print('removing role %s from instance profile %s' % (role.name, | |
|
367 | profile.name)) | |
|
375 | print( | |
|
376 | 'removing role %s from instance profile %s' | |
|
377 | % (role.name, profile.name) | |
|
378 | ) | |
|
368 | 379 | profile.remove_role(RoleName=role.name) |
|
369 | 380 | |
|
370 | 381 | print('deleting instance profile %s' % profile.name) |
@@ -404,7 +415,8 b' def ensure_iam_state(iamclient, iamresou' | |||
|
404 | 415 | print('creating IAM instance profile %s' % actual) |
|
405 | 416 | |
|
406 | 417 | profile = iamresource.create_instance_profile( |
|
407 |
InstanceProfileName=actual |
|
|
418 | InstanceProfileName=actual | |
|
419 | ) | |
|
408 | 420 | remote_profiles[name] = profile |
|
409 | 421 | |
|
410 | 422 | waiter = iamclient.get_waiter('instance_profile_exists') |
@@ -453,23 +465,12 b' def find_image(ec2resource, owner_id, na' | |||
|
453 | 465 | |
|
454 | 466 | images = ec2resource.images.filter( |
|
455 | 467 | Filters=[ |
|
456 | { | |
|
457 | 'Name': 'owner-id', | |
|
458 |
|
|
|
459 | }, | |
|
460 |
|
|
|
461 | 'Name': 'state', | |
|
462 | 'Values': ['available'], | |
|
463 | }, | |
|
464 | { | |
|
465 | 'Name': 'image-type', | |
|
466 | 'Values': ['machine'], | |
|
467 | }, | |
|
468 | { | |
|
469 | 'Name': 'name', | |
|
470 | 'Values': [name], | |
|
471 | }, | |
|
472 | ]) | |
|
468 | {'Name': 'owner-id', 'Values': [owner_id],}, | |
|
469 | {'Name': 'state', 'Values': ['available'],}, | |
|
470 | {'Name': 'image-type', 'Values': ['machine'],}, | |
|
471 | {'Name': 'name', 'Values': [name],}, | |
|
472 | ] | |
|
473 | ) | |
|
473 | 474 | |
|
474 | 475 | for image in images: |
|
475 | 476 | return image |
@@ -507,13 +508,10 b' def ensure_security_groups(ec2resource, ' | |||
|
507 | 508 | print('adding security group %s' % actual) |
|
508 | 509 | |
|
509 | 510 | group_res = ec2resource.create_security_group( |
|
510 | Description=group['description'], | |
|
511 | GroupName=actual, | |
|
511 | Description=group['description'], GroupName=actual, | |
|
512 | 512 | ) |
|
513 | 513 | |
|
514 | group_res.authorize_ingress( | |
|
515 | IpPermissions=group['ingress'], | |
|
516 | ) | |
|
514 | group_res.authorize_ingress(IpPermissions=group['ingress'],) | |
|
517 | 515 | |
|
518 | 516 | security_groups[name] = group_res |
|
519 | 517 | |
@@ -577,8 +575,10 b' def wait_for_ip_addresses(instances):' | |||
|
577 | 575 | instance.reload() |
|
578 | 576 | continue |
|
579 | 577 | |
|
580 | print('public IP address for %s: %s' % ( | |
|
581 | instance.id, instance.public_ip_address)) | |
|
578 | print( | |
|
579 | 'public IP address for %s: %s' | |
|
580 | % (instance.id, instance.public_ip_address) | |
|
581 | ) | |
|
582 | 582 | break |
|
583 | 583 | |
|
584 | 584 | |
@@ -603,10 +603,7 b' def wait_for_ssm(ssmclient, instances):' | |||
|
603 | 603 | while True: |
|
604 | 604 | res = ssmclient.describe_instance_information( |
|
605 | 605 | Filters=[ |
|
606 | { | |
|
607 | 'Key': 'InstanceIds', | |
|
608 | 'Values': [i.id for i in instances], | |
|
609 | }, | |
|
606 | {'Key': 'InstanceIds', 'Values': [i.id for i in instances],}, | |
|
610 | 607 | ], |
|
611 | 608 | ) |
|
612 | 609 | |
@@ -628,9 +625,7 b' def run_ssm_command(ssmclient, instances' | |||
|
628 | 625 | InstanceIds=[i.id for i in instances], |
|
629 | 626 | DocumentName=document_name, |
|
630 | 627 | Parameters=parameters, |
|
631 | CloudWatchOutputConfig={ | |
|
632 | 'CloudWatchOutputEnabled': True, | |
|
633 | }, | |
|
628 | CloudWatchOutputConfig={'CloudWatchOutputEnabled': True,}, | |
|
634 | 629 | ) |
|
635 | 630 | |
|
636 | 631 | command_id = res['Command']['CommandId'] |
@@ -639,8 +634,7 b' def run_ssm_command(ssmclient, instances' | |||
|
639 | 634 | while True: |
|
640 | 635 | try: |
|
641 | 636 | res = ssmclient.get_command_invocation( |
|
642 | CommandId=command_id, | |
|
643 | InstanceId=instance.id, | |
|
637 | CommandId=command_id, InstanceId=instance.id, | |
|
644 | 638 | ) |
|
645 | 639 | except botocore.exceptions.ClientError as e: |
|
646 | 640 | if e.response['Error']['Code'] == 'InvocationDoesNotExist': |
@@ -655,8 +649,9 b' def run_ssm_command(ssmclient, instances' | |||
|
655 | 649 | elif res['Status'] in ('Pending', 'InProgress', 'Delayed'): |
|
656 | 650 | time.sleep(2) |
|
657 | 651 | else: |
|
658 |
raise Exception( |
|
|
659 |
instance.id, res['Status']) |
|
|
652 | raise Exception( | |
|
653 | 'command failed on %s: %s' % (instance.id, res['Status']) | |
|
654 | ) | |
|
660 | 655 | |
|
661 | 656 | |
|
662 | 657 | @contextlib.contextmanager |
@@ -711,10 +706,12 b' def create_temp_windows_ec2_instances(c:' | |||
|
711 | 706 | config['IamInstanceProfile'] = { |
|
712 | 707 | 'Name': 'hg-ephemeral-ec2-1', |
|
713 | 708 | } |
|
714 |
config.setdefault('TagSpecifications', []).append( |
|
|
709 | config.setdefault('TagSpecifications', []).append( | |
|
710 | { | |
|
715 | 711 | 'ResourceType': 'instance', |
|
716 | 712 | 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}], |
|
717 |
} |
|
|
713 | } | |
|
714 | ) | |
|
718 | 715 | config['UserData'] = WINDOWS_USER_DATA % password |
|
719 | 716 | |
|
720 | 717 | with temporary_ec2_instances(c.ec2resource, config) as instances: |
@@ -723,7 +720,9 b' def create_temp_windows_ec2_instances(c:' | |||
|
723 | 720 | print('waiting for Windows Remote Management service...') |
|
724 | 721 | |
|
725 | 722 | for instance in instances: |
|
726 | client = wait_for_winrm(instance.public_ip_address, 'Administrator', password) | |
|
723 | client = wait_for_winrm( | |
|
724 | instance.public_ip_address, 'Administrator', password | |
|
725 | ) | |
|
727 | 726 | print('established WinRM connection to %s' % instance.id) |
|
728 | 727 | instance.winrm_client = client |
|
729 | 728 | |
@@ -748,14 +747,17 b' def find_and_reconcile_image(ec2resource' | |||
|
748 | 747 | # Store a reference to a good image so it can be returned one the |
|
749 | 748 | # image state is reconciled. |
|
750 | 749 | images = ec2resource.images.filter( |
|
751 |
Filters=[{'Name': 'name', 'Values': [name]}] |
|
|
750 | Filters=[{'Name': 'name', 'Values': [name]}] | |
|
751 | ) | |
|
752 | 752 | |
|
753 | 753 | existing_image = None |
|
754 | 754 | |
|
755 | 755 | for image in images: |
|
756 | 756 | if image.tags is None: |
|
757 | print('image %s for %s lacks required tags; removing' % ( | |
|
758 | image.id, image.name)) | |
|
757 | print( | |
|
758 | 'image %s for %s lacks required tags; removing' | |
|
759 | % (image.id, image.name) | |
|
760 | ) | |
|
759 | 761 | remove_ami(ec2resource, image) |
|
760 | 762 | else: |
|
761 | 763 | tags = {t['Key']: t['Value'] for t in image.tags} |
@@ -763,15 +765,18 b' def find_and_reconcile_image(ec2resource' | |||
|
763 | 765 | if tags.get('HGIMAGEFINGERPRINT') == fingerprint: |
|
764 | 766 | existing_image = image |
|
765 | 767 | else: |
|
766 | print('image %s for %s has wrong fingerprint; removing' % ( | |
|
767 | image.id, image.name)) | |
|
768 | print( | |
|
769 | 'image %s for %s has wrong fingerprint; removing' | |
|
770 | % (image.id, image.name) | |
|
771 | ) | |
|
768 | 772 | remove_ami(ec2resource, image) |
|
769 | 773 | |
|
770 | 774 | return existing_image |
|
771 | 775 | |
|
772 | 776 | |
|
773 | def create_ami_from_instance(ec2client, instance, name, description, | |
|
774 | fingerprint): | |
|
777 | def create_ami_from_instance( | |
|
778 | ec2client, instance, name, description, fingerprint | |
|
779 | ): | |
|
775 | 780 | """Create an AMI from a running instance. |
|
776 | 781 | |
|
777 | 782 | Returns the ``ec2resource.Image`` representing the created AMI. |
@@ -779,29 +784,19 b' def create_ami_from_instance(ec2client, ' | |||
|
779 | 784 | instance.stop() |
|
780 | 785 | |
|
781 | 786 | ec2client.get_waiter('instance_stopped').wait( |
|
782 | InstanceIds=[instance.id], | |
|
783 | WaiterConfig={ | |
|
784 | 'Delay': 5, | |
|
785 | }) | |
|
787 | InstanceIds=[instance.id], WaiterConfig={'Delay': 5,} | |
|
788 | ) | |
|
786 | 789 | print('%s is stopped' % instance.id) |
|
787 | 790 | |
|
788 | image = instance.create_image( | |
|
789 | Name=name, | |
|
790 | Description=description, | |
|
791 | ) | |
|
791 | image = instance.create_image(Name=name, Description=description,) | |
|
792 | 792 | |
|
793 |
image.create_tags( |
|
|
794 | { | |
|
795 | 'Key': 'HGIMAGEFINGERPRINT', | |
|
796 | 'Value': fingerprint, | |
|
797 | }, | |
|
798 | ]) | |
|
793 | image.create_tags( | |
|
794 | Tags=[{'Key': 'HGIMAGEFINGERPRINT', 'Value': fingerprint,},] | |
|
795 | ) | |
|
799 | 796 | |
|
800 | 797 | print('waiting for image %s' % image.id) |
|
801 | 798 | |
|
802 | ec2client.get_waiter('image_available').wait( | |
|
803 | ImageIds=[image.id], | |
|
804 | ) | |
|
799 | ec2client.get_waiter('image_available').wait(ImageIds=[image.id],) | |
|
805 | 800 | |
|
806 | 801 | print('image %s available as %s' % (image.id, image.name)) |
|
807 | 802 | |
@@ -827,9 +822,7 b' def ensure_linux_dev_ami(c: AWSConnectio' | |||
|
827 | 822 | ssh_username = 'admin' |
|
828 | 823 | elif distro == 'debian10': |
|
829 | 824 | image = find_image( |
|
830 | ec2resource, | |
|
831 | DEBIAN_ACCOUNT_ID_2, | |
|
832 | 'debian-10-amd64-20190909-10', | |
|
825 | ec2resource, DEBIAN_ACCOUNT_ID_2, 'debian-10-amd64-20190909-10', | |
|
833 | 826 | ) |
|
834 | 827 | ssh_username = 'admin' |
|
835 | 828 | elif distro == 'ubuntu18.04': |
@@ -871,10 +864,12 b' def ensure_linux_dev_ami(c: AWSConnectio' | |||
|
871 | 864 | 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id], |
|
872 | 865 | } |
|
873 | 866 | |
|
874 | requirements2_path = (pathlib.Path(__file__).parent.parent / | |
|
875 | 'linux-requirements-py2.txt') | |
|
876 | requirements3_path = (pathlib.Path(__file__).parent.parent / | |
|
877 | 'linux-requirements-py3.txt') | |
|
867 | requirements2_path = ( | |
|
868 | pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt' | |
|
869 | ) | |
|
870 | requirements3_path = ( | |
|
871 | pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt' | |
|
872 | ) | |
|
878 | 873 | with requirements2_path.open('r', encoding='utf-8') as fh: |
|
879 | 874 | requirements2 = fh.read() |
|
880 | 875 | with requirements3_path.open('r', encoding='utf-8') as fh: |
@@ -882,12 +877,14 b' def ensure_linux_dev_ami(c: AWSConnectio' | |||
|
882 | 877 | |
|
883 | 878 | # Compute a deterministic fingerprint to determine whether image needs to |
|
884 | 879 | # be regenerated. |
|
885 |
fingerprint = resolve_fingerprint( |
|
|
880 | fingerprint = resolve_fingerprint( | |
|
881 | { | |
|
886 | 882 | 'instance_config': config, |
|
887 | 883 | 'bootstrap_script': BOOTSTRAP_DEBIAN, |
|
888 | 884 | 'requirements_py2': requirements2, |
|
889 | 885 | 'requirements_py3': requirements3, |
|
890 |
} |
|
|
886 | } | |
|
887 | ) | |
|
891 | 888 | |
|
892 | 889 | existing_image = find_and_reconcile_image(ec2resource, name, fingerprint) |
|
893 | 890 | |
@@ -902,9 +899,11 b' def ensure_linux_dev_ami(c: AWSConnectio' | |||
|
902 | 899 | instance = instances[0] |
|
903 | 900 | |
|
904 | 901 | client = wait_for_ssh( |
|
905 |
instance.public_ip_address, |
|
|
902 | instance.public_ip_address, | |
|
903 | 22, | |
|
906 | 904 | username=ssh_username, |
|
907 |
key_filename=str(c.key_pair_path_private('automation')) |
|
|
905 | key_filename=str(c.key_pair_path_private('automation')), | |
|
906 | ) | |
|
908 | 907 | |
|
909 | 908 | home = '/home/%s' % ssh_username |
|
910 | 909 | |
@@ -926,8 +925,9 b' def ensure_linux_dev_ami(c: AWSConnectio' | |||
|
926 | 925 | fh.chmod(0o0700) |
|
927 | 926 | |
|
928 | 927 | print('executing bootstrap') |
|
929 |
chan, stdin, stdout = ssh_exec_command( |
|
|
930 |
|
|
|
928 | chan, stdin, stdout = ssh_exec_command( | |
|
929 | client, '%s/bootstrap' % home | |
|
930 | ) | |
|
931 | 931 | stdin.close() |
|
932 | 932 | |
|
933 | 933 | for line in stdout: |
@@ -937,17 +937,28 b' def ensure_linux_dev_ami(c: AWSConnectio' | |||
|
937 | 937 | if res: |
|
938 | 938 | raise Exception('non-0 exit from bootstrap: %d' % res) |
|
939 | 939 | |
|
940 | print('bootstrap completed; stopping %s to create %s' % ( | |
|
941 | instance.id, name)) | |
|
940 | print( | |
|
941 | 'bootstrap completed; stopping %s to create %s' | |
|
942 | % (instance.id, name) | |
|
943 | ) | |
|
942 | 944 | |
|
943 |
return create_ami_from_instance( |
|
|
945 | return create_ami_from_instance( | |
|
946 | ec2client, | |
|
947 | instance, | |
|
948 | name, | |
|
944 | 949 |
|
|
945 |
|
|
|
950 | fingerprint, | |
|
951 | ) | |
|
946 | 952 | |
|
947 | 953 | |
|
948 | 954 | @contextlib.contextmanager |
|
949 |
def temporary_linux_dev_instances( |
|
|
950 | prefix='hg-', ensure_extra_volume=False): | |
|
955 | def temporary_linux_dev_instances( | |
|
956 | c: AWSConnection, | |
|
957 | image, | |
|
958 | instance_type, | |
|
959 | prefix='hg-', | |
|
960 | ensure_extra_volume=False, | |
|
961 | ): | |
|
951 | 962 | """Create temporary Linux development EC2 instances. |
|
952 | 963 | |
|
953 | 964 | Context manager resolves to a list of ``ec2.Instance`` that were created |
@@ -979,8 +990,9 b' def temporary_linux_dev_instances(c: AWS' | |||
|
979 | 990 | |
|
980 | 991 | # This is not an exhaustive list of instance types having instance storage. |
|
981 | 992 | # But |
|
982 | if (ensure_extra_volume | |
|
983 |
|
|
|
993 | if ensure_extra_volume and not instance_type.startswith( | |
|
994 | tuple(INSTANCE_TYPES_WITH_STORAGE) | |
|
995 | ): | |
|
984 | 996 | main_device = block_device_mappings[0]['DeviceName'] |
|
985 | 997 | |
|
986 | 998 | if main_device == 'xvda': |
@@ -988,17 +1000,20 b' def temporary_linux_dev_instances(c: AWS' | |||
|
988 | 1000 | elif main_device == '/dev/sda1': |
|
989 | 1001 | second_device = '/dev/sdb' |
|
990 | 1002 | else: |
|
991 | raise ValueError('unhandled primary EBS device name: %s' % | |
|
992 |
|
|
|
1003 | raise ValueError( | |
|
1004 | 'unhandled primary EBS device name: %s' % main_device | |
|
1005 | ) | |
|
993 | 1006 | |
|
994 |
block_device_mappings.append( |
|
|
1007 | block_device_mappings.append( | |
|
1008 | { | |
|
995 | 1009 | 'DeviceName': second_device, |
|
996 | 1010 | 'Ebs': { |
|
997 | 1011 | 'DeleteOnTermination': True, |
|
998 | 1012 | 'VolumeSize': 8, |
|
999 | 1013 | 'VolumeType': 'gp2', |
|
1014 | }, | |
|
1000 | 1015 | } |
|
1001 |
|
|
|
1016 | ) | |
|
1002 | 1017 | |
|
1003 | 1018 | config = { |
|
1004 | 1019 | 'BlockDeviceMappings': block_device_mappings, |
@@ -1019,9 +1034,11 b' def temporary_linux_dev_instances(c: AWS' | |||
|
1019 | 1034 | |
|
1020 | 1035 | for instance in instances: |
|
1021 | 1036 | client = wait_for_ssh( |
|
1022 |
instance.public_ip_address, |
|
|
1037 | instance.public_ip_address, | |
|
1038 | 22, | |
|
1023 | 1039 | username='hg', |
|
1024 |
key_filename=ssh_private_key_path |
|
|
1040 | key_filename=ssh_private_key_path, | |
|
1041 | ) | |
|
1025 | 1042 | |
|
1026 | 1043 | instance.ssh_client = client |
|
1027 | 1044 | instance.ssh_private_key_path = ssh_private_key_path |
@@ -1033,8 +1050,9 b' def temporary_linux_dev_instances(c: AWS' | |||
|
1033 | 1050 | instance.ssh_client.close() |
|
1034 | 1051 | |
|
1035 | 1052 | |
|
1036 |
def ensure_windows_dev_ami( |
|
|
1037 |
|
|
|
1053 | def ensure_windows_dev_ami( | |
|
1054 | c: AWSConnection, prefix='hg-', base_image_name=WINDOWS_BASE_IMAGE_NAME | |
|
1055 | ): | |
|
1038 | 1056 | """Ensure Windows Development AMI is available and up-to-date. |
|
1039 | 1057 | |
|
1040 | 1058 | If necessary, a modern AMI will be built by starting a temporary EC2 |
@@ -1100,13 +1118,15 b' def ensure_windows_dev_ami(c: AWSConnect' | |||
|
1100 | 1118 | |
|
1101 | 1119 | # Compute a deterministic fingerprint to determine whether image needs |
|
1102 | 1120 | # to be regenerated. |
|
1103 |
fingerprint = resolve_fingerprint( |
|
|
1121 | fingerprint = resolve_fingerprint( | |
|
1122 | { | |
|
1104 | 1123 | 'instance_config': config, |
|
1105 | 1124 | 'user_data': WINDOWS_USER_DATA, |
|
1106 | 1125 | 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL, |
|
1107 | 1126 | 'bootstrap_commands': commands, |
|
1108 | 1127 | 'base_image_name': base_image_name, |
|
1109 |
} |
|
|
1128 | } | |
|
1129 | ) | |
|
1110 | 1130 | |
|
1111 | 1131 | existing_image = find_and_reconcile_image(ec2resource, name, fingerprint) |
|
1112 | 1132 | |
@@ -1131,9 +1151,7 b' def ensure_windows_dev_ami(c: AWSConnect' | |||
|
1131 | 1151 | ssmclient, |
|
1132 | 1152 | [instance], |
|
1133 | 1153 | 'AWS-RunPowerShellScript', |
|
1134 | { | |
|
1135 | 'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'), | |
|
1136 | }, | |
|
1154 | {'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),}, | |
|
1137 | 1155 | ) |
|
1138 | 1156 | |
|
1139 | 1157 | # Reboot so all updates are fully applied. |
@@ -1145,10 +1163,8 b' def ensure_windows_dev_ami(c: AWSConnect' | |||
|
1145 | 1163 | print('rebooting instance %s' % instance.id) |
|
1146 | 1164 | instance.stop() |
|
1147 | 1165 | ec2client.get_waiter('instance_stopped').wait( |
|
1148 | InstanceIds=[instance.id], | |
|
1149 | WaiterConfig={ | |
|
1150 | 'Delay': 5, | |
|
1151 | }) | |
|
1166 | InstanceIds=[instance.id], WaiterConfig={'Delay': 5,} | |
|
1167 | ) | |
|
1152 | 1168 | |
|
1153 | 1169 | instance.start() |
|
1154 | 1170 | wait_for_ip_addresses([instance]) |
@@ -1159,8 +1175,11 b' def ensure_windows_dev_ami(c: AWSConnect' | |||
|
1159 | 1175 | # TODO figure out a workaround. |
|
1160 | 1176 | |
|
1161 | 1177 | print('waiting for Windows Remote Management to come back...') |
|
1162 | client = wait_for_winrm(instance.public_ip_address, 'Administrator', | |
|
1163 | c.automation.default_password()) | |
|
1178 | client = wait_for_winrm( | |
|
1179 | instance.public_ip_address, | |
|
1180 | 'Administrator', | |
|
1181 | c.automation.default_password(), | |
|
1182 | ) | |
|
1164 | 1183 | print('established WinRM connection to %s' % instance.id) |
|
1165 | 1184 | instance.winrm_client = client |
|
1166 | 1185 | |
@@ -1168,14 +1187,23 b' def ensure_windows_dev_ami(c: AWSConnect' | |||
|
1168 | 1187 | run_powershell(instance.winrm_client, '\n'.join(commands)) |
|
1169 | 1188 | |
|
1170 | 1189 | print('bootstrap completed; stopping %s to create image' % instance.id) |
|
1171 |
return create_ami_from_instance( |
|
|
1190 | return create_ami_from_instance( | |
|
1191 | ec2client, | |
|
1192 | instance, | |
|
1193 | name, | |
|
1172 | 1194 |
|
|
1173 |
|
|
|
1195 | fingerprint, | |
|
1196 | ) | |
|
1174 | 1197 | |
|
1175 | 1198 | |
|
1176 | 1199 | @contextlib.contextmanager |
|
1177 |
def temporary_windows_dev_instances( |
|
|
1178 | prefix='hg-', disable_antivirus=False): | |
|
1200 | def temporary_windows_dev_instances( | |
|
1201 | c: AWSConnection, | |
|
1202 | image, | |
|
1203 | instance_type, | |
|
1204 | prefix='hg-', | |
|
1205 | disable_antivirus=False, | |
|
1206 | ): | |
|
1179 | 1207 | """Create a temporary Windows development EC2 instance. |
|
1180 | 1208 | |
|
1181 | 1209 | Context manager resolves to the list of ``EC2.Instance`` that were created. |
@@ -1205,6 +1233,7 b' def temporary_windows_dev_instances(c: A' | |||
|
1205 | 1233 | for instance in instances: |
|
1206 | 1234 | run_powershell( |
|
1207 | 1235 | instance.winrm_client, |
|
1208 |
'Set-MpPreference -DisableRealtimeMonitoring $true' |
|
|
1236 | 'Set-MpPreference -DisableRealtimeMonitoring $true', | |
|
1237 | ) | |
|
1209 | 1238 | |
|
1210 | 1239 | yield instances |
@@ -22,12 +22,15 b' from . import (' | |||
|
22 | 22 | ) |
|
23 | 23 | |
|
24 | 24 | |
|
25 | SOURCE_ROOT = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent.parent | |
|
25 | SOURCE_ROOT = pathlib.Path( | |
|
26 | os.path.abspath(__file__) | |
|
27 | ).parent.parent.parent.parent | |
|
26 | 28 | DIST_PATH = SOURCE_ROOT / 'dist' |
|
27 | 29 | |
|
28 | 30 | |
|
29 | def bootstrap_linux_dev(hga: HGAutomation, aws_region, distros=None, | |
|
30 | parallel=False): | |
|
31 | def bootstrap_linux_dev( | |
|
32 | hga: HGAutomation, aws_region, distros=None, parallel=False | |
|
33 | ): | |
|
31 | 34 | c = hga.aws_connection(aws_region) |
|
32 | 35 | |
|
33 | 36 | if distros: |
@@ -59,8 +62,9 b' def bootstrap_windows_dev(hga: HGAutomat' | |||
|
59 | 62 | print('Windows development AMI available as %s' % image.id) |
|
60 | 63 | |
|
61 | 64 | |
|
62 | def build_inno(hga: HGAutomation, aws_region, arch, revision, version, | |
|
63 | base_image_name): | |
|
65 | def build_inno( | |
|
66 | hga: HGAutomation, aws_region, arch, revision, version, base_image_name | |
|
67 | ): | |
|
64 | 68 | c = hga.aws_connection(aws_region) |
|
65 | 69 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
66 | 70 | DIST_PATH.mkdir(exist_ok=True) |
@@ -71,13 +75,14 b' def build_inno(hga: HGAutomation, aws_re' | |||
|
71 | 75 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
72 | 76 | |
|
73 | 77 | for a in arch: |
|
74 |
windows.build_inno_installer( |
|
|
75 | DIST_PATH, | |
|
76 | version=version) | |
|
78 | windows.build_inno_installer( | |
|
79 | instance.winrm_client, a, DIST_PATH, version=version | |
|
80 | ) | |
|
77 | 81 | |
|
78 | 82 | |
|
79 | def build_wix(hga: HGAutomation, aws_region, arch, revision, version, | |
|
80 | base_image_name): | |
|
83 | def build_wix( | |
|
84 | hga: HGAutomation, aws_region, arch, revision, version, base_image_name | |
|
85 | ): | |
|
81 | 86 | c = hga.aws_connection(aws_region) |
|
82 | 87 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
83 | 88 | DIST_PATH.mkdir(exist_ok=True) |
@@ -88,12 +93,14 b' def build_wix(hga: HGAutomation, aws_reg' | |||
|
88 | 93 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
89 | 94 | |
|
90 | 95 | for a in arch: |
|
91 |
windows.build_wix_installer( |
|
|
92 |
|
|
|
96 | windows.build_wix_installer( | |
|
97 | instance.winrm_client, a, DIST_PATH, version=version | |
|
98 | ) | |
|
93 | 99 | |
|
94 | 100 | |
|
95 | def build_windows_wheel(hga: HGAutomation, aws_region, arch, revision, | |
|
96 | base_image_name): | |
|
101 | def build_windows_wheel( | |
|
102 | hga: HGAutomation, aws_region, arch, revision, base_image_name | |
|
103 | ): | |
|
97 | 104 | c = hga.aws_connection(aws_region) |
|
98 | 105 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
99 | 106 | DIST_PATH.mkdir(exist_ok=True) |
@@ -107,8 +114,9 b' def build_windows_wheel(hga: HGAutomatio' | |||
|
107 | 114 | windows.build_wheel(instance.winrm_client, a, DIST_PATH) |
|
108 | 115 | |
|
109 | 116 | |
|
110 |
def build_all_windows_packages( |
|
|
111 | version, base_image_name): | |
|
117 | def build_all_windows_packages( | |
|
118 | hga: HGAutomation, aws_region, revision, version, base_image_name | |
|
119 | ): | |
|
112 | 120 | c = hga.aws_connection(aws_region) |
|
113 | 121 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
114 | 122 | DIST_PATH.mkdir(exist_ok=True) |
@@ -124,11 +132,13 b' def build_all_windows_packages(hga: HGAu' | |||
|
124 | 132 | windows.purge_hg(winrm_client) |
|
125 | 133 | windows.build_wheel(winrm_client, arch, DIST_PATH) |
|
126 | 134 | windows.purge_hg(winrm_client) |
|
127 |
windows.build_inno_installer( |
|
|
128 | version=version) | |
|
135 | windows.build_inno_installer( | |
|
136 | winrm_client, arch, DIST_PATH, version=version | |
|
137 | ) | |
|
129 | 138 | windows.purge_hg(winrm_client) |
|
130 |
windows.build_wix_installer( |
|
|
131 | version=version) | |
|
139 | windows.build_wix_installer( | |
|
140 | winrm_client, arch, DIST_PATH, version=version | |
|
141 | ) | |
|
132 | 142 | |
|
133 | 143 | |
|
134 | 144 | def terminate_ec2_instances(hga: HGAutomation, aws_region): |
@@ -141,8 +151,15 b' def purge_ec2_resources(hga: HGAutomatio' | |||
|
141 | 151 | aws.remove_resources(c) |
|
142 | 152 | |
|
143 | 153 | |
|
144 | def run_tests_linux(hga: HGAutomation, aws_region, instance_type, | |
|
145 | python_version, test_flags, distro, filesystem): | |
|
154 | def run_tests_linux( | |
|
155 | hga: HGAutomation, | |
|
156 | aws_region, | |
|
157 | instance_type, | |
|
158 | python_version, | |
|
159 | test_flags, | |
|
160 | distro, | |
|
161 | filesystem, | |
|
162 | ): | |
|
146 | 163 | c = hga.aws_connection(aws_region) |
|
147 | 164 | image = aws.ensure_linux_dev_ami(c, distro=distro) |
|
148 | 165 | |
@@ -151,17 +168,17 b' def run_tests_linux(hga: HGAutomation, a' | |||
|
151 | 168 | ensure_extra_volume = filesystem not in ('default', 'tmpfs') |
|
152 | 169 | |
|
153 | 170 | with aws.temporary_linux_dev_instances( |
|
154 | c, image, instance_type, | |
|
155 | ensure_extra_volume=ensure_extra_volume) as insts: | |
|
171 | c, image, instance_type, ensure_extra_volume=ensure_extra_volume | |
|
172 | ) as insts: | |
|
156 | 173 | |
|
157 | 174 | instance = insts[0] |
|
158 | 175 | |
|
159 |
linux.prepare_exec_environment( |
|
|
160 | filesystem=filesystem) | |
|
176 | linux.prepare_exec_environment( | |
|
177 | instance.ssh_client, filesystem=filesystem | |
|
178 | ) | |
|
161 | 179 | linux.synchronize_hg(SOURCE_ROOT, instance, '.') |
|
162 | 180 | t_prepared = time.time() |
|
163 | linux.run_tests(instance.ssh_client, python_version, | |
|
164 | test_flags) | |
|
181 | linux.run_tests(instance.ssh_client, python_version, test_flags) | |
|
165 | 182 | t_done = time.time() |
|
166 | 183 | |
|
167 | 184 | t_setup = t_prepared - t_start |
@@ -169,29 +186,48 b' def run_tests_linux(hga: HGAutomation, a' | |||
|
169 | 186 | |
|
170 | 187 | print( |
|
171 | 188 | 'total time: %.1fs; setup: %.1fs; tests: %.1fs; setup overhead: %.1f%%' |
|
172 |
% (t_all, t_setup, t_done - t_prepared, t_setup / t_all * 100.0) |
|
|
189 | % (t_all, t_setup, t_done - t_prepared, t_setup / t_all * 100.0) | |
|
190 | ) | |
|
173 | 191 | |
|
174 | 192 | |
|
175 | def run_tests_windows(hga: HGAutomation, aws_region, instance_type, | |
|
176 | python_version, arch, test_flags, base_image_name): | |
|
193 | def run_tests_windows( | |
|
194 | hga: HGAutomation, | |
|
195 | aws_region, | |
|
196 | instance_type, | |
|
197 | python_version, | |
|
198 | arch, | |
|
199 | test_flags, | |
|
200 | base_image_name, | |
|
201 | ): | |
|
177 | 202 | c = hga.aws_connection(aws_region) |
|
178 | 203 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
179 | 204 | |
|
180 |
with aws.temporary_windows_dev_instances( |
|
|
181 | disable_antivirus=True) as insts: | |
|
205 | with aws.temporary_windows_dev_instances( | |
|
206 | c, image, instance_type, disable_antivirus=True | |
|
207 | ) as insts: | |
|
182 | 208 | instance = insts[0] |
|
183 | 209 | |
|
184 | 210 | windows.synchronize_hg(SOURCE_ROOT, '.', instance) |
|
185 | windows.run_tests(instance.winrm_client, python_version, arch, | |
|
186 | test_flags) | |
|
211 | windows.run_tests( | |
|
212 | instance.winrm_client, python_version, arch, test_flags | |
|
213 | ) | |
|
187 | 214 | |
|
188 | 215 | |
|
189 | def publish_windows_artifacts(hg: HGAutomation, aws_region, version: str, | |
|
190 | pypi: bool, mercurial_scm_org: bool, | |
|
191 | ssh_username: str): | |
|
192 | windows.publish_artifacts(DIST_PATH, version, | |
|
193 | pypi=pypi, mercurial_scm_org=mercurial_scm_org, | |
|
194 | ssh_username=ssh_username) | |
|
216 | def publish_windows_artifacts( | |
|
217 | hg: HGAutomation, | |
|
218 | aws_region, | |
|
219 | version: str, | |
|
220 | pypi: bool, | |
|
221 | mercurial_scm_org: bool, | |
|
222 | ssh_username: str, | |
|
223 | ): | |
|
224 | windows.publish_artifacts( | |
|
225 | DIST_PATH, | |
|
226 | version, | |
|
227 | pypi=pypi, | |
|
228 | mercurial_scm_org=mercurial_scm_org, | |
|
229 | ssh_username=ssh_username, | |
|
230 | ) | |
|
195 | 231 | |
|
196 | 232 | |
|
197 | 233 | def run_try(hga: HGAutomation, aws_region: str, rev: str): |
@@ -208,25 +244,21 b' def get_parser():' | |||
|
208 | 244 | help='Path for local state files', |
|
209 | 245 | ) |
|
210 | 246 | parser.add_argument( |
|
211 | '--aws-region', | |
|
212 | help='AWS region to use', | |
|
213 | default='us-west-2', | |
|
247 | '--aws-region', help='AWS region to use', default='us-west-2', | |
|
214 | 248 | ) |
|
215 | 249 | |
|
216 | 250 | subparsers = parser.add_subparsers() |
|
217 | 251 | |
|
218 | 252 | sp = subparsers.add_parser( |
|
219 | 'bootstrap-linux-dev', | |
|
220 | help='Bootstrap Linux development environments', | |
|
253 | 'bootstrap-linux-dev', help='Bootstrap Linux development environments', | |
|
221 | 254 | ) |
|
222 | 255 | sp.add_argument( |
|
223 | '--distros', | |
|
224 | help='Comma delimited list of distros to bootstrap', | |
|
256 | '--distros', help='Comma delimited list of distros to bootstrap', | |
|
225 | 257 | ) |
|
226 | 258 | sp.add_argument( |
|
227 | 259 | '--parallel', |
|
228 | 260 | action='store_true', |
|
229 | help='Generate AMIs in parallel (not CTRL-c safe)' | |
|
261 | help='Generate AMIs in parallel (not CTRL-c safe)', | |
|
230 | 262 | ) |
|
231 | 263 | sp.set_defaults(func=bootstrap_linux_dev) |
|
232 | 264 | |
@@ -242,17 +274,13 b' def get_parser():' | |||
|
242 | 274 | sp.set_defaults(func=bootstrap_windows_dev) |
|
243 | 275 | |
|
244 | 276 | sp = subparsers.add_parser( |
|
245 | 'build-all-windows-packages', | |
|
246 | help='Build all Windows packages', | |
|
277 | 'build-all-windows-packages', help='Build all Windows packages', | |
|
247 | 278 | ) |
|
248 | 279 | sp.add_argument( |
|
249 | '--revision', | |
|
250 | help='Mercurial revision to build', | |
|
251 | default='.', | |
|
280 | '--revision', help='Mercurial revision to build', default='.', | |
|
252 | 281 | ) |
|
253 | 282 | sp.add_argument( |
|
254 | '--version', | |
|
255 | help='Mercurial version string to use', | |
|
283 | '--version', help='Mercurial version string to use', | |
|
256 | 284 | ) |
|
257 | 285 | sp.add_argument( |
|
258 | 286 | '--base-image-name', |
@@ -262,8 +290,7 b' def get_parser():' | |||
|
262 | 290 | sp.set_defaults(func=build_all_windows_packages) |
|
263 | 291 | |
|
264 | 292 | sp = subparsers.add_parser( |
|
265 | 'build-inno', | |
|
266 | help='Build Inno Setup installer(s)', | |
|
293 | 'build-inno', help='Build Inno Setup installer(s)', | |
|
267 | 294 | ) |
|
268 | 295 | sp.add_argument( |
|
269 | 296 | '--arch', |
@@ -273,13 +300,10 b' def get_parser():' | |||
|
273 | 300 | default=['x64'], |
|
274 | 301 | ) |
|
275 | 302 | sp.add_argument( |
|
276 | '--revision', | |
|
277 | help='Mercurial revision to build', | |
|
278 | default='.', | |
|
303 | '--revision', help='Mercurial revision to build', default='.', | |
|
279 | 304 | ) |
|
280 | 305 | sp.add_argument( |
|
281 | '--version', | |
|
282 | help='Mercurial version string to use in installer', | |
|
306 | '--version', help='Mercurial version string to use in installer', | |
|
283 | 307 | ) |
|
284 | 308 | sp.add_argument( |
|
285 | 309 | '--base-image-name', |
@@ -289,8 +313,7 b' def get_parser():' | |||
|
289 | 313 | sp.set_defaults(func=build_inno) |
|
290 | 314 | |
|
291 | 315 | sp = subparsers.add_parser( |
|
292 | 'build-windows-wheel', | |
|
293 | help='Build Windows wheel(s)', | |
|
316 | 'build-windows-wheel', help='Build Windows wheel(s)', | |
|
294 | 317 | ) |
|
295 | 318 | sp.add_argument( |
|
296 | 319 | '--arch', |
@@ -300,9 +323,7 b' def get_parser():' | |||
|
300 | 323 | default=['x64'], |
|
301 | 324 | ) |
|
302 | 325 | sp.add_argument( |
|
303 | '--revision', | |
|
304 | help='Mercurial revision to build', | |
|
305 | default='.', | |
|
326 | '--revision', help='Mercurial revision to build', default='.', | |
|
306 | 327 | ) |
|
307 | 328 | sp.add_argument( |
|
308 | 329 | '--base-image-name', |
@@ -311,10 +332,7 b' def get_parser():' | |||
|
311 | 332 | ) |
|
312 | 333 | sp.set_defaults(func=build_windows_wheel) |
|
313 | 334 | |
|
314 | sp = subparsers.add_parser( | |
|
315 | 'build-wix', | |
|
316 | help='Build WiX installer(s)' | |
|
317 | ) | |
|
335 | sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)') | |
|
318 | 336 | sp.add_argument( |
|
319 | 337 | '--arch', |
|
320 | 338 | help='Architecture to build for', |
@@ -323,13 +341,10 b' def get_parser():' | |||
|
323 | 341 | default=['x64'], |
|
324 | 342 | ) |
|
325 | 343 | sp.add_argument( |
|
326 | '--revision', | |
|
327 | help='Mercurial revision to build', | |
|
328 | default='.', | |
|
344 | '--revision', help='Mercurial revision to build', default='.', | |
|
329 | 345 | ) |
|
330 | 346 | sp.add_argument( |
|
331 | '--version', | |
|
332 | help='Mercurial version string to use in installer', | |
|
347 | '--version', help='Mercurial version string to use in installer', | |
|
333 | 348 | ) |
|
334 | 349 | sp.add_argument( |
|
335 | 350 | '--base-image-name', |
@@ -345,15 +360,11 b' def get_parser():' | |||
|
345 | 360 | sp.set_defaults(func=terminate_ec2_instances) |
|
346 | 361 | |
|
347 | 362 | sp = subparsers.add_parser( |
|
348 | 'purge-ec2-resources', | |
|
349 | help='Purge all EC2 resources managed by us', | |
|
363 | 'purge-ec2-resources', help='Purge all EC2 resources managed by us', | |
|
350 | 364 | ) |
|
351 | 365 | sp.set_defaults(func=purge_ec2_resources) |
|
352 | 366 | |
|
353 | sp = subparsers.add_parser( | |
|
354 | 'run-tests-linux', | |
|
355 | help='Run tests on Linux', | |
|
356 | ) | |
|
367 | sp = subparsers.add_parser('run-tests-linux', help='Run tests on Linux',) | |
|
357 | 368 | sp.add_argument( |
|
358 | 369 | '--distro', |
|
359 | 370 | help='Linux distribution to run tests on', |
@@ -374,8 +385,18 b' def get_parser():' | |||
|
374 | 385 | sp.add_argument( |
|
375 | 386 | '--python-version', |
|
376 | 387 | help='Python version to use', |
|
377 | choices={'system2', 'system3', '2.7', '3.5', '3.6', '3.7', '3.8', | |
|
378 | 'pypy', 'pypy3.5', 'pypy3.6'}, | |
|
388 | choices={ | |
|
389 | 'system2', | |
|
390 | 'system3', | |
|
391 | '2.7', | |
|
392 | '3.5', | |
|
393 | '3.6', | |
|
394 | '3.7', | |
|
395 | '3.8', | |
|
396 | 'pypy', | |
|
397 | 'pypy3.5', | |
|
398 | 'pypy3.6', | |
|
399 | }, | |
|
379 | 400 | default='system2', |
|
380 | 401 | ) |
|
381 | 402 | sp.add_argument( |
@@ -386,13 +407,10 b' def get_parser():' | |||
|
386 | 407 | sp.set_defaults(func=run_tests_linux) |
|
387 | 408 | |
|
388 | 409 | sp = subparsers.add_parser( |
|
389 | 'run-tests-windows', | |
|
390 | help='Run tests on Windows', | |
|
410 | 'run-tests-windows', help='Run tests on Windows', | |
|
391 | 411 | ) |
|
392 | 412 | sp.add_argument( |
|
393 | '--instance-type', | |
|
394 | help='EC2 instance type to use', | |
|
395 | default='t3.medium', | |
|
413 | '--instance-type', help='EC2 instance type to use', default='t3.medium', | |
|
396 | 414 | ) |
|
397 | 415 | sp.add_argument( |
|
398 | 416 | '--python-version', |
@@ -407,8 +425,7 b' def get_parser():' | |||
|
407 | 425 | default='x64', |
|
408 | 426 | ) |
|
409 | 427 | sp.add_argument( |
|
410 | '--test-flags', | |
|
411 | help='Extra command line flags to pass to run-tests.py', | |
|
428 | '--test-flags', help='Extra command line flags to pass to run-tests.py', | |
|
412 | 429 | ) |
|
413 | 430 | sp.add_argument( |
|
414 | 431 | '--base-image-name', |
@@ -419,7 +436,7 b' def get_parser():' | |||
|
419 | 436 | |
|
420 | 437 | sp = subparsers.add_parser( |
|
421 | 438 | 'publish-windows-artifacts', |
|
422 | help='Publish built Windows artifacts (wheels, installers, etc)' | |
|
439 | help='Publish built Windows artifacts (wheels, installers, etc)', | |
|
423 | 440 | ) |
|
424 | 441 | sp.add_argument( |
|
425 | 442 | '--no-pypi', |
@@ -436,22 +453,17 b' def get_parser():' | |||
|
436 | 453 | help='Skip uploading to www.mercurial-scm.org', |
|
437 | 454 | ) |
|
438 | 455 | sp.add_argument( |
|
439 | '--ssh-username', | |
|
440 | help='SSH username for mercurial-scm.org', | |
|
456 | '--ssh-username', help='SSH username for mercurial-scm.org', | |
|
441 | 457 | ) |
|
442 | 458 | sp.add_argument( |
|
443 | 'version', | |
|
444 | help='Mercurial version string to locate local packages', | |
|
459 | 'version', help='Mercurial version string to locate local packages', | |
|
445 | 460 | ) |
|
446 | 461 | sp.set_defaults(func=publish_windows_artifacts) |
|
447 | 462 | |
|
448 | 463 | sp = subparsers.add_parser( |
|
449 | 'try', | |
|
450 | help='Run CI automation against a custom changeset' | |
|
464 | 'try', help='Run CI automation against a custom changeset' | |
|
451 | 465 | ) |
|
452 | sp.add_argument('-r', '--rev', | |
|
453 | default='.', | |
|
454 | help='Revision to run CI on') | |
|
466 | sp.add_argument('-r', '--rev', default='.', help='Revision to run CI on') | |
|
455 | 467 | sp.set_defaults(func=run_try) |
|
456 | 468 | |
|
457 | 469 | return parser |
@@ -13,9 +13,7 b' import shlex' | |||
|
13 | 13 | import subprocess |
|
14 | 14 | import tempfile |
|
15 | 15 | |
|
16 |
from .ssh import |
|
|
17 | exec_command, | |
|
18 | ) | |
|
16 | from .ssh import exec_command | |
|
19 | 17 | |
|
20 | 18 | |
|
21 | 19 | # Linux distributions that are supported. |
@@ -62,7 +60,9 b' for v in ${PYENV3_VERSIONS}; do' | |||
|
62 | 60 | done |
|
63 | 61 | |
|
64 | 62 | pyenv global ${PYENV2_VERSIONS} ${PYENV3_VERSIONS} system |
|
65 |
'''.lstrip().replace( |
|
|
63 | '''.lstrip().replace( | |
|
64 | '\r\n', '\n' | |
|
65 | ) | |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | INSTALL_RUST = r''' |
@@ -87,10 +87,13 b' wget -O ${HG_TARBALL} --progress dot:meg' | |||
|
87 | 87 | echo "${HG_SHA256} ${HG_TARBALL}" | sha256sum --check - |
|
88 | 88 | |
|
89 | 89 | /hgdev/venv-bootstrap/bin/pip install ${HG_TARBALL} |
|
90 |
'''.lstrip().replace( |
|
|
90 | '''.lstrip().replace( | |
|
91 | '\r\n', '\n' | |
|
92 | ) | |
|
91 | 93 | |
|
92 | 94 | |
|
93 |
BOOTSTRAP_DEBIAN = |
|
|
95 | BOOTSTRAP_DEBIAN = ( | |
|
96 | r''' | |
|
94 | 97 |
|
|
95 | 98 |
|
|
96 | 99 |
|
@@ -323,11 +326,14 b' publish = false' | |||
|
323 | 326 |
|
|
324 | 327 |
|
|
325 | 328 |
|
|
326 |
|
|
|
329 | '''.lstrip() | |
|
330 | .format( | |
|
327 | 331 | install_rust=INSTALL_RUST, |
|
328 | 332 | install_pythons=INSTALL_PYTHONS, |
|
329 | bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV | |
|
330 | ).replace('\r\n', '\n') | |
|
333 | bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV, | |
|
334 | ) | |
|
335 | .replace('\r\n', '\n') | |
|
336 | ) | |
|
331 | 337 | |
|
332 | 338 | |
|
333 | 339 | # Prepares /hgdev for operations. |
@@ -409,7 +415,9 b' mkdir /hgwork/tmp' | |||
|
409 | 415 | chown hg:hg /hgwork/tmp |
|
410 | 416 | |
|
411 | 417 | rsync -a /hgdev/src /hgwork/ |
|
412 |
'''.lstrip().replace( |
|
|
418 | '''.lstrip().replace( | |
|
419 | '\r\n', '\n' | |
|
420 | ) | |
|
413 | 421 | |
|
414 | 422 | |
|
415 | 423 | HG_UPDATE_CLEAN = ''' |
@@ -421,7 +429,9 b' cd /hgwork/src' | |||
|
421 | 429 | ${HG} --config extensions.purge= purge --all |
|
422 | 430 | ${HG} update -C $1 |
|
423 | 431 | ${HG} log -r . |
|
424 |
'''.lstrip().replace( |
|
|
432 | '''.lstrip().replace( | |
|
433 | '\r\n', '\n' | |
|
434 | ) | |
|
425 | 435 | |
|
426 | 436 | |
|
427 | 437 | def prepare_exec_environment(ssh_client, filesystem='default'): |
@@ -456,11 +466,12 b' def prepare_exec_environment(ssh_client,' | |||
|
456 | 466 | res = chan.recv_exit_status() |
|
457 | 467 | |
|
458 | 468 | if res: |
|
459 | raise Exception('non-0 exit code updating working directory; %d' | |
|
460 | % res) | |
|
469 | raise Exception('non-0 exit code updating working directory; %d' % res) | |
|
461 | 470 | |
|
462 | 471 | |
|
463 | def synchronize_hg(source_path: pathlib.Path, ec2_instance, revision: str=None): | |
|
472 | def synchronize_hg( | |
|
473 | source_path: pathlib.Path, ec2_instance, revision: str = None | |
|
474 | ): | |
|
464 | 475 | """Synchronize a local Mercurial source path to remote EC2 instance.""" |
|
465 | 476 | |
|
466 | 477 | with tempfile.TemporaryDirectory() as temp_dir: |
@@ -482,8 +493,10 b' def synchronize_hg(source_path: pathlib.' | |||
|
482 | 493 | fh.write(' IdentityFile %s\n' % ec2_instance.ssh_private_key_path) |
|
483 | 494 | |
|
484 | 495 | if not (source_path / '.hg').is_dir(): |
|
485 | raise Exception('%s is not a Mercurial repository; synchronization ' | |
|
486 | 'not yet supported' % source_path) | |
|
496 | raise Exception( | |
|
497 | '%s is not a Mercurial repository; synchronization ' | |
|
498 | 'not yet supported' % source_path | |
|
499 | ) | |
|
487 | 500 | |
|
488 | 501 | env = dict(os.environ) |
|
489 | 502 | env['HGPLAIN'] = '1' |
@@ -493,17 +506,29 b' def synchronize_hg(source_path: pathlib.' | |||
|
493 | 506 | |
|
494 | 507 | res = subprocess.run( |
|
495 | 508 | ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], |
|
496 |
cwd=str(source_path), |
|
|
509 | cwd=str(source_path), | |
|
510 | env=env, | |
|
511 | check=True, | |
|
512 | capture_output=True, | |
|
513 | ) | |
|
497 | 514 | |
|
498 | 515 | full_revision = res.stdout.decode('ascii') |
|
499 | 516 | |
|
500 | 517 | args = [ |
|
501 |
'python2.7', |
|
|
502 | '--config', 'ui.ssh=ssh -F %s' % ssh_config, | |
|
503 | '--config', 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg', | |
|
518 | 'python2.7', | |
|
519 | str(hg_bin), | |
|
520 | '--config', | |
|
521 | 'ui.ssh=ssh -F %s' % ssh_config, | |
|
522 | '--config', | |
|
523 | 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg', | |
|
504 | 524 | # Also ensure .hgtags changes are present so auto version |
|
505 | 525 | # calculation works. |
|
506 | 'push', '-f', '-r', full_revision, '-r', 'file(.hgtags)', | |
|
526 | 'push', | |
|
527 | '-f', | |
|
528 | '-r', | |
|
529 | full_revision, | |
|
530 | '-r', | |
|
531 | 'file(.hgtags)', | |
|
507 | 532 | 'ssh://%s//hgwork/src' % public_ip, |
|
508 | 533 | ] |
|
509 | 534 | |
@@ -522,7 +547,8 b' def synchronize_hg(source_path: pathlib.' | |||
|
522 | 547 | fh.chmod(0o0700) |
|
523 | 548 | |
|
524 | 549 | chan, stdin, stdout = exec_command( |
|
525 |
ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision |
|
|
550 | ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision | |
|
551 | ) | |
|
526 | 552 | stdin.close() |
|
527 | 553 | |
|
528 | 554 | for line in stdout: |
@@ -531,8 +557,9 b' def synchronize_hg(source_path: pathlib.' | |||
|
531 | 557 | res = chan.recv_exit_status() |
|
532 | 558 | |
|
533 | 559 | if res: |
|
534 | raise Exception('non-0 exit code updating working directory; %d' | |
|
535 | % res) | |
|
560 | raise Exception( | |
|
561 | 'non-0 exit code updating working directory; %d' % res | |
|
562 | ) | |
|
536 | 563 | |
|
537 | 564 | |
|
538 | 565 | def run_tests(ssh_client, python_version, test_flags=None): |
@@ -554,8 +581,8 b' def run_tests(ssh_client, python_version' | |||
|
554 | 581 | |
|
555 | 582 | command = ( |
|
556 | 583 | '/bin/sh -c "export TMPDIR=/hgwork/tmp; ' |
|
557 | 'cd /hgwork/src/tests && %s run-tests.py %s"' % ( | |
|
558 | python, test_flags)) | |
|
584 | 'cd /hgwork/src/tests && %s run-tests.py %s"' % (python, test_flags) | |
|
585 | ) | |
|
559 | 586 | |
|
560 | 587 | chan, stdin, stdout = exec_command(ssh_client, command) |
|
561 | 588 |
@@ -7,12 +7,8 b'' | |||
|
7 | 7 | |
|
8 | 8 | # no-check-code because Python 3 native. |
|
9 | 9 | |
|
10 |
from twine.commands.upload import |
|
|
11 | upload as twine_upload, | |
|
12 | ) | |
|
13 | from twine.settings import ( | |
|
14 | Settings, | |
|
15 | ) | |
|
10 | from twine.commands.upload import upload as twine_upload | |
|
11 | from twine.settings import Settings | |
|
16 | 12 | |
|
17 | 13 | |
|
18 | 14 | def upload(paths): |
@@ -11,14 +11,13 b' import socket' | |||
|
11 | 11 | import time |
|
12 | 12 | import warnings |
|
13 | 13 | |
|
14 |
from cryptography.utils import |
|
|
15 | CryptographyDeprecationWarning, | |
|
16 | ) | |
|
14 | from cryptography.utils import CryptographyDeprecationWarning | |
|
17 | 15 | import paramiko |
|
18 | 16 | |
|
19 | 17 | |
|
20 | 18 | def wait_for_ssh(hostname, port, timeout=60, username=None, key_filename=None): |
|
21 | 19 | """Wait for an SSH server to start on the specified host and port.""" |
|
20 | ||
|
22 | 21 | class IgnoreHostKeyPolicy(paramiko.MissingHostKeyPolicy): |
|
23 | 22 | def missing_host_key(self, client, hostname, key): |
|
24 | 23 | return |
@@ -28,17 +27,23 b' def wait_for_ssh(hostname, port, timeout' | |||
|
28 | 27 | # paramiko triggers a CryptographyDeprecationWarning in the cryptography |
|
29 | 28 | # package. Let's suppress |
|
30 | 29 | with warnings.catch_warnings(): |
|
31 |
warnings.filterwarnings( |
|
|
32 |
|
|
|
30 | warnings.filterwarnings( | |
|
31 | 'ignore', category=CryptographyDeprecationWarning | |
|
32 | ) | |
|
33 | 33 | |
|
34 | 34 | while True: |
|
35 | 35 | client = paramiko.SSHClient() |
|
36 | 36 | client.set_missing_host_key_policy(IgnoreHostKeyPolicy()) |
|
37 | 37 | try: |
|
38 |
client.connect( |
|
|
38 | client.connect( | |
|
39 | hostname, | |
|
40 | port=port, | |
|
41 | username=username, | |
|
39 | 42 |
|
|
40 |
|
|
|
41 |
|
|
|
43 | timeout=5.0, | |
|
44 | allow_agent=False, | |
|
45 | look_for_keys=False, | |
|
46 | ) | |
|
42 | 47 | |
|
43 | 48 | return client |
|
44 | 49 | except socket.error: |
@@ -15,12 +15,8 b' import re' | |||
|
15 | 15 | import subprocess |
|
16 | 16 | import tempfile |
|
17 | 17 | |
|
18 |
from .pypi import |
|
|
19 | upload as pypi_upload, | |
|
20 | ) | |
|
21 | from .winrm import ( | |
|
22 | run_powershell, | |
|
23 | ) | |
|
18 | from .pypi import upload as pypi_upload | |
|
19 | from .winrm import run_powershell | |
|
24 | 20 | |
|
25 | 21 | |
|
26 | 22 | # PowerShell commands to activate a Visual Studio 2008 environment. |
@@ -117,14 +113,21 b" MERCURIAL_SCM_BASE_URL = 'https://mercur" | |||
|
117 | 113 | X86_USER_AGENT_PATTERN = '.*Windows.*' |
|
118 | 114 | X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' |
|
119 | 115 | |
|
120 | X86_EXE_DESCRIPTION = ('Mercurial {version} Inno Setup installer - x86 Windows ' | |
|
121 | '- does not require admin rights') | |
|
122 | X64_EXE_DESCRIPTION = ('Mercurial {version} Inno Setup installer - x64 Windows ' | |
|
123 | '- does not require admin rights') | |
|
124 | X86_MSI_DESCRIPTION = ('Mercurial {version} MSI installer - x86 Windows ' | |
|
125 | '- requires admin rights') | |
|
126 | X64_MSI_DESCRIPTION = ('Mercurial {version} MSI installer - x64 Windows ' | |
|
127 | '- requires admin rights') | |
|
116 | X86_EXE_DESCRIPTION = ( | |
|
117 | 'Mercurial {version} Inno Setup installer - x86 Windows ' | |
|
118 | '- does not require admin rights' | |
|
119 | ) | |
|
120 | X64_EXE_DESCRIPTION = ( | |
|
121 | 'Mercurial {version} Inno Setup installer - x64 Windows ' | |
|
122 | '- does not require admin rights' | |
|
123 | ) | |
|
124 | X86_MSI_DESCRIPTION = ( | |
|
125 | 'Mercurial {version} MSI installer - x86 Windows ' '- requires admin rights' | |
|
126 | ) | |
|
127 | X64_MSI_DESCRIPTION = ( | |
|
128 | 'Mercurial {version} MSI installer - x64 Windows ' '- requires admin rights' | |
|
129 | ) | |
|
130 | ||
|
128 | 131 | |
|
129 | 132 | def get_vc_prefix(arch): |
|
130 | 133 | if arch == 'x86': |
@@ -158,10 +161,21 b' def synchronize_hg(hg_repo: pathlib.Path' | |||
|
158 | 161 | ssh_dir.chmod(0o0700) |
|
159 | 162 | |
|
160 | 163 | # Generate SSH key to use for communication. |
|
161 |
subprocess.run( |
|
|
162 | 'ssh-keygen', '-t', 'rsa', '-b', '4096', '-N', '', | |
|
163 | '-f', str(ssh_dir / 'id_rsa')], | |
|
164 | check=True, capture_output=True) | |
|
164 | subprocess.run( | |
|
165 | [ | |
|
166 | 'ssh-keygen', | |
|
167 | '-t', | |
|
168 | 'rsa', | |
|
169 | '-b', | |
|
170 | '4096', | |
|
171 | '-N', | |
|
172 | '', | |
|
173 | '-f', | |
|
174 | str(ssh_dir / 'id_rsa'), | |
|
175 | ], | |
|
176 | check=True, | |
|
177 | capture_output=True, | |
|
178 | ) | |
|
165 | 179 | |
|
166 | 180 | # Add it to ~/.ssh/authorized_keys on remote. |
|
167 | 181 | # This assumes the file doesn't already exist. |
@@ -182,8 +196,10 b' def synchronize_hg(hg_repo: pathlib.Path' | |||
|
182 | 196 | fh.write(' IdentityFile %s\n' % (ssh_dir / 'id_rsa')) |
|
183 | 197 | |
|
184 | 198 | if not (hg_repo / '.hg').is_dir(): |
|
185 |
raise Exception( |
|
|
186 | 'synchronization not yet supported' % hg_repo) | |
|
199 | raise Exception( | |
|
200 | '%s is not a Mercurial repository; ' | |
|
201 | 'synchronization not yet supported' % hg_repo | |
|
202 | ) | |
|
187 | 203 | |
|
188 | 204 | env = dict(os.environ) |
|
189 | 205 | env['HGPLAIN'] = '1' |
@@ -193,17 +209,29 b' def synchronize_hg(hg_repo: pathlib.Path' | |||
|
193 | 209 | |
|
194 | 210 | res = subprocess.run( |
|
195 | 211 | ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], |
|
196 | cwd=str(hg_repo), env=env, check=True, capture_output=True) | |
|
212 | cwd=str(hg_repo), | |
|
213 | env=env, | |
|
214 | check=True, | |
|
215 | capture_output=True, | |
|
216 | ) | |
|
197 | 217 | |
|
198 | 218 | full_revision = res.stdout.decode('ascii') |
|
199 | 219 | |
|
200 | 220 | args = [ |
|
201 |
'python2.7', |
|
|
202 | '--config', 'ui.ssh=ssh -F %s' % ssh_config, | |
|
203 | '--config', 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', | |
|
221 | 'python2.7', | |
|
222 | hg_bin, | |
|
223 | '--config', | |
|
224 | 'ui.ssh=ssh -F %s' % ssh_config, | |
|
225 | '--config', | |
|
226 | 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', | |
|
204 | 227 | # Also ensure .hgtags changes are present so auto version |
|
205 | 228 | # calculation works. |
|
206 | 'push', '-f', '-r', full_revision, '-r', 'file(.hgtags)', | |
|
229 | 'push', | |
|
230 | '-f', | |
|
231 | '-r', | |
|
232 | full_revision, | |
|
233 | '-r', | |
|
234 | 'file(.hgtags)', | |
|
207 | 235 | 'ssh://%s/c:/hgdev/src' % public_ip, |
|
208 | 236 | ] |
|
209 | 237 | |
@@ -213,8 +241,9 b' def synchronize_hg(hg_repo: pathlib.Path' | |||
|
213 | 241 | if res.returncode not in (0, 1): |
|
214 | 242 | res.check_returncode() |
|
215 | 243 | |
|
216 |
run_powershell( |
|
|
217 |
|
|
|
244 | run_powershell( | |
|
245 | winrm_client, HG_UPDATE_CLEAN.format(revision=full_revision) | |
|
246 | ) | |
|
218 | 247 | |
|
219 | 248 | # TODO detect dirty local working directory and synchronize accordingly. |
|
220 | 249 | |
@@ -250,8 +279,9 b' def copy_latest_dist(winrm_client, patte' | |||
|
250 | 279 | winrm_client.fetch(source, str(dest)) |
|
251 | 280 | |
|
252 | 281 | |
|
253 | def build_inno_installer(winrm_client, arch: str, dest_path: pathlib.Path, | |
|
254 | version=None): | |
|
282 | def build_inno_installer( | |
|
283 | winrm_client, arch: str, dest_path: pathlib.Path, version=None | |
|
284 | ): | |
|
255 | 285 | """Build the Inno Setup installer on a remote machine. |
|
256 | 286 | |
|
257 | 287 | Using a WinRM client, remote commands are executed to build |
@@ -263,8 +293,9 b' def build_inno_installer(winrm_client, a' | |||
|
263 | 293 | if version: |
|
264 | 294 | extra_args.extend(['--version', version]) |
|
265 | 295 | |
|
266 |
ps = get_vc_prefix(arch) + BUILD_INNO.format( |
|
|
267 |
|
|
|
296 | ps = get_vc_prefix(arch) + BUILD_INNO.format( | |
|
297 | arch=arch, extra_args=' '.join(extra_args) | |
|
298 | ) | |
|
268 | 299 | run_powershell(winrm_client, ps) |
|
269 | 300 | copy_latest_dist(winrm_client, '*.exe', dest_path) |
|
270 | 301 | |
@@ -281,8 +312,9 b' def build_wheel(winrm_client, arch: str,' | |||
|
281 | 312 | copy_latest_dist(winrm_client, '*.whl', dest_path) |
|
282 | 313 | |
|
283 | 314 | |
|
284 | def build_wix_installer(winrm_client, arch: str, dest_path: pathlib.Path, | |
|
285 | version=None): | |
|
315 | def build_wix_installer( | |
|
316 | winrm_client, arch: str, dest_path: pathlib.Path, version=None | |
|
317 | ): | |
|
286 | 318 | """Build the WiX installer on a remote machine. |
|
287 | 319 | |
|
288 | 320 | Using a WinRM client, remote commands are executed to build a WiX installer. |
@@ -292,8 +324,9 b' def build_wix_installer(winrm_client, ar' | |||
|
292 | 324 | if version: |
|
293 | 325 | extra_args.extend(['--version', version]) |
|
294 | 326 | |
|
295 |
ps = get_vc_prefix(arch) + BUILD_WIX.format( |
|
|
296 |
|
|
|
327 | ps = get_vc_prefix(arch) + BUILD_WIX.format( | |
|
328 | arch=arch, extra_args=' '.join(extra_args) | |
|
329 | ) | |
|
297 | 330 | run_powershell(winrm_client, ps) |
|
298 | 331 | copy_latest_dist(winrm_client, '*.msi', dest_path) |
|
299 | 332 | |
@@ -307,18 +340,16 b' def run_tests(winrm_client, python_versi' | |||
|
307 | 340 | ``run-tests.py``. |
|
308 | 341 | """ |
|
309 | 342 | if not re.match(r'\d\.\d', python_version): |
|
310 | raise ValueError(r'python_version must be \d.\d; got %s' % | |
|
311 |
|
|
|
343 | raise ValueError( | |
|
344 | r'python_version must be \d.\d; got %s' % python_version | |
|
345 | ) | |
|
312 | 346 | |
|
313 | 347 | if arch not in ('x86', 'x64'): |
|
314 | 348 | raise ValueError('arch must be x86 or x64; got %s' % arch) |
|
315 | 349 | |
|
316 | 350 | python_path = 'python%s-%s' % (python_version.replace('.', ''), arch) |
|
317 | 351 | |
|
318 | ps = RUN_TESTS.format( | |
|
319 | python_path=python_path, | |
|
320 | test_flags=test_flags or '', | |
|
321 | ) | |
|
352 | ps = RUN_TESTS.format(python_path=python_path, test_flags=test_flags or '',) | |
|
322 | 353 | |
|
323 | 354 | run_powershell(winrm_client, ps) |
|
324 | 355 | |
@@ -374,8 +405,8 b' def generate_latest_dat(version: str):' | |||
|
374 | 405 | version, |
|
375 | 406 | X64_USER_AGENT_PATTERN, |
|
376 | 407 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename), |
|
377 | X64_MSI_DESCRIPTION.format(version=version) | |
|
378 | ) | |
|
408 | X64_MSI_DESCRIPTION.format(version=version), | |
|
409 | ), | |
|
379 | 410 | ) |
|
380 | 411 | |
|
381 | 412 | lines = ['\t'.join(e) for e in entries] |
@@ -396,8 +427,9 b' def publish_artifacts_pypi(dist_path: pa' | |||
|
396 | 427 | pypi_upload(wheel_paths) |
|
397 | 428 | |
|
398 | 429 | |
|
399 |
def publish_artifacts_mercurial_scm_org( |
|
|
400 | ssh_username=None): | |
|
430 | def publish_artifacts_mercurial_scm_org( | |
|
431 | dist_path: pathlib.Path, version: str, ssh_username=None | |
|
432 | ): | |
|
401 | 433 | """Publish Windows release artifacts to mercurial-scm.org.""" |
|
402 | 434 | all_paths = resolve_all_artifacts(dist_path, version) |
|
403 | 435 | |
@@ -436,7 +468,8 b' def publish_artifacts_mercurial_scm_org(' | |||
|
436 | 468 | |
|
437 | 469 | now = datetime.datetime.utcnow() |
|
438 | 470 | backup_path = dist_path / ( |
|
439 |
'latest-windows-%s.dat' % now.strftime('%Y%m%dT%H%M%S') |
|
|
471 | 'latest-windows-%s.dat' % now.strftime('%Y%m%dT%H%M%S') | |
|
472 | ) | |
|
440 | 473 | print('backing up %s to %s' % (latest_dat_path, backup_path)) |
|
441 | 474 | |
|
442 | 475 | with sftp.open(latest_dat_path, 'rb') as fh: |
@@ -453,9 +486,13 b' def publish_artifacts_mercurial_scm_org(' | |||
|
453 | 486 | fh.write(latest_dat_content.encode('ascii')) |
|
454 | 487 | |
|
455 | 488 | |
|
456 | def publish_artifacts(dist_path: pathlib.Path, version: str, | |
|
457 | pypi=True, mercurial_scm_org=True, | |
|
458 | ssh_username=None): | |
|
489 | def publish_artifacts( | |
|
490 | dist_path: pathlib.Path, | |
|
491 | version: str, | |
|
492 | pypi=True, | |
|
493 | mercurial_scm_org=True, | |
|
494 | ssh_username=None, | |
|
495 | ): | |
|
459 | 496 | """Publish Windows release artifacts. |
|
460 | 497 | |
|
461 | 498 | Files are found in `dist_path`. We will look for files with version string |
@@ -468,5 +505,6 b' def publish_artifacts(dist_path: pathlib' | |||
|
468 | 505 | publish_artifacts_pypi(dist_path, version) |
|
469 | 506 | |
|
470 | 507 | if mercurial_scm_org: |
|
471 |
publish_artifacts_mercurial_scm_org( |
|
|
472 |
|
|
|
508 | publish_artifacts_mercurial_scm_org( | |
|
509 | dist_path, version, ssh_username=ssh_username | |
|
510 | ) |
@@ -11,9 +11,7 b' import logging' | |||
|
11 | 11 | import pprint |
|
12 | 12 | import time |
|
13 | 13 | |
|
14 |
from pypsrp.client import |
|
|
15 | Client, | |
|
16 | ) | |
|
14 | from pypsrp.client import Client | |
|
17 | 15 | from pypsrp.powershell import ( |
|
18 | 16 | PowerShell, |
|
19 | 17 | PSInvocationState, |
@@ -35,8 +33,13 b' def wait_for_winrm(host, username, passw' | |||
|
35 | 33 | |
|
36 | 34 | while True: |
|
37 | 35 | try: |
|
38 | client = Client(host, username=username, password=password, | |
|
39 | ssl=ssl, connection_timeout=5) | |
|
36 | client = Client( | |
|
37 | host, | |
|
38 | username=username, | |
|
39 | password=password, | |
|
40 | ssl=ssl, | |
|
41 | connection_timeout=5, | |
|
42 | ) | |
|
40 | 43 | client.execute_ps("Write-Host 'Hello, World!'") |
|
41 | 44 | return client |
|
42 | 45 | except requests.exceptions.ConnectionError: |
@@ -78,5 +81,7 b' def run_powershell(client, script):' | |||
|
78 | 81 | print(format_object(o)) |
|
79 | 82 | |
|
80 | 83 | if ps.state == PSInvocationState.FAILED: |
|
81 |
raise Exception( |
|
|
82 | ' '.join(map(format_object, ps.streams.error))) | |
|
84 | raise Exception( | |
|
85 | 'PowerShell execution failed: %s' | |
|
86 | % ' '.join(map(format_object, ps.streams.error)) | |
|
87 | ) |
@@ -9,15 +9,20 b' from mercurial import (' | |||
|
9 | 9 | pycompat, |
|
10 | 10 | ) |
|
11 | 11 | |
|
12 | ||
|
12 | 13 | def reducetest(a, b): |
|
13 | 14 | tries = 0 |
|
14 | 15 | reductions = 0 |
|
15 | 16 | print("reducing...") |
|
16 | 17 | while tries < 1000: |
|
17 | a2 = "\n".join(l for l in a.splitlines() | |
|
18 |
|
|
|
19 | b2 = "\n".join(l for l in b.splitlines() | |
|
20 | if random.randint(0, 100) > 0) + "\n" | |
|
18 | a2 = ( | |
|
19 | "\n".join(l for l in a.splitlines() if random.randint(0, 100) > 0) | |
|
20 | + "\n" | |
|
21 | ) | |
|
22 | b2 = ( | |
|
23 | "\n".join(l for l in b.splitlines() if random.randint(0, 100) > 0) | |
|
24 | + "\n" | |
|
25 | ) | |
|
21 | 26 | if a2 == a and b2 == b: |
|
22 | 27 | continue |
|
23 | 28 | if a2 == b2: |
@@ -32,8 +37,7 b' def reducetest(a, b):' | |||
|
32 | 37 | a = a2 |
|
33 | 38 | b = b2 |
|
34 | 39 | |
|
35 | print("reduced:", reductions, len(a) + len(b), | |
|
36 | repr(a), repr(b)) | |
|
40 | print("reduced:", reductions, len(a) + len(b), repr(a), repr(b)) | |
|
37 | 41 | try: |
|
38 | 42 | test1(a, b) |
|
39 | 43 | except Exception as inst: |
@@ -41,6 +45,7 b' def reducetest(a, b):' | |||
|
41 | 45 | |
|
42 | 46 | sys.exit(0) |
|
43 | 47 | |
|
48 | ||
|
44 | 49 | def test1(a, b): |
|
45 | 50 | d = mdiff.textdiff(a, b) |
|
46 | 51 | if not d: |
@@ -49,6 +54,7 b' def test1(a, b):' | |||
|
49 | 54 | if c != b: |
|
50 | 55 | raise ValueError("bad") |
|
51 | 56 | |
|
57 | ||
|
52 | 58 | def testwrap(a, b): |
|
53 | 59 | try: |
|
54 | 60 | test1(a, b) |
@@ -57,10 +63,12 b' def testwrap(a, b):' | |||
|
57 | 63 | print("exception:", inst) |
|
58 | 64 | reducetest(a, b) |
|
59 | 65 | |
|
66 | ||
|
60 | 67 | def test(a, b): |
|
61 | 68 | testwrap(a, b) |
|
62 | 69 | testwrap(b, a) |
|
63 | 70 | |
|
71 | ||
|
64 | 72 | def rndtest(size, noise): |
|
65 | 73 | a = [] |
|
66 | 74 | src = " aaaaaaaabbbbccd" |
@@ -82,6 +90,7 b' def rndtest(size, noise):' | |||
|
82 | 90 | |
|
83 | 91 | test(a, b) |
|
84 | 92 | |
|
93 | ||
|
85 | 94 | maxvol = 10000 |
|
86 | 95 | startsize = 2 |
|
87 | 96 | while True: |
@@ -44,15 +44,24 b' from mercurial import (' | |||
|
44 | 44 | util, |
|
45 | 45 | ) |
|
46 | 46 | |
|
47 | basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), | |
|
48 | os.path.pardir, os.path.pardir)) | |
|
47 | basedir = os.path.abspath( | |
|
48 | os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir) | |
|
49 | ) | |
|
49 | 50 | reposdir = os.environ['REPOS_DIR'] |
|
50 | reposnames = [name for name in os.listdir(reposdir) | |
|
51 | if os.path.isdir(os.path.join(reposdir, name, ".hg"))] | |
|
51 | reposnames = [ | |
|
52 | name | |
|
53 | for name in os.listdir(reposdir) | |
|
54 | if os.path.isdir(os.path.join(reposdir, name, ".hg")) | |
|
55 | ] | |
|
52 | 56 | if not reposnames: |
|
53 | 57 | raise ValueError("No repositories found in $REPO_DIR") |
|
54 | outputre = re.compile((r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys ' | |
|
55 | r'\d+.\d+ \(best of \d+\)')) | |
|
58 | outputre = re.compile( | |
|
59 | ( | |
|
60 | r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys ' | |
|
61 | r'\d+.\d+ \(best of \d+\)' | |
|
62 | ) | |
|
63 | ) | |
|
64 | ||
|
56 | 65 | |
|
57 | 66 | def runperfcommand(reponame, command, *args, **kwargs): |
|
58 | 67 | os.environ["HGRCPATH"] = os.environ.get("ASVHGRCPATH", "") |
@@ -63,8 +72,9 b' def runperfcommand(reponame, command, *a' | |||
|
63 | 72 | else: |
|
64 | 73 | ui = uimod.ui() |
|
65 | 74 | repo = hg.repository(ui, os.path.join(reposdir, reponame)) |
|
66 |
perfext = extensions.load( |
|
|
67 |
|
|
|
75 | perfext = extensions.load( | |
|
76 | ui, 'perfext', os.path.join(basedir, 'contrib', 'perf.py') | |
|
77 | ) | |
|
68 | 78 | cmd = getattr(perfext, command) |
|
69 | 79 | ui.pushbuffer() |
|
70 | 80 | cmd(ui, repo, *args, **kwargs) |
@@ -74,6 +84,7 b' def runperfcommand(reponame, command, *a' | |||
|
74 | 84 | raise ValueError("Invalid output {0}".format(output)) |
|
75 | 85 | return float(match.group(1)) |
|
76 | 86 | |
|
87 | ||
|
77 | 88 | def perfbench(repos=reposnames, name=None, params=None): |
|
78 | 89 | """decorator to declare ASV benchmark based on contrib/perf.py extension |
|
79 | 90 | |
@@ -104,10 +115,12 b' def perfbench(repos=reposnames, name=Non' | |||
|
104 | 115 | def wrapped(repo, *args): |
|
105 | 116 | def perf(command, *a, **kw): |
|
106 | 117 | return runperfcommand(repo, command, *a, **kw) |
|
118 | ||
|
107 | 119 | return func(perf, *args) |
|
108 | 120 | |
|
109 | 121 | wrapped.params = [p[1] for p in params] |
|
110 | 122 | wrapped.param_names = [p[0] for p in params] |
|
111 | 123 | wrapped.pretty_name = name |
|
112 | 124 | return wrapped |
|
125 | ||
|
113 | 126 | return decorator |
@@ -9,18 +9,22 b' from __future__ import absolute_import' | |||
|
9 | 9 | |
|
10 | 10 | from . import perfbench |
|
11 | 11 | |
|
12 | ||
|
12 | 13 | @perfbench() |
|
13 | 14 | def track_tags(perf): |
|
14 | 15 | return perf("perftags") |
|
15 | 16 | |
|
17 | ||
|
16 | 18 | @perfbench() |
|
17 | 19 | def track_status(perf): |
|
18 | 20 | return perf("perfstatus", unknown=False) |
|
19 | 21 | |
|
22 | ||
|
20 | 23 | @perfbench(params=[('rev', ['1000', '10000', 'tip'])]) |
|
21 | 24 | def track_manifest(perf, rev): |
|
22 | 25 | return perf("perfmanifest", rev) |
|
23 | 26 | |
|
27 | ||
|
24 | 28 | @perfbench() |
|
25 | 29 | def track_heads(perf): |
|
26 | 30 | return perf("perfheads") |
@@ -18,15 +18,16 b' import sys' | |||
|
18 | 18 | |
|
19 | 19 | from . import basedir, perfbench |
|
20 | 20 | |
|
21 | ||
|
21 | 22 | def createrevsetbenchmark(baseset, variants=None): |
|
22 | 23 | if variants is None: |
|
23 | 24 | # Default variants |
|
24 | variants = ["plain", "first", "last", "sort", "sort+first", | |
|
25 | "sort+last"] | |
|
26 | fname = "track_" + "_".join("".join([ | |
|
27 | c if c in string.digits + string.letters else " " | |
|
28 | for c in baseset | |
|
29 | ]).split()) | |
|
25 | variants = ["plain", "first", "last", "sort", "sort+first", "sort+last"] | |
|
26 | fname = "track_" + "_".join( | |
|
27 | "".join( | |
|
28 | [c if c in string.digits + string.letters else " " for c in baseset] | |
|
29 | ).split() | |
|
30 | ) | |
|
30 | 31 | |
|
31 | 32 | def wrap(fname, baseset): |
|
32 | 33 | @perfbench(name=baseset, params=[("variant", variants)]) |
@@ -36,18 +37,21 b' def createrevsetbenchmark(baseset, varia' | |||
|
36 | 37 | for var in variant.split("+"): |
|
37 | 38 | revset = "%s(%s)" % (var, revset) |
|
38 | 39 | return perf("perfrevset", revset) |
|
40 | ||
|
39 | 41 | f.__name__ = fname |
|
40 | 42 | return f |
|
43 | ||
|
41 | 44 | return wrap(fname, baseset) |
|
42 | 45 | |
|
46 | ||
|
43 | 47 | def initializerevsetbenchmarks(): |
|
44 | 48 | mod = sys.modules[__name__] |
|
45 | with open(os.path.join(basedir, 'contrib', 'base-revsets.txt'), | |
|
46 | 'rb') as fh: | |
|
49 | with open(os.path.join(basedir, 'contrib', 'base-revsets.txt'), 'rb') as fh: | |
|
47 | 50 | for line in fh: |
|
48 | 51 | baseset = line.strip() |
|
49 | 52 | if baseset and not baseset.startswith('#'): |
|
50 | 53 | func = createrevsetbenchmark(baseset) |
|
51 | 54 | setattr(mod, func.__name__, func) |
|
52 | 55 | |
|
56 | ||
|
53 | 57 | initializerevsetbenchmarks() |
@@ -18,10 +18,13 b' import tempfile' | |||
|
18 | 18 | import token |
|
19 | 19 | import tokenize |
|
20 | 20 | |
|
21 | ||
|
21 | 22 | def adjusttokenpos(t, ofs): |
|
22 | 23 | """Adjust start/end column of the given token""" |
|
23 | return t._replace(start=(t.start[0], t.start[1] + ofs), | |
|
24 |
|
|
|
24 | return t._replace( | |
|
25 | start=(t.start[0], t.start[1] + ofs), end=(t.end[0], t.end[1] + ofs) | |
|
26 | ) | |
|
27 | ||
|
25 | 28 | |
|
26 | 29 | def replacetokens(tokens, opts): |
|
27 | 30 | """Transform a stream of tokens from raw to Python 3. |
@@ -82,9 +85,8 b' def replacetokens(tokens, opts):' | |||
|
82 | 85 | currtoken = tokens[k] |
|
83 | 86 | while currtoken.type in (token.STRING, token.NEWLINE, tokenize.NL): |
|
84 | 87 | k += 1 |
|
85 | if ( | |
|
86 | currtoken.type == token.STRING | |
|
87 | and currtoken.string.startswith(("'", '"')) | |
|
88 | if currtoken.type == token.STRING and currtoken.string.startswith( | |
|
89 | ("'", '"') | |
|
88 | 90 | ): |
|
89 | 91 | sysstrtokens.add(currtoken) |
|
90 | 92 | try: |
@@ -135,9 +137,9 b' def replacetokens(tokens, opts):' | |||
|
135 | 137 | lastparen = parens[-1] |
|
136 | 138 | if t.start[1] == lastparen[1]: |
|
137 | 139 | coloffset = lastparen[2] |
|
138 | elif ( | |
|
139 | t.start[1] + 1 == lastparen[1] | |
|
140 | and lastparen[3] not in (token.NEWLINE, tokenize.NL) | |
|
140 | elif t.start[1] + 1 == lastparen[1] and lastparen[3] not in ( | |
|
141 | token.NEWLINE, | |
|
142 | tokenize.NL, | |
|
141 | 143 | ): |
|
142 | 144 | # fix misaligned indent of s/util.Abort/error.Abort/ |
|
143 | 145 | coloffset = lastparen[2] + (lastparen[1] - t.start[1]) |
@@ -202,8 +204,7 b' def replacetokens(tokens, opts):' | |||
|
202 | 204 | continue |
|
203 | 205 | |
|
204 | 206 | # String literal. Prefix to make a b'' string. |
|
205 | yield adjusttokenpos(t._replace(string='b%s' % t.string), | |
|
206 | coloffset) | |
|
207 | yield adjusttokenpos(t._replace(string='b%s' % t.string), coloffset) | |
|
207 | 208 | coldelta += 1 |
|
208 | 209 | continue |
|
209 | 210 | |
@@ -213,8 +214,13 b' def replacetokens(tokens, opts):' | |||
|
213 | 214 | |
|
214 | 215 | # *attr() builtins don't accept byte strings to 2nd argument. |
|
215 | 216 | if fn in ( |
|
216 | 'getattr', 'setattr', 'hasattr', 'safehasattr', 'wrapfunction', | |
|
217 |
' |
|
|
217 | 'getattr', | |
|
218 | 'setattr', | |
|
219 | 'hasattr', | |
|
220 | 'safehasattr', | |
|
221 | 'wrapfunction', | |
|
222 | 'wrapclass', | |
|
223 | 'addattr', | |
|
218 | 224 | ) and (opts['allow-attr-methods'] or not _isop(i - 1, '.')): |
|
219 | 225 | arg1idx = _findargnofcall(1) |
|
220 | 226 | if arg1idx is not None: |
@@ -241,18 +247,23 b' def replacetokens(tokens, opts):' | |||
|
241 | 247 | _ensuresysstr(i + 4) |
|
242 | 248 | |
|
243 | 249 | # Looks like "if __name__ == '__main__'". |
|
244 | if (t.type == token.NAME and t.string == '__name__' | |
|
245 | and _isop(i + 1, '==')): | |
|
250 | if ( | |
|
251 | t.type == token.NAME | |
|
252 | and t.string == '__name__' | |
|
253 | and _isop(i + 1, '==') | |
|
254 | ): | |
|
246 | 255 | _ensuresysstr(i + 2) |
|
247 | 256 | |
|
248 | 257 | # Emit unmodified token. |
|
249 | 258 | yield adjusttokenpos(t, coloffset) |
|
250 | 259 | |
|
260 | ||
|
251 | 261 | def process(fin, fout, opts): |
|
252 | 262 | tokens = tokenize.tokenize(fin.readline) |
|
253 | 263 | tokens = replacetokens(list(tokens), opts) |
|
254 | 264 | fout.write(tokenize.untokenize(tokens)) |
|
255 | 265 | |
|
266 | ||
|
256 | 267 | def tryunlink(fname): |
|
257 | 268 | try: |
|
258 | 269 | os.unlink(fname) |
@@ -260,12 +271,14 b' def tryunlink(fname):' | |||
|
260 | 271 | if err.errno != errno.ENOENT: |
|
261 | 272 | raise |
|
262 | 273 | |
|
274 | ||
|
263 | 275 | @contextlib.contextmanager |
|
264 | 276 | def editinplace(fname): |
|
265 | 277 | n = os.path.basename(fname) |
|
266 | 278 | d = os.path.dirname(fname) |
|
267 |
fp = tempfile.NamedTemporaryFile( |
|
|
268 | delete=False) | |
|
279 | fp = tempfile.NamedTemporaryFile( | |
|
280 | prefix='.%s-' % n, suffix='~', dir=d, delete=False | |
|
281 | ) | |
|
269 | 282 | try: |
|
270 | 283 | yield fp |
|
271 | 284 | fp.close() |
@@ -276,19 +289,37 b' def editinplace(fname):' | |||
|
276 | 289 | fp.close() |
|
277 | 290 | tryunlink(fp.name) |
|
278 | 291 | |
|
292 | ||
|
279 | 293 | def main(): |
|
280 | 294 | ap = argparse.ArgumentParser() |
|
281 | ap.add_argument('--version', action='version', | |
|
282 |
|
|
|
283 | ap.add_argument('-i', '--inplace', action='store_true', default=False, | |
|
284 | help='edit files in place') | |
|
285 | ap.add_argument('--dictiter', action='store_true', default=False, | |
|
286 | help='rewrite iteritems() and itervalues()'), | |
|
287 | ap.add_argument('--allow-attr-methods', action='store_true', | |
|
295 | ap.add_argument( | |
|
296 | '--version', action='version', version='Byteify strings 1.0' | |
|
297 | ) | |
|
298 | ap.add_argument( | |
|
299 | '-i', | |
|
300 | '--inplace', | |
|
301 | action='store_true', | |
|
302 | default=False, | |
|
303 | help='edit files in place', | |
|
304 | ) | |
|
305 | ap.add_argument( | |
|
306 | '--dictiter', | |
|
307 | action='store_true', | |
|
288 | 308 |
|
|
289 | help='also handle attr*() when they are methods'), | |
|
290 | ap.add_argument('--treat-as-kwargs', nargs="+", default=[], | |
|
291 | help="ignore kwargs-like objects"), | |
|
309 | help='rewrite iteritems() and itervalues()', | |
|
310 | ), | |
|
311 | ap.add_argument( | |
|
312 | '--allow-attr-methods', | |
|
313 | action='store_true', | |
|
314 | default=False, | |
|
315 | help='also handle attr*() when they are methods', | |
|
316 | ), | |
|
317 | ap.add_argument( | |
|
318 | '--treat-as-kwargs', | |
|
319 | nargs="+", | |
|
320 | default=[], | |
|
321 | help="ignore kwargs-like objects", | |
|
322 | ), | |
|
292 | 323 | ap.add_argument('files', metavar='FILE', nargs='+', help='source file') |
|
293 | 324 | args = ap.parse_args() |
|
294 | 325 | opts = { |
@@ -306,6 +337,7 b' def main():' | |||
|
306 | 337 | fout = sys.stdout.buffer |
|
307 | 338 | process(fin, fout, opts) |
|
308 | 339 | |
|
340 | ||
|
309 | 341 | if __name__ == '__main__': |
|
310 | 342 | if sys.version_info.major < 3: |
|
311 | 343 | print('This script must be run under Python 3.') |
@@ -1,12 +1,12 b'' | |||
|
1 | 1 | from __future__ import absolute_import |
|
2 | 2 | import __builtin__ |
|
3 | 3 | import os |
|
4 |
from mercurial import |
|
|
5 | util, | |
|
6 | ) | |
|
4 | from mercurial import util | |
|
5 | ||
|
7 | 6 | |
|
8 | 7 | def lowerwrap(scope, funcname): |
|
9 | 8 | f = getattr(scope, funcname) |
|
9 | ||
|
10 | 10 | def wrap(fname, *args, **kwargs): |
|
11 | 11 | d, base = os.path.split(fname) |
|
12 | 12 | try: |
@@ -19,11 +19,14 b' def lowerwrap(scope, funcname):' | |||
|
19 | 19 | if fn.lower() == base.lower(): |
|
20 | 20 | return f(os.path.join(d, fn), *args, **kwargs) |
|
21 | 21 | return f(fname, *args, **kwargs) |
|
22 | ||
|
22 | 23 | scope.__dict__[funcname] = wrap |
|
23 | 24 | |
|
25 | ||
|
24 | 26 | def normcase(path): |
|
25 | 27 | return path.lower() |
|
26 | 28 | |
|
29 | ||
|
27 | 30 | os.path.normcase = normcase |
|
28 | 31 | |
|
29 | 32 | for f in 'file open'.split(): |
@@ -53,15 +53,28 b' import timeit' | |||
|
53 | 53 | # Python version and OS |
|
54 | 54 | timer = timeit.default_timer |
|
55 | 55 | |
|
56 | ||
|
56 | 57 | def main(): |
|
57 | 58 | parser = argparse.ArgumentParser() |
|
58 |
parser.add_argument( |
|
|
59 | help='Path of named pipe to create and listen on.') | |
|
60 | parser.add_argument('output', default='trace.json', type=str, nargs='?', | |
|
61 | help='Path of json file to create where the traces ' | |
|
62 | 'will be stored.') | |
|
63 | parser.add_argument('--debug', default=False, action='store_true', | |
|
64 | help='Print useful debug messages') | |
|
59 | parser.add_argument( | |
|
60 | 'pipe', | |
|
61 | type=str, | |
|
62 | nargs=1, | |
|
63 | help='Path of named pipe to create and listen on.', | |
|
64 | ) | |
|
65 | parser.add_argument( | |
|
66 | 'output', | |
|
67 | default='trace.json', | |
|
68 | type=str, | |
|
69 | nargs='?', | |
|
70 | help='Path of json file to create where the traces ' 'will be stored.', | |
|
71 | ) | |
|
72 | parser.add_argument( | |
|
73 | '--debug', | |
|
74 | default=False, | |
|
75 | action='store_true', | |
|
76 | help='Print useful debug messages', | |
|
77 | ) | |
|
65 | 78 | args = parser.parse_args() |
|
66 | 79 | fn = args.pipe[0] |
|
67 | 80 | os.mkfifo(fn) |
@@ -86,7 +99,8 b' def main():' | |||
|
86 | 99 | payload_args = {} |
|
87 | 100 | pid = _threadmap[session] |
|
88 | 101 | ts_micros = (now - start) * 1000000 |
|
89 |
out.write( |
|
|
102 | out.write( | |
|
103 | json.dumps( | |
|
90 | 104 | { |
|
91 | 105 | "name": label, |
|
92 | 106 | "cat": "misc", |
@@ -95,10 +109,13 b' def main():' | |||
|
95 | 109 | "pid": pid, |
|
96 | 110 | "tid": 1, |
|
97 | 111 | "args": payload_args, |
|
98 |
} |
|
|
112 | } | |
|
113 | ) | |
|
114 | ) | |
|
99 | 115 | out.write(',\n') |
|
100 | 116 | finally: |
|
101 | 117 | os.unlink(fn) |
|
102 | 118 | |
|
119 | ||
|
103 | 120 | if __name__ == '__main__': |
|
104 | 121 | main() |
This diff has been collapsed as it changes many lines, (616 lines changed) Show them Hide them | |||
@@ -26,11 +26,15 b' import optparse' | |||
|
26 | 26 | import os |
|
27 | 27 | import re |
|
28 | 28 | import sys |
|
29 | ||
|
29 | 30 | if sys.version_info[0] < 3: |
|
30 | 31 | opentext = open |
|
31 | 32 | else: |
|
33 | ||
|
32 | 34 | def opentext(f): |
|
33 | 35 | return open(f, encoding='latin1') |
|
36 | ||
|
37 | ||
|
34 | 38 | try: |
|
35 | 39 | xrange |
|
36 | 40 | except NameError: |
@@ -42,6 +46,7 b' except ImportError:' | |||
|
42 | 46 | |
|
43 | 47 | import testparseutil |
|
44 | 48 | |
|
49 | ||
|
45 | 50 | def compilere(pat, multiline=False): |
|
46 | 51 | if multiline: |
|
47 | 52 | pat = '(?m)' + pat |
@@ -52,10 +57,22 b' def compilere(pat, multiline=False):' | |||
|
52 | 57 | pass |
|
53 | 58 | return re.compile(pat) |
|
54 | 59 | |
|
60 | ||
|
55 | 61 | # check "rules depending on implementation of repquote()" in each |
|
56 | 62 | # patterns (especially pypats), before changing around repquote() |
|
57 | _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q', | |
|
58 | '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'} | |
|
63 | _repquotefixedmap = { | |
|
64 | ' ': ' ', | |
|
65 | '\n': '\n', | |
|
66 | '.': 'p', | |
|
67 | ':': 'q', | |
|
68 | '%': '%', | |
|
69 | '\\': 'b', | |
|
70 | '*': 'A', | |
|
71 | '+': 'P', | |
|
72 | '-': 'M', | |
|
73 | } | |
|
74 | ||
|
75 | ||
|
59 | 76 | def _repquoteencodechr(i): |
|
60 | 77 | if i > 255: |
|
61 | 78 | return 'u' |
@@ -67,13 +84,17 b' def _repquoteencodechr(i):' | |||
|
67 | 84 | if c.isdigit(): |
|
68 | 85 | return 'n' |
|
69 | 86 | return 'o' |
|
87 | ||
|
88 | ||
|
70 | 89 | _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256)) |
|
71 | 90 | |
|
91 | ||
|
72 | 92 | def repquote(m): |
|
73 | 93 | t = m.group('text') |
|
74 | 94 | t = t.translate(_repquotett) |
|
75 | 95 | return m.group('quote') + t + m.group('quote') |
|
76 | 96 | |
|
97 | ||
|
77 | 98 | def reppython(m): |
|
78 | 99 | comment = m.group('comment') |
|
79 | 100 | if comment: |
@@ -81,20 +102,25 b' def reppython(m):' | |||
|
81 | 102 | return "#" * l + comment[l:] |
|
82 | 103 | return repquote(m) |
|
83 | 104 | |
|
105 | ||
|
84 | 106 | def repcomment(m): |
|
85 | 107 | return m.group(1) + "#" * len(m.group(2)) |
|
86 | 108 | |
|
109 | ||
|
87 | 110 | def repccomment(m): |
|
88 | 111 | t = re.sub(r"((?<=\n) )|\S", "x", m.group(2)) |
|
89 | 112 | return m.group(1) + t + "*/" |
|
90 | 113 | |
|
114 | ||
|
91 | 115 | def repcallspaces(m): |
|
92 | 116 | t = re.sub(r"\n\s+", "\n", m.group(2)) |
|
93 | 117 | return m.group(1) + t |
|
94 | 118 | |
|
119 | ||
|
95 | 120 | def repinclude(m): |
|
96 | 121 | return m.group(1) + "<foo>" |
|
97 | 122 | |
|
123 | ||
|
98 | 124 | def rephere(m): |
|
99 | 125 | t = re.sub(r"\S", "x", m.group(2)) |
|
100 | 126 | return m.group(1) + t |
@@ -117,9 +143,14 b' testpats = [' | |||
|
117 | 143 | (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"), |
|
118 | 144 | (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"), |
|
119 | 145 | (r'rm -rf \*', "don't use naked rm -rf, target a directory"), |
|
120 | (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'), | |
|
121 | (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', | |
|
122 | "use egrep for extended grep syntax"), | |
|
146 | ( | |
|
147 | r'\[[^\]]+==', | |
|
148 | '[ foo == bar ] is a bashism, use [ foo = bar ] instead', | |
|
149 | ), | |
|
150 | ( | |
|
151 | r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', | |
|
152 | "use egrep for extended grep syntax", | |
|
153 | ), | |
|
123 | 154 | (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"), |
|
124 | 155 | (r'(?<!!)/bin/', "don't use explicit paths for tools"), |
|
125 | 156 | (r'#!.*/bash', "don't use bash in shebang, use sh"), |
@@ -136,8 +167,10 b' testpats = [' | |||
|
136 | 167 | (r'if\s*!', "don't use '!' to negate exit status"), |
|
137 | 168 | (r'/dev/u?random', "don't use entropy, use /dev/zero"), |
|
138 | 169 | (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"), |
|
139 | (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)', | |
|
140 | "put a backslash-escaped newline after sed 'i' command"), | |
|
170 | ( | |
|
171 | r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)', | |
|
172 | "put a backslash-escaped newline after sed 'i' command", | |
|
173 | ), | |
|
141 | 174 | (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"), |
|
142 | 175 | (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"), |
|
143 | 176 | (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"), |
@@ -145,12 +178,17 b' testpats = [' | |||
|
145 | 178 | (r'\butil\.Abort\b', "directly use error.Abort"), |
|
146 | 179 | (r'\|&', "don't use |&, use 2>&1"), |
|
147 | 180 | (r'\w = +\w', "only one space after = allowed"), |
|
148 | (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"), | |
|
181 | ( | |
|
182 | r'\bsed\b.*[^\\]\\n', | |
|
183 | "don't use 'sed ... \\n', use a \\ and a newline", | |
|
184 | ), | |
|
149 | 185 | (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"), |
|
150 | 186 | (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"), |
|
151 | 187 | (r'grep.* -[ABC]', "don't use grep's context flags"), |
|
152 | (r'find.*-printf', | |
|
153 | "don't use 'find -printf', it doesn't exist on BSD find(1)"), | |
|
188 | ( | |
|
189 | r'find.*-printf', | |
|
190 | "don't use 'find -printf', it doesn't exist on BSD find(1)", | |
|
191 | ), | |
|
154 | 192 | (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"), |
|
155 | 193 | ], |
|
156 | 194 | # warnings |
@@ -159,8 +197,8 b' testpats = [' | |||
|
159 | 197 | (r'^diff.*-\w*N', "don't use 'diff -N'"), |
|
160 | 198 | (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"), |
|
161 | 199 | (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"), |
|
162 | (r'kill (`|\$\()', "don't use kill, use killdaemons.py") | |
|
163 | ] | |
|
200 | (r'kill (`|\$\()', "don't use kill, use killdaemons.py"), | |
|
201 | ], | |
|
164 | 202 | ] |
|
165 | 203 | |
|
166 | 204 | testfilters = [ |
@@ -172,43 +210,70 b' uprefix = r"^ \\$ "' | |||
|
172 | 210 | utestpats = [ |
|
173 | 211 | [ |
|
174 | 212 | (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"), |
|
175 | (uprefix + r'.*\|\s*sed[^|>\n]*\n', | |
|
176 | "use regex test output patterns instead of sed"), | |
|
213 | ( | |
|
214 | uprefix + r'.*\|\s*sed[^|>\n]*\n', | |
|
215 | "use regex test output patterns instead of sed", | |
|
216 | ), | |
|
177 | 217 | (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), |
|
178 | 218 | (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"), |
|
179 | (uprefix + r'.*\|\| echo.*(fail|error)', | |
|
180 | "explicit exit code checks unnecessary"), | |
|
219 | ( | |
|
220 | uprefix + r'.*\|\| echo.*(fail|error)', | |
|
221 | "explicit exit code checks unnecessary", | |
|
222 | ), | |
|
181 | 223 | (uprefix + r'set -e', "don't use set -e"), |
|
182 | 224 | (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"), |
|
183 | (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite " | |
|
184 | "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx | |
|
185 | '# no-msys'), # in test-pull.t which is skipped on windows | |
|
186 | (r'^ [^$>].*27\.0\.0\.1', | |
|
187 | 'use $LOCALIP not an explicit loopback address'), | |
|
188 | (r'^ (?![>$] ).*\$LOCALIP.*[^)]$', | |
|
189 | 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'), | |
|
190 | (r'^ (cat|find): .*: \$ENOENT\$', | |
|
191 | 'use test -f to test for file existence'), | |
|
192 | (r'^ diff -[^ -]*p', | |
|
193 | "don't use (external) diff with -p for portability"), | |
|
225 | ( | |
|
226 | uprefix + r'.*:\.\S*/', | |
|
227 | "x:.y in a path does not work on msys, rewrite " | |
|
228 | "as x://.y, or see `hg log -k msys` for alternatives", | |
|
229 | r'-\S+:\.|' '# no-msys', # -Rxxx | |
|
230 | ), # in test-pull.t which is skipped on windows | |
|
231 | ( | |
|
232 | r'^ [^$>].*27\.0\.0\.1', | |
|
233 | 'use $LOCALIP not an explicit loopback address', | |
|
234 | ), | |
|
235 | ( | |
|
236 | r'^ (?![>$] ).*\$LOCALIP.*[^)]$', | |
|
237 | 'mark $LOCALIP output lines with (glob) to help tests in BSD jails', | |
|
238 | ), | |
|
239 | ( | |
|
240 | r'^ (cat|find): .*: \$ENOENT\$', | |
|
241 | 'use test -f to test for file existence', | |
|
242 | ), | |
|
243 | ( | |
|
244 | r'^ diff -[^ -]*p', | |
|
245 | "don't use (external) diff with -p for portability", | |
|
246 | ), | |
|
194 | 247 | (r' readlink ', 'use readlink.py instead of readlink'), |
|
195 | (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)', | |
|
196 | "glob timezone field in diff output for portability"), | |
|
197 | (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@', | |
|
198 | "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"), | |
|
199 | (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@', | |
|
200 | "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"), | |
|
201 | (r'^ @@ -[0-9]+ [+][0-9]+ @@', | |
|
202 | "use '@@ -N* +N* @@ (glob)' style chunk header for portability"), | |
|
203 | (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff' | |
|
248 | ( | |
|
249 | r'^ [-+][-+][-+] .* [-+]0000 \(glob\)', | |
|
250 | "glob timezone field in diff output for portability", | |
|
251 | ), | |
|
252 | ( | |
|
253 | r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@', | |
|
254 | "use '@@ -N* +N,n @@ (glob)' style chunk header for portability", | |
|
255 | ), | |
|
256 | ( | |
|
257 | r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@', | |
|
258 | "use '@@ -N,n +N* @@ (glob)' style chunk header for portability", | |
|
259 | ), | |
|
260 | ( | |
|
261 | r'^ @@ -[0-9]+ [+][0-9]+ @@', | |
|
262 | "use '@@ -N* +N* @@ (glob)' style chunk header for portability", | |
|
263 | ), | |
|
264 | ( | |
|
265 | uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff' | |
|
204 | 266 | r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$', |
|
205 |
"use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)" |
|
|
267 | "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)", | |
|
268 | ), | |
|
206 | 269 | ], |
|
207 | 270 | # warnings |
|
208 | 271 | [ |
|
209 | (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$', | |
|
210 | "glob match with no glob string (?, *, /, and $LOCALIP)"), | |
|
211 | ] | |
|
272 | ( | |
|
273 | r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$', | |
|
274 | "glob match with no glob string (?, *, /, and $LOCALIP)", | |
|
275 | ), | |
|
276 | ], | |
|
212 | 277 | ] |
|
213 | 278 | |
|
214 | 279 | # transform plain test rules to unified test's |
@@ -236,14 +301,21 b' utestfilters = [' | |||
|
236 | 301 | commonpypats = [ |
|
237 | 302 | [ |
|
238 | 303 | (r'\\$', 'Use () to wrap long lines in Python, not \\'), |
|
239 | (r'^\s*def\s*\w+\s*\(.*,\s*\(', | |
|
240 | "tuple parameter unpacking not available in Python 3+"), | |
|
241 | (r'lambda\s*\(.*,.*\)', | |
|
242 | "tuple parameter unpacking not available in Python 3+"), | |
|
304 | ( | |
|
305 | r'^\s*def\s*\w+\s*\(.*,\s*\(', | |
|
306 | "tuple parameter unpacking not available in Python 3+", | |
|
307 | ), | |
|
308 | ( | |
|
309 | r'lambda\s*\(.*,.*\)', | |
|
310 | "tuple parameter unpacking not available in Python 3+", | |
|
311 | ), | |
|
243 | 312 | (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"), |
|
244 | 313 | (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"), |
|
245 | (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}', | |
|
246 | 'dict-from-generator'), | |
|
314 | ( | |
|
315 | r'\bdict\(.*=', | |
|
316 | 'dict() is different in Py2 and 3 and is slower than {}', | |
|
317 | 'dict-from-generator', | |
|
318 | ), | |
|
247 | 319 | (r'\.has_key\b', "dict.has_key is not available in Python 3+"), |
|
248 | 320 | (r'\s<>\s', '<> operator is not available in Python 3+, use !='), |
|
249 | 321 | (r'^\s*\t', "don't use tabs"), |
@@ -253,7 +325,8 b' commonpypats = [' | |||
|
253 | 325 | (r'(\w|\)),\w', "missing whitespace after ,"), |
|
254 | 326 | (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), |
|
255 | 327 | (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), |
|
256 |
|
|
|
328 | ( | |
|
329 | ( | |
|
257 | 330 | # a line ending with a colon, potentially with trailing comments |
|
258 | 331 | r':([ \t]*#[^\n]*)?\n' |
|
259 | 332 | # one that is not a pass and not only a comment |
@@ -262,93 +335,142 b' commonpypats = [' | |||
|
262 | 335 | r'((?P=indent)[^\n]+\n)*' |
|
263 | 336 | # a pass at the same indent level, which is bogus |
|
264 | 337 | r'(?P=indent)pass[ \t\n#]' |
|
265 | ), 'omit superfluous pass'), | |
|
338 | ), | |
|
339 | 'omit superfluous pass', | |
|
340 | ), | |
|
266 | 341 | (r'[^\n]\Z', "no trailing newline"), |
|
267 | 342 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), |
|
268 | 343 | # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', |
|
269 | 344 | # "don't use underbars in identifiers"), |
|
270 | (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ', | |
|
271 | "don't use camelcase in identifiers", r'#.*camelcase-required'), | |
|
272 | (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', | |
|
273 | "linebreak after :"), | |
|
274 | (r'class\s[^( \n]+:', "old-style class, use class foo(object)", | |
|
275 | r'#.*old-style'), | |
|
276 | (r'class\s[^( \n]+\(\):', | |
|
345 | ( | |
|
346 | r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ', | |
|
347 | "don't use camelcase in identifiers", | |
|
348 | r'#.*camelcase-required', | |
|
349 | ), | |
|
350 | ( | |
|
351 | r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', | |
|
352 | "linebreak after :", | |
|
353 | ), | |
|
354 | ( | |
|
355 | r'class\s[^( \n]+:', | |
|
356 | "old-style class, use class foo(object)", | |
|
357 | r'#.*old-style', | |
|
358 | ), | |
|
359 | ( | |
|
360 | r'class\s[^( \n]+\(\):', | |
|
277 | 361 | "class foo() creates old style object, use class foo(object)", |
|
278 |
r'#.*old-style' |
|
|
279 | (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist | |
|
280 | if k not in ('print', 'exec')), | |
|
281 | "Python keyword is not a function"), | |
|
362 | r'#.*old-style', | |
|
363 | ), | |
|
364 | ( | |
|
365 | r'\b(%s)\(' | |
|
366 | % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')), | |
|
367 | "Python keyword is not a function", | |
|
368 | ), | |
|
282 | 369 | # (r'class\s[A-Z][^\(]*\((?!Exception)', |
|
283 | 370 | # "don't capitalize non-exception classes"), |
|
284 | 371 | # (r'in range\(', "use xrange"), |
|
285 | 372 | # (r'^\s*print\s+', "avoid using print in core and extensions"), |
|
286 | 373 | (r'[\x80-\xff]', "non-ASCII character literal"), |
|
287 | 374 | (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"), |
|
288 | (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"), | |
|
375 | ( | |
|
376 | r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', | |
|
377 | "gratuitous whitespace in () or []", | |
|
378 | ), | |
|
289 | 379 | # (r'\s\s=', "gratuitous whitespace before ="), |
|
290 | (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', | |
|
291 | "missing whitespace around operator"), | |
|
292 | (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', | |
|
293 | "missing whitespace around operator"), | |
|
294 | (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', | |
|
295 | "missing whitespace around operator"), | |
|
296 | (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', | |
|
297 | "wrong whitespace around ="), | |
|
298 | (r'\([^()]*( =[^=]|[^<>!=]= )', | |
|
299 | "no whitespace around = for named parameters"), | |
|
300 | (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', | |
|
301 | "don't use old-style two-argument raise, use Exception(message)"), | |
|
380 | ( | |
|
381 | r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', | |
|
382 | "missing whitespace around operator", | |
|
383 | ), | |
|
384 | ( | |
|
385 | r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', | |
|
386 | "missing whitespace around operator", | |
|
387 | ), | |
|
388 | ( | |
|
389 | r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', | |
|
390 | "missing whitespace around operator", | |
|
391 | ), | |
|
392 | (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="), | |
|
393 | ( | |
|
394 | r'\([^()]*( =[^=]|[^<>!=]= )', | |
|
395 | "no whitespace around = for named parameters", | |
|
396 | ), | |
|
397 | ( | |
|
398 | r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', | |
|
399 | "don't use old-style two-argument raise, use Exception(message)", | |
|
400 | ), | |
|
302 | 401 | (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), |
|
303 | (r' [=!]=\s+(True|False|None)', | |
|
304 | "comparison with singleton, use 'is' or 'is not' instead"), | |
|
305 | (r'^\s*(while|if) [01]:', | |
|
306 | "use True/False for constant Boolean expression"), | |
|
402 | ( | |
|
403 | r' [=!]=\s+(True|False|None)', | |
|
404 | "comparison with singleton, use 'is' or 'is not' instead", | |
|
405 | ), | |
|
406 | ( | |
|
407 | r'^\s*(while|if) [01]:', | |
|
408 | "use True/False for constant Boolean expression", | |
|
409 | ), | |
|
307 | 410 | (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'), |
|
308 | (r'(?:(?<!def)\s+|\()hasattr\(', | |
|
411 | ( | |
|
412 | r'(?:(?<!def)\s+|\()hasattr\(', | |
|
309 | 413 | 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) ' |
|
310 | 'instead', r'#.*hasattr-py3-only'), | |
|
311 | (r'opener\([^)]*\).read\(', | |
|
312 | "use opener.read() instead"), | |
|
313 | (r'opener\([^)]*\).write\(', | |
|
314 | "use opener.write() instead"), | |
|
414 | 'instead', | |
|
415 | r'#.*hasattr-py3-only', | |
|
416 | ), | |
|
417 | (r'opener\([^)]*\).read\(', "use opener.read() instead"), | |
|
418 | (r'opener\([^)]*\).write\(', "use opener.write() instead"), | |
|
315 | 419 | (r'(?i)descend[e]nt', "the proper spelling is descendAnt"), |
|
316 | 420 | (r'\.debug\(\_', "don't mark debug messages for translation"), |
|
317 | 421 | (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"), |
|
318 | 422 | (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'), |
|
319 | (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,', | |
|
320 | 'legacy exception syntax; use "as" instead of ","'), | |
|
423 | ( | |
|
424 | r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,', | |
|
425 | 'legacy exception syntax; use "as" instead of ","', | |
|
426 | ), | |
|
321 | 427 | (r'release\(.*wlock, .*lock\)', "wrong lock release order"), |
|
322 | 428 | (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"), |
|
323 | (r'os\.path\.join\(.*, *(""|\'\')\)', | |
|
324 | "use pathutil.normasprefix(path) instead of os.path.join(path, '')"), | |
|
429 | ( | |
|
430 | r'os\.path\.join\(.*, *(""|\'\')\)', | |
|
431 | "use pathutil.normasprefix(path) instead of os.path.join(path, '')", | |
|
432 | ), | |
|
325 | 433 | (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'), |
|
326 | 434 | # XXX only catch mutable arguments on the first line of the definition |
|
327 | 435 | (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"), |
|
328 | 436 | (r'\butil\.Abort\b', "directly use error.Abort"), |
|
329 | (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"), | |
|
330 | (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + " | |
|
331 | "pycompat.queue.Empty"), | |
|
332 | (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"), | |
|
437 | ( | |
|
438 | r'^@(\w*\.)?cachefunc', | |
|
439 | "module-level @cachefunc is risky, please avoid", | |
|
440 | ), | |
|
441 | ( | |
|
442 | r'^import Queue', | |
|
443 | "don't use Queue, use pycompat.queue.Queue + " | |
|
444 | "pycompat.queue.Empty", | |
|
445 | ), | |
|
446 | ( | |
|
447 | r'^import cStringIO', | |
|
448 | "don't use cStringIO.StringIO, use util.stringio", | |
|
449 | ), | |
|
333 | 450 | (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"), |
|
334 | (r'^import SocketServer', "don't use SockerServer, use util.socketserver"), | |
|
451 | ( | |
|
452 | r'^import SocketServer', | |
|
453 | "don't use SockerServer, use util.socketserver", | |
|
454 | ), | |
|
335 | 455 | (r'^import urlparse', "don't use urlparse, use util.urlreq"), |
|
336 | 456 | (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"), |
|
337 | 457 | (r'^import cPickle', "don't use cPickle, use util.pickle"), |
|
338 | 458 | (r'^import pickle', "don't use pickle, use util.pickle"), |
|
339 | 459 | (r'^import httplib', "don't use httplib, use util.httplib"), |
|
340 | 460 | (r'^import BaseHTTPServer', "use util.httpserver instead"), |
|
341 | (r'^(from|import) mercurial\.(cext|pure|cffi)', | |
|
342 | "use mercurial.policy.importmod instead"), | |
|
461 | ( | |
|
462 | r'^(from|import) mercurial\.(cext|pure|cffi)', | |
|
463 | "use mercurial.policy.importmod instead", | |
|
464 | ), | |
|
343 | 465 | (r'\.next\(\)', "don't use .next(), use next(...)"), |
|
344 | (r'([a-z]*).revision\(\1\.node\(', | |
|
345 | "don't convert rev to node before passing to revision(nodeorrev)"), | |
|
466 | ( | |
|
467 | r'([a-z]*).revision\(\1\.node\(', | |
|
468 | "don't convert rev to node before passing to revision(nodeorrev)", | |
|
469 | ), | |
|
346 | 470 | (r'platform\.system\(\)', "don't use platform.system(), use pycompat"), |
|
347 | ||
|
348 | 471 | ], |
|
349 | 472 | # warnings |
|
350 | [ | |
|
351 | ] | |
|
473 | [], | |
|
352 | 474 | ] |
|
353 | 475 | |
|
354 | 476 | # patterns to check normal *.py files |
@@ -361,21 +483,28 b' pypats = [' | |||
|
361 | 483 | # scripts for these patterns requires many changes, and has less |
|
362 | 484 | # profit than effort. |
|
363 | 485 | (r'raise Exception', "don't raise generic exceptions"), |
|
364 | (r'[\s\(](open|file)\([^)]*\)\.read\(', | |
|
365 | "use util.readfile() instead"), | |
|
366 |
|
|
|
367 |
"use util.writefile() instead" |
|
|
368 | (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))', | |
|
369 | "always assign an opened file to a variable, and close it afterwards"), | |
|
370 |
|
|
|
371 |
"always assign an opened file to a variable, and close it afterwards" |
|
|
486 | (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"), | |
|
487 | ( | |
|
488 | r'[\s\(](open|file)\([^)]*\)\.write\(', | |
|
489 | "use util.writefile() instead", | |
|
490 | ), | |
|
491 | ( | |
|
492 | r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))', | |
|
493 | "always assign an opened file to a variable, and close it afterwards", | |
|
494 | ), | |
|
495 | ( | |
|
496 | r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))', | |
|
497 | "always assign an opened file to a variable, and close it afterwards", | |
|
498 | ), | |
|
372 | 499 | (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"), |
|
373 | 500 | (r'^import atexit', "don't use atexit, use ui.atexit"), |
|
374 | ||
|
375 | 501 | # rules depending on implementation of repquote() |
|
376 | (r' x+[xpqo%APM][\'"]\n\s+[\'"]x', | |
|
377 | 'string join across lines with no space'), | |
|
378 | (r'''(?x)ui\.(status|progress|write|note|warn)\( | |
|
502 | ( | |
|
503 | r' x+[xpqo%APM][\'"]\n\s+[\'"]x', | |
|
504 | 'string join across lines with no space', | |
|
505 | ), | |
|
506 | ( | |
|
507 | r'''(?x)ui\.(status|progress|write|note|warn)\( | |
|
379 | 508 | [ \t\n#]* |
|
380 | 509 |
|
|
381 | 510 |
|
@@ -389,51 +518,55 b' pypats = [' | |||
|
389 | 518 | (?# this regexp can't use [^...] style, |
|
390 | 519 | # because _preparepats forcibly adds "\n" into [^...], |
|
391 | 520 | # even though this regexp wants match it against "\n")''', |
|
392 |
"missing _() in ui message (use () to hide false-positives)" |
|
|
393 | ] + commonpypats[0], | |
|
521 | "missing _() in ui message (use () to hide false-positives)", | |
|
522 | ), | |
|
523 | ] | |
|
524 | + commonpypats[0], | |
|
394 | 525 | # warnings |
|
395 | 526 | [ |
|
396 | 527 | # rules depending on implementation of repquote() |
|
397 | 528 | (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"), |
|
398 | ] + commonpypats[1] | |
|
529 | ] | |
|
530 | + commonpypats[1], | |
|
399 | 531 | ] |
|
400 | 532 | |
|
401 | 533 | # patterns to check *.py for embedded ones in test script |
|
402 | 534 | embeddedpypats = [ |
|
403 | [ | |
|
404 | ] + commonpypats[0], | |
|
535 | [] + commonpypats[0], | |
|
405 | 536 | # warnings |
|
406 | [ | |
|
407 | ] + commonpypats[1] | |
|
537 | [] + commonpypats[1], | |
|
408 | 538 | ] |
|
409 | 539 | |
|
410 | 540 | # common filters to convert *.py |
|
411 | 541 | commonpyfilters = [ |
|
412 | (r"""(?msx)(?P<comment>\#.*?$)| | |
|
542 | ( | |
|
543 | r"""(?msx)(?P<comment>\#.*?$)| | |
|
413 | 544 | ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!"))) |
|
414 | 545 | (?P<text>(([^\\]|\\.)*?)) |
|
415 |
(?P=quote))""" |
|
|
546 | (?P=quote))""", | |
|
547 | reppython, | |
|
548 | ), | |
|
416 | 549 | ] |
|
417 | 550 | |
|
418 | 551 | # filters to convert normal *.py files |
|
419 | pyfilters = [ | |
|
420 | ] + commonpyfilters | |
|
552 | pyfilters = [] + commonpyfilters | |
|
421 | 553 | |
|
422 | 554 | # non-filter patterns |
|
423 | 555 | pynfpats = [ |
|
424 | 556 | [ |
|
425 | 557 | (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"), |
|
426 | 558 | (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"), |
|
427 | (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]', | |
|
428 | "use pycompat.isdarwin"), | |
|
559 | ( | |
|
560 | r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]', | |
|
561 | "use pycompat.isdarwin", | |
|
562 | ), | |
|
429 | 563 | ], |
|
430 | 564 | # warnings |
|
431 | 565 | [], |
|
432 | 566 | ] |
|
433 | 567 | |
|
434 | 568 | # filters to convert *.py for embedded ones in test script |
|
435 | embeddedpyfilters = [ | |
|
436 | ] + commonpyfilters | |
|
569 | embeddedpyfilters = [] + commonpyfilters | |
|
437 | 570 | |
|
438 | 571 | # extension non-filter patterns |
|
439 | 572 | pyextnfpats = [ |
@@ -447,9 +580,9 b' txtfilters = []' | |||
|
447 | 580 | txtpats = [ |
|
448 | 581 | [ |
|
449 | 582 | (r'\s$', 'trailing whitespace'), |
|
450 | ('.. note::[ \n][^\n]', 'add two newlines after note::') | |
|
583 | ('.. note::[ \n][^\n]', 'add two newlines after note::'), | |
|
451 | 584 | ], |
|
452 | [] | |
|
585 | [], | |
|
453 | 586 | ] |
|
454 | 587 | |
|
455 | 588 | cpats = [ |
@@ -473,13 +606,12 b' cpats = [' | |||
|
473 | 606 | (r'^\s*#import\b', "use only #include in standard C code"), |
|
474 | 607 | (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"), |
|
475 | 608 | (r'strcat\(', "don't use strcat"), |
|
476 | ||
|
477 | 609 | # rules depending on implementation of repquote() |
|
478 | 610 | ], |
|
479 | 611 | # warnings |
|
480 | 612 | [ |
|
481 | 613 | # rules depending on implementation of repquote() |
|
482 | ] | |
|
614 | ], | |
|
483 | 615 | ] |
|
484 | 616 | |
|
485 | 617 | cfilters = [ |
@@ -490,19 +622,15 b' cfilters = [' | |||
|
490 | 622 | ] |
|
491 | 623 | |
|
492 | 624 | inutilpats = [ |
|
493 | [ | |
|
494 | (r'\bui\.', "don't use ui in util"), | |
|
495 | ], | |
|
625 | [(r'\bui\.', "don't use ui in util"),], | |
|
496 | 626 | # warnings |
|
497 | [] | |
|
627 | [], | |
|
498 | 628 | ] |
|
499 | 629 | |
|
500 | 630 | inrevlogpats = [ |
|
501 | [ | |
|
502 | (r'\brepo\.', "don't use repo in revlog"), | |
|
503 | ], | |
|
631 | [(r'\brepo\.', "don't use repo in revlog"),], | |
|
504 | 632 | # warnings |
|
505 | [] | |
|
633 | [], | |
|
506 | 634 | ] |
|
507 | 635 | |
|
508 | 636 | webtemplatefilters = [] |
@@ -510,21 +638,29 b' webtemplatefilters = []' | |||
|
510 | 638 | webtemplatepats = [ |
|
511 | 639 | [], |
|
512 | 640 | [ |
|
513 | (r'{desc(\|(?!websub|firstline)[^\|]*)+}', | |
|
514 | 'follow desc keyword with either firstline or websub'), | |
|
515 | ] | |
|
641 | ( | |
|
642 | r'{desc(\|(?!websub|firstline)[^\|]*)+}', | |
|
643 | 'follow desc keyword with either firstline or websub', | |
|
644 | ), | |
|
645 | ], | |
|
516 | 646 | ] |
|
517 | 647 | |
|
518 | 648 | allfilesfilters = [] |
|
519 | 649 | |
|
520 | 650 | allfilespats = [ |
|
521 | 651 | [ |
|
522 | (r'(http|https)://[a-zA-Z0-9./]*selenic.com/', | |
|
523 | 'use mercurial-scm.org domain URL'), | |
|
524 | (r'mercurial@selenic\.com', | |
|
525 | 'use mercurial-scm.org domain for mercurial ML address'), | |
|
526 | (r'mercurial-devel@selenic\.com', | |
|
527 | 'use mercurial-scm.org domain for mercurial-devel ML address'), | |
|
652 | ( | |
|
653 | r'(http|https)://[a-zA-Z0-9./]*selenic.com/', | |
|
654 | 'use mercurial-scm.org domain URL', | |
|
655 | ), | |
|
656 | ( | |
|
657 | r'mercurial@selenic\.com', | |
|
658 | 'use mercurial-scm.org domain for mercurial ML address', | |
|
659 | ), | |
|
660 | ( | |
|
661 | r'mercurial-devel@selenic\.com', | |
|
662 | 'use mercurial-scm.org domain for mercurial-devel ML address', | |
|
663 | ), | |
|
528 | 664 | ], |
|
529 | 665 | # warnings |
|
530 | 666 | [], |
@@ -532,7 +668,11 b' allfilespats = [' | |||
|
532 | 668 | |
|
533 | 669 | py3pats = [ |
|
534 | 670 | [ |
|
535 | (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'), | |
|
671 | ( | |
|
672 | r'os\.environ', | |
|
673 | "use encoding.environ instead (py3)", | |
|
674 | r'#.*re-exports', | |
|
675 | ), | |
|
536 | 676 | (r'os\.name', "use pycompat.osname instead (py3)"), |
|
537 | 677 | (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'), |
|
538 | 678 | (r'os\.sep', "use pycompat.ossep instead (py3)"), |
@@ -552,20 +692,39 b' checks = [' | |||
|
552 | 692 | ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats), |
|
553 | 693 | ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats), |
|
554 | 694 | ('python', r'.*hgext.*\.py$', '', [], pyextnfpats), |
|
555 | ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py', | |
|
556 | '', pyfilters, py3pats), | |
|
695 | ( | |
|
696 | 'python 3', | |
|
697 | r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py', | |
|
698 | '', | |
|
699 | pyfilters, | |
|
700 | py3pats, | |
|
701 | ), | |
|
557 | 702 | ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats), |
|
558 | 703 | ('c', r'.*\.[ch]$', '', cfilters, cpats), |
|
559 | 704 | ('unified test', r'.*\.t$', '', utestfilters, utestpats), |
|
560 | ('layering violation repo in revlog', r'mercurial/revlog\.py', '', | |
|
561 | pyfilters, inrevlogpats), | |
|
562 | ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters, | |
|
563 | inutilpats), | |
|
705 | ( | |
|
706 | 'layering violation repo in revlog', | |
|
707 | r'mercurial/revlog\.py', | |
|
708 | '', | |
|
709 | pyfilters, | |
|
710 | inrevlogpats, | |
|
711 | ), | |
|
712 | ( | |
|
713 | 'layering violation ui in util', | |
|
714 | r'mercurial/util\.py', | |
|
715 | '', | |
|
716 | pyfilters, | |
|
717 | inutilpats, | |
|
718 | ), | |
|
564 | 719 | ('txt', r'.*\.txt$', '', txtfilters, txtpats), |
|
565 | ('web template', r'mercurial/templates/.*\.tmpl', '', | |
|
566 | webtemplatefilters, webtemplatepats), | |
|
567 | ('all except for .po', r'.*(?<!\.po)$', '', | |
|
568 | allfilesfilters, allfilespats), | |
|
720 | ( | |
|
721 | 'web template', | |
|
722 | r'mercurial/templates/.*\.tmpl', | |
|
723 | '', | |
|
724 | webtemplatefilters, | |
|
725 | webtemplatepats, | |
|
726 | ), | |
|
727 | ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats), | |
|
569 | 728 | ] |
|
570 | 729 | |
|
571 | 730 | # (desc, |
@@ -573,10 +732,15 b' checks = [' | |||
|
573 | 732 | # list of patterns to convert target files |
|
574 | 733 | # list of patterns to detect errors/warnings) |
|
575 | 734 | embeddedchecks = [ |
|
576 | ('embedded python', | |
|
577 | testparseutil.pyembedded, embeddedpyfilters, embeddedpypats) | |
|
735 | ( | |
|
736 | 'embedded python', | |
|
737 | testparseutil.pyembedded, | |
|
738 | embeddedpyfilters, | |
|
739 | embeddedpypats, | |
|
740 | ) | |
|
578 | 741 | ] |
|
579 | 742 | |
|
743 | ||
|
580 | 744 | def _preparepats(): |
|
581 | 745 | def preparefailandwarn(failandwarn): |
|
582 | 746 | for pats in failandwarn: |
@@ -605,6 +769,7 b' def _preparepats():' | |||
|
605 | 769 | filters = c[-2] |
|
606 | 770 | preparefilters(filters) |
|
607 | 771 | |
|
772 | ||
|
608 | 773 | class norepeatlogger(object): |
|
609 | 774 | def __init__(self): |
|
610 | 775 | self._lastseen = None |
@@ -630,8 +795,10 b' class norepeatlogger(object):' | |||
|
630 | 795 | self._lastseen = msgid |
|
631 | 796 | print(" " + msg) |
|
632 | 797 | |
|
798 | ||
|
633 | 799 | _defaultlogger = norepeatlogger() |
|
634 | 800 | |
|
801 | ||
|
635 | 802 | def getblame(f): |
|
636 | 803 | lines = [] |
|
637 | 804 | for l in os.popen('hg annotate -un %s' % f): |
@@ -640,8 +807,16 b' def getblame(f):' | |||
|
640 | 807 | lines.append((line[1:-1], user, rev)) |
|
641 | 808 | return lines |
|
642 | 809 | |
|
643 | def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False, | |
|
644 | blame=False, debug=False, lineno=True): | |
|
810 | ||
|
811 | def checkfile( | |
|
812 | f, | |
|
813 | logfunc=_defaultlogger.log, | |
|
814 | maxerr=None, | |
|
815 | warnings=False, | |
|
816 | blame=False, | |
|
817 | debug=False, | |
|
818 | lineno=True, | |
|
819 | ): | |
|
645 | 820 | """checks style and portability of a given file |
|
646 | 821 | |
|
647 | 822 | :f: filepath |
@@ -673,8 +848,9 b' def checkfile(f, logfunc=_defaultlogger.' | |||
|
673 | 848 | print(name, f) |
|
674 | 849 | if not (re.match(match, f) or (magic and re.search(magic, pre))): |
|
675 | 850 | if debug: |
|
676 | print("Skipping %s for %s it doesn't match %s" % ( | |
|
677 |
name, match, f) |
|
|
851 | print( | |
|
852 | "Skipping %s for %s it doesn't match %s" % (name, match, f) | |
|
853 | ) | |
|
678 | 854 | continue |
|
679 | 855 | if "no-" "check-code" in pre: |
|
680 | 856 | # If you're looking at this line, it's because a file has: |
@@ -686,14 +862,26 b' def checkfile(f, logfunc=_defaultlogger.' | |||
|
686 | 862 | print("Skipping %s it has no-che?k-code (glob)" % f) |
|
687 | 863 | return "Skip" # skip checking this file |
|
688 | 864 | |
|
689 |
fc = _checkfiledata( |
|
|
690 | logfunc, maxerr, warnings, blame, debug, lineno) | |
|
865 | fc = _checkfiledata( | |
|
866 | name, | |
|
867 | f, | |
|
868 | pre, | |
|
869 | filters, | |
|
870 | pats, | |
|
871 | context, | |
|
872 | logfunc, | |
|
873 | maxerr, | |
|
874 | warnings, | |
|
875 | blame, | |
|
876 | debug, | |
|
877 | lineno, | |
|
878 | ) | |
|
691 | 879 | if fc: |
|
692 | 880 | result = False |
|
693 | 881 | |
|
694 | 882 | if f.endswith('.t') and "no-" "check-code" not in pre: |
|
695 | 883 | if debug: |
|
696 |
print("Checking embedded code in %s" % |
|
|
884 | print("Checking embedded code in %s" % f) | |
|
697 | 885 | |
|
698 | 886 | prelines = pre.splitlines() |
|
699 | 887 | embeddederros = [] |
@@ -705,9 +893,21 b' def checkfile(f, logfunc=_defaultlogger.' | |||
|
705 | 893 | |
|
706 | 894 | for found in embedded(f, prelines, embeddederros): |
|
707 | 895 | filename, starts, ends, code = found |
|
708 |
fc = _checkfiledata( |
|
|
709 | logfunc, curmaxerr, warnings, blame, debug, | |
|
710 | lineno, offset=starts - 1) | |
|
896 | fc = _checkfiledata( | |
|
897 | name, | |
|
898 | f, | |
|
899 | code, | |
|
900 | filters, | |
|
901 | pats, | |
|
902 | context, | |
|
903 | logfunc, | |
|
904 | curmaxerr, | |
|
905 | warnings, | |
|
906 | blame, | |
|
907 | debug, | |
|
908 | lineno, | |
|
909 | offset=starts - 1, | |
|
910 | ) | |
|
711 | 911 | if fc: |
|
712 | 912 | result = False |
|
713 | 913 | if curmaxerr: |
@@ -717,9 +917,22 b' def checkfile(f, logfunc=_defaultlogger.' | |||
|
717 | 917 | |
|
718 | 918 | return result |
|
719 | 919 | |
|
720 | def _checkfiledata(name, f, filedata, filters, pats, context, | |
|
721 | logfunc, maxerr, warnings, blame, debug, lineno, | |
|
722 | offset=None): | |
|
920 | ||
|
921 | def _checkfiledata( | |
|
922 | name, | |
|
923 | f, | |
|
924 | filedata, | |
|
925 | filters, | |
|
926 | pats, | |
|
927 | context, | |
|
928 | logfunc, | |
|
929 | maxerr, | |
|
930 | warnings, | |
|
931 | blame, | |
|
932 | debug, | |
|
933 | lineno, | |
|
934 | offset=None, | |
|
935 | ): | |
|
723 | 936 | """Execute actual error check for file data |
|
724 | 937 | |
|
725 | 938 | :name: of the checking category |
@@ -794,8 +1007,10 b' def _checkfiledata(name, f, filedata, fi' | |||
|
794 | 1007 | |
|
795 | 1008 | if ignore and re.search(ignore, l, re.MULTILINE): |
|
796 | 1009 | if debug: |
|
797 | print("Skipping %s for %s:%s (ignore pattern)" % ( | |
|
798 |
|
|
|
1010 | print( | |
|
1011 | "Skipping %s for %s:%s (ignore pattern)" | |
|
1012 | % (name, f, (n + lineoffset)) | |
|
1013 | ) | |
|
799 | 1014 | continue |
|
800 | 1015 | bd = "" |
|
801 | 1016 | if blame: |
@@ -830,21 +1045,38 b' def _checkfiledata(name, f, filedata, fi' | |||
|
830 | 1045 | |
|
831 | 1046 | return fc |
|
832 | 1047 | |
|
1048 | ||
|
833 | 1049 | def main(): |
|
834 | 1050 | parser = optparse.OptionParser("%prog [options] [files | -]") |
|
835 | parser.add_option("-w", "--warnings", action="store_true", | |
|
836 | help="include warning-level checks") | |
|
837 | parser.add_option("-p", "--per-file", type="int", | |
|
838 | help="max warnings per file") | |
|
839 | parser.add_option("-b", "--blame", action="store_true", | |
|
840 | help="use annotate to generate blame info") | |
|
841 | parser.add_option("", "--debug", action="store_true", | |
|
842 | help="show debug information") | |
|
843 | parser.add_option("", "--nolineno", action="store_false", | |
|
844 | dest='lineno', help="don't show line numbers") | |
|
1051 | parser.add_option( | |
|
1052 | "-w", | |
|
1053 | "--warnings", | |
|
1054 | action="store_true", | |
|
1055 | help="include warning-level checks", | |
|
1056 | ) | |
|
1057 | parser.add_option( | |
|
1058 | "-p", "--per-file", type="int", help="max warnings per file" | |
|
1059 | ) | |
|
1060 | parser.add_option( | |
|
1061 | "-b", | |
|
1062 | "--blame", | |
|
1063 | action="store_true", | |
|
1064 | help="use annotate to generate blame info", | |
|
1065 | ) | |
|
1066 | parser.add_option( | |
|
1067 | "", "--debug", action="store_true", help="show debug information" | |
|
1068 | ) | |
|
1069 | parser.add_option( | |
|
1070 | "", | |
|
1071 | "--nolineno", | |
|
1072 | action="store_false", | |
|
1073 | dest='lineno', | |
|
1074 | help="don't show line numbers", | |
|
1075 | ) | |
|
845 | 1076 | |
|
846 | parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False, | |
|
847 | lineno=True) | |
|
1077 | parser.set_defaults( | |
|
1078 | per_file=15, warnings=False, blame=False, debug=False, lineno=True | |
|
1079 | ) | |
|
848 | 1080 | (options, args) = parser.parse_args() |
|
849 | 1081 | |
|
850 | 1082 | if len(args) == 0: |
@@ -859,11 +1091,17 b' def main():' | |||
|
859 | 1091 | |
|
860 | 1092 | ret = 0 |
|
861 | 1093 | for f in check: |
|
862 | if not checkfile(f, maxerr=options.per_file, warnings=options.warnings, | |
|
863 | blame=options.blame, debug=options.debug, | |
|
864 | lineno=options.lineno): | |
|
1094 | if not checkfile( | |
|
1095 | f, | |
|
1096 | maxerr=options.per_file, | |
|
1097 | warnings=options.warnings, | |
|
1098 | blame=options.blame, | |
|
1099 | debug=options.debug, | |
|
1100 | lineno=options.lineno, | |
|
1101 | ): | |
|
865 | 1102 | ret = 1 |
|
866 | 1103 | return ret |
|
867 | 1104 | |
|
1105 | ||
|
868 | 1106 | if __name__ == "__main__": |
|
869 | 1107 | sys.exit(main()) |
@@ -15,7 +15,8 b' foundopts = {}' | |||
|
15 | 15 | documented = {} |
|
16 | 16 | allowinconsistent = set() |
|
17 | 17 | |
|
18 |
configre = re.compile( |
|
|
18 | configre = re.compile( | |
|
19 | br''' | |
|
19 | 20 |
|
|
20 | 21 | ui\.config(?P<ctype>|int|bool|list)\( |
|
21 | 22 |
|
@@ -23,9 +24,12 b" configre = re.compile(br'''" | |||
|
23 | 24 |
|
|
24 | 25 | ['"](?P<option>\S+)['"](,\s+ |
|
25 | 26 | (?:default=)?(?P<default>\S+?))? |
|
26 | \)''', re.VERBOSE | re.MULTILINE) | |
|
27 | \)''', | |
|
28 | re.VERBOSE | re.MULTILINE, | |
|
29 | ) | |
|
27 | 30 | |
|
28 |
configwithre = re.compile( |
|
|
31 | configwithre = re.compile( | |
|
32 | br''' | |
|
29 | 33 | ui\.config(?P<ctype>with)\( |
|
30 | 34 | # First argument is callback function. This doesn't parse robustly |
|
31 | 35 |
|
@@ -33,23 +37,32 b" configwithre = re.compile(br'''" | |||
|
33 | 37 | ['"](?P<section>\S+)['"],\s* |
|
34 | 38 | ['"](?P<option>\S+)['"](,\s+ |
|
35 | 39 | (?:default=)?(?P<default>\S+?))? |
|
36 | \)''', re.VERBOSE | re.MULTILINE) | |
|
40 | \)''', | |
|
41 | re.VERBOSE | re.MULTILINE, | |
|
42 | ) | |
|
37 | 43 | |
|
38 |
configpartialre = |
|
|
44 | configpartialre = br"""ui\.config""" | |
|
39 | 45 | |
|
40 |
ignorere = re.compile( |
|
|
46 | ignorere = re.compile( | |
|
47 | br''' | |
|
41 | 48 | \#\s(?P<reason>internal|experimental|deprecated|developer|inconsistent)\s |
|
42 | 49 | config:\s(?P<config>\S+\.\S+)$ |
|
43 | ''', re.VERBOSE | re.MULTILINE) | |
|
50 | ''', | |
|
51 | re.VERBOSE | re.MULTILINE, | |
|
52 | ) | |
|
44 | 53 | |
|
45 | 54 | if sys.version_info[0] > 2: |
|
55 | ||
|
46 | 56 | def mkstr(b): |
|
47 | 57 | if isinstance(b, str): |
|
48 | 58 | return b |
|
49 | 59 | return b.decode('utf8') |
|
60 | ||
|
61 | ||
|
50 | 62 | else: |
|
51 | 63 | mkstr = lambda x: x |
|
52 | 64 | |
|
65 | ||
|
53 | 66 | def main(args): |
|
54 | 67 | for f in args: |
|
55 | 68 | sect = b'' |
@@ -115,18 +128,32 b' def main(args):' | |||
|
115 | 128 | name = m.group('section') + b"." + m.group('option') |
|
116 | 129 | default = m.group('default') |
|
117 | 130 | if default in ( |
|
118 | None, b'False', b'None', b'0', b'[]', b'""', b"''"): | |
|
131 | None, | |
|
132 | b'False', | |
|
133 | b'None', | |
|
134 | b'0', | |
|
135 | b'[]', | |
|
136 | b'""', | |
|
137 | b"''", | |
|
138 | ): | |
|
119 | 139 | default = b'' |
|
120 | 140 | if re.match(b'[a-z.]+$', default): |
|
121 | 141 | default = b'<variable>' |
|
122 | if (name in foundopts and (ctype, default) != foundopts[name] | |
|
123 |
|
|
|
142 | if ( | |
|
143 | name in foundopts | |
|
144 | and (ctype, default) != foundopts[name] | |
|
145 | and name not in allowinconsistent | |
|
146 | ): | |
|
124 | 147 | print(mkstr(l.rstrip())) |
|
125 | 148 | fctype, fdefault = foundopts[name] |
|
126 |
print( |
|
|
149 | print( | |
|
150 | "conflict on %s: %r != %r" | |
|
151 | % ( | |
|
127 | 152 | mkstr(name), |
|
128 | 153 | (mkstr(ctype), mkstr(default)), |
|
129 |
(mkstr(fctype), mkstr(fdefault)) |
|
|
154 | (mkstr(fctype), mkstr(fdefault)), | |
|
155 | ) | |
|
156 | ) | |
|
130 | 157 | print("at %s:%d:" % (mkstr(f), linenum)) |
|
131 | 158 | foundopts[name] = (ctype, default) |
|
132 | 159 | carryover = b'' |
@@ -139,9 +166,11 b' def main(args):' | |||
|
139 | 166 | |
|
140 | 167 | for name in sorted(foundopts): |
|
141 | 168 | if name not in documented: |
|
142 | if not (name.startswith(b"devel.") or | |
|
143 |
|
|
|
144 |
|
|
|
169 | if not ( | |
|
170 | name.startswith(b"devel.") | |
|
171 | or name.startswith(b"experimental.") | |
|
172 | or name.startswith(b"debug.") | |
|
173 | ): | |
|
145 | 174 | ctype, default = foundopts[name] |
|
146 | 175 | if default: |
|
147 | 176 | if isinstance(default, bytes): |
@@ -149,8 +178,11 b' def main(args):' | |||
|
149 | 178 | default = ' [%s]' % default |
|
150 | 179 | elif isinstance(default, bytes): |
|
151 | 180 | default = mkstr(default) |
|
152 |
print( |
|
|
153 | mkstr(name), mkstr(ctype), default)) | |
|
181 | print( | |
|
182 | "undocumented: %s (%s)%s" | |
|
183 | % (mkstr(name), mkstr(ctype), default) | |
|
184 | ) | |
|
185 | ||
|
154 | 186 | |
|
155 | 187 | if __name__ == "__main__": |
|
156 | 188 | if len(sys.argv) > 1: |
@@ -16,6 +16,7 b' import sys' | |||
|
16 | 16 | import traceback |
|
17 | 17 | import warnings |
|
18 | 18 | |
|
19 | ||
|
19 | 20 | def check_compat_py2(f): |
|
20 | 21 | """Check Python 3 compatibility for a file with Python 2""" |
|
21 | 22 | with open(f, 'rb') as fh: |
@@ -40,6 +41,7 b' def check_compat_py2(f):' | |||
|
40 | 41 | if haveprint and 'print_function' not in futures: |
|
41 | 42 | print('%s requires print_function' % f) |
|
42 | 43 | |
|
44 | ||
|
43 | 45 | def check_compat_py3(f): |
|
44 | 46 | """Check Python 3 compatibility of a file with Python 3.""" |
|
45 | 47 | with open(f, 'rb') as fh: |
@@ -54,8 +56,9 b' def check_compat_py3(f):' | |||
|
54 | 56 | # Try to import the module. |
|
55 | 57 | # For now we only support modules in packages because figuring out module |
|
56 | 58 | # paths for things not in a package can be confusing. |
|
57 | if (f.startswith(('hgdemandimport/', 'hgext/', 'mercurial/')) | |
|
58 | and not f.endswith('__init__.py')): | |
|
59 | if f.startswith( | |
|
60 | ('hgdemandimport/', 'hgext/', 'mercurial/') | |
|
61 | ) and not f.endswith('__init__.py'): | |
|
59 | 62 | assert f.endswith('.py') |
|
60 | 63 | name = f.replace('/', '.')[:-3] |
|
61 | 64 | try: |
@@ -79,11 +82,16 b' def check_compat_py3(f):' | |||
|
79 | 82 | |
|
80 | 83 | if frame.filename: |
|
81 | 84 | filename = os.path.basename(frame.filename) |
|
82 | print('%s: error importing: <%s> %s (error at %s:%d)' % ( | |
|
83 | f, type(e).__name__, e, filename, frame.lineno)) | |
|
85 | print( | |
|
86 | '%s: error importing: <%s> %s (error at %s:%d)' | |
|
87 | % (f, type(e).__name__, e, filename, frame.lineno) | |
|
88 | ) | |
|
84 | 89 | else: |
|
85 | print('%s: error importing module: <%s> %s (line %d)' % ( | |
|
86 | f, type(e).__name__, e, frame.lineno)) | |
|
90 | print( | |
|
91 | '%s: error importing module: <%s> %s (line %d)' | |
|
92 | % (f, type(e).__name__, e, frame.lineno) | |
|
93 | ) | |
|
94 | ||
|
87 | 95 | |
|
88 | 96 | if __name__ == '__main__': |
|
89 | 97 | if sys.version_info[0] == 2: |
@@ -96,7 +104,10 b" if __name__ == '__main__':" | |||
|
96 | 104 | fn(f) |
|
97 | 105 | |
|
98 | 106 | for w in warns: |
|
99 | print(warnings.formatwarning(w.message, w.category, | |
|
100 | w.filename, w.lineno).rstrip()) | |
|
107 | print( | |
|
108 | warnings.formatwarning( | |
|
109 | w.message, w.category, w.filename, w.lineno | |
|
110 | ).rstrip() | |
|
111 | ) | |
|
101 | 112 | |
|
102 | 113 | sys.exit(0) |
@@ -23,6 +23,7 b" if sys.argv[1] == '-':" | |||
|
23 | 23 | else: |
|
24 | 24 | log = open(sys.argv[1], 'a') |
|
25 | 25 | |
|
26 | ||
|
26 | 27 | def read(size): |
|
27 | 28 | data = sys.stdin.read(size) |
|
28 | 29 | if not data: |
@@ -31,6 +32,7 b' def read(size):' | |||
|
31 | 32 | sys.stdout.flush() |
|
32 | 33 | return data |
|
33 | 34 | |
|
35 | ||
|
34 | 36 | try: |
|
35 | 37 | while True: |
|
36 | 38 | header = read(outputfmtsize) |
@@ -14,6 +14,7 b' from mercurial import (' | |||
|
14 | 14 | cmdtable = {} |
|
15 | 15 | command = registrar.command(cmdtable) |
|
16 | 16 | |
|
17 | ||
|
17 | 18 | def pdb(ui, repo, msg, **opts): |
|
18 | 19 | objects = { |
|
19 | 20 | 'mercurial': mercurial, |
@@ -24,6 +25,7 b' def pdb(ui, repo, msg, **opts):' | |||
|
24 | 25 | |
|
25 | 26 | code.interact(msg, local=objects) |
|
26 | 27 | |
|
28 | ||
|
27 | 29 | def ipdb(ui, repo, msg, **opts): |
|
28 | 30 | import IPython |
|
29 | 31 | |
@@ -33,16 +35,15 b' def ipdb(ui, repo, msg, **opts):' | |||
|
33 | 35 | |
|
34 | 36 | IPython.embed() |
|
35 | 37 | |
|
38 | ||
|
36 | 39 | @command(b'debugshell|dbsh', []) |
|
37 | 40 | def debugshell(ui, repo, **opts): |
|
38 |
bannermsg = |
|
|
39 |
|
|
|
40 |
|
|
|
41 | bannermsg = "loaded repo : %s\n" "using source: %s" % ( | |
|
42 | pycompat.sysstr(repo.root), | |
|
43 | mercurial.__path__[0], | |
|
44 | ) | |
|
41 | 45 | |
|
42 | pdbmap = { | |
|
43 | 'pdb' : 'code', | |
|
44 | 'ipdb' : 'IPython' | |
|
45 | } | |
|
46 | pdbmap = {'pdb': 'code', 'ipdb': 'IPython'} | |
|
46 | 47 | |
|
47 | 48 | debugger = ui.config(b"ui", b"debugger") |
|
48 | 49 | if not debugger: |
@@ -55,8 +56,10 b' def debugshell(ui, repo, **opts):' | |||
|
55 | 56 | with demandimport.deactivated(): |
|
56 | 57 | __import__(pdbmap[debugger]) |
|
57 | 58 | except ImportError: |
|
58 | ui.warn((b"%s debugger specified but %s module was not found\n") | |
|
59 | % (debugger, pdbmap[debugger])) | |
|
59 | ui.warn( | |
|
60 | b"%s debugger specified but %s module was not found\n" | |
|
61 | % (debugger, pdbmap[debugger]) | |
|
62 | ) | |
|
60 | 63 | debugger = b'pdb' |
|
61 | 64 | |
|
62 | 65 | getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts) |
@@ -13,6 +13,7 b' from mercurial import (' | |||
|
13 | 13 | extensions, |
|
14 | 14 | ) |
|
15 | 15 | |
|
16 | ||
|
16 | 17 | def nonnormalentries(dmap): |
|
17 | 18 | """Compute nonnormal entries from dirstate's dmap""" |
|
18 | 19 | res = set() |
@@ -21,6 +22,7 b' def nonnormalentries(dmap):' | |||
|
21 | 22 | res.add(f) |
|
22 | 23 | return res |
|
23 | 24 | |
|
25 | ||
|
24 | 26 | def checkconsistency(ui, orig, dmap, _nonnormalset, label): |
|
25 | 27 | """Compute nonnormalset from dmap, check that it matches _nonnormalset""" |
|
26 | 28 | nonnormalcomputedmap = nonnormalentries(dmap) |
@@ -30,15 +32,19 b' def checkconsistency(ui, orig, dmap, _no' | |||
|
30 | 32 | ui.develwarn(b"[nonnormalset] %s\n" % _nonnormalset, config=b'dirstate') |
|
31 | 33 | ui.develwarn(b"[map] %s\n" % nonnormalcomputedmap, config=b'dirstate') |
|
32 | 34 | |
|
35 | ||
|
33 | 36 | def _checkdirstate(orig, self, arg): |
|
34 | 37 | """Check nonnormal set consistency before and after the call to orig""" |
|
35 | checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, | |
|
36 | b"before") | |
|
38 | checkconsistency( | |
|
39 | self._ui, orig, self._map, self._map.nonnormalset, b"before" | |
|
40 | ) | |
|
37 | 41 | r = orig(self, arg) |
|
38 | checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, | |
|
39 | b"after") | |
|
42 | checkconsistency( | |
|
43 | self._ui, orig, self._map, self._map.nonnormalset, b"after" | |
|
44 | ) | |
|
40 | 45 | return r |
|
41 | 46 | |
|
47 | ||
|
42 | 48 | def extsetup(ui): |
|
43 | 49 | """Wrap functions modifying dirstate to check nonnormalset consistency""" |
|
44 | 50 | dirstatecl = dirstate.dirstate |
@@ -8,8 +8,7 b' ap = argparse.ArgumentParser()' | |||
|
8 | 8 | ap.add_argument("out", metavar="some.zip", type=str, nargs=1) |
|
9 | 9 | args = ap.parse_args() |
|
10 | 10 | |
|
11 | reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), | |
|
12 | '..', '..')) | |
|
11 | reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..')) | |
|
13 | 12 | dirstate = os.path.join(reporoot, '.hg', 'dirstate') |
|
14 | 13 | |
|
15 | 14 | with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf: |
@@ -33,4 +33,6 b' with zipfile.ZipFile(args.out[0], "w", z' | |||
|
33 | 33 | 'nhistedituserAugie Fackler <raf@durin42.com>\x00\x00\x00yA\xd7\x02' |
|
34 | 34 | 'MtA\xd4\xe1\x01,\x00\x00\x01\x03\x03"\xa5\xcb\x86\xb6\xf4\xbaO\xa0' |
|
35 | 35 | 'sH\xe7?\xcb\x9b\xc2n\xcfI\x9e\x14\xf0D\xf0!\x18DN\xcd\x97\x016\xa5' |
|
36 |
'\xef\xa06\xcb\x884\x8a\x03\x01\t\x08\x04\x1fef14operationhisted' |
|
|
36 | '\xef\xa06\xcb\x884\x8a\x03\x01\t\x08\x04\x1fef14operationhisted' | |
|
37 | ), | |
|
38 | ) |
@@ -8,7 +8,8 b' ap.add_argument("out", metavar="some.zip' | |||
|
8 | 8 | args = ap.parse_args() |
|
9 | 9 | |
|
10 | 10 | with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf: |
|
11 |
zf.writestr( |
|
|
11 | zf.writestr( | |
|
12 | "manifest_zero", | |
|
12 | 13 | '''PKG-INFO\09b3ed8f2b81095a13064402e930565f083346e9a |
|
13 | 14 | README\080b6e76643dcb44d4bc729e932fc464b3e36dbe3 |
|
14 | 15 | hg\0b6444347c629cc058d478023905cfb83b7f5bb9d |
@@ -22,9 +23,11 b' mercurial/transaction.py\\09d180df101dc14' | |||
|
22 | 23 | notes.txt\0703afcec5edb749cf5cec67831f554d6da13f2fb |
|
23 | 24 | setup.py\0ccf3f6daf0f13101ca73631f7a1769e328b472c9 |
|
24 | 25 | tkmerge\03c922edb43a9c143682f7bc7b00f98b3c756ebe7 |
|
25 |
''' |
|
|
26 | zf.writestr("badmanifest_shorthashes", | |
|
27 | "narf\0aa\nnarf2\0aaa\n") | |
|
28 |
zf.writestr( |
|
|
26 | ''', | |
|
27 | ) | |
|
28 | zf.writestr("badmanifest_shorthashes", "narf\0aa\nnarf2\0aaa\n") | |
|
29 | zf.writestr( | |
|
30 | "badmanifest_nonull", | |
|
29 | 31 |
|
|
30 |
|
|
|
32 | "narf2aaaaaaaaaaaaaaaaaaaa\n", | |
|
33 | ) |
@@ -13,6 +13,7 b' ap = argparse.ArgumentParser()' | |||
|
13 | 13 | ap.add_argument("out", metavar="some.zip", type=str, nargs=1) |
|
14 | 14 | args = ap.parse_args() |
|
15 | 15 | |
|
16 | ||
|
16 | 17 | class deltafrag(object): |
|
17 | 18 | def __init__(self, start, end, data): |
|
18 | 19 | self.start = start |
@@ -20,8 +21,11 b' class deltafrag(object):' | |||
|
20 | 21 | self.data = data |
|
21 | 22 | |
|
22 | 23 | def __str__(self): |
|
23 |
return |
|
|
24 |
">lll", self.start, self.end, len(self.data)) |
|
|
24 | return ( | |
|
25 | struct.pack(">lll", self.start, self.end, len(self.data)) | |
|
26 | + self.data | |
|
27 | ) | |
|
28 | ||
|
25 | 29 | |
|
26 | 30 | class delta(object): |
|
27 | 31 | def __init__(self, frags): |
@@ -30,8 +34,8 b' class delta(object):' | |||
|
30 | 34 | def __str__(self): |
|
31 | 35 | return ''.join(str(f) for f in self.frags) |
|
32 | 36 | |
|
37 | ||
|
33 | 38 | class corpus(object): |
|
34 | ||
|
35 | 39 | def __init__(self, base, deltas): |
|
36 | 40 | self.base = base |
|
37 | 41 | self.deltas = deltas |
@@ -49,19 +53,19 b' class corpus(object):' | |||
|
49 | 53 | ) |
|
50 | 54 | return "".join(parts) |
|
51 | 55 | |
|
56 | ||
|
52 | 57 | with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf: |
|
53 | 58 | # Manually constructed entries |
|
54 | 59 | zf.writestr( |
|
55 | "one_delta_applies", | |
|
56 | str(corpus('a', [delta([deltafrag(0, 1, 'b')])])) | |
|
60 | "one_delta_applies", str(corpus('a', [delta([deltafrag(0, 1, 'b')])])) | |
|
57 | 61 | ) |
|
58 | 62 | zf.writestr( |
|
59 | 63 | "one_delta_starts_late", |
|
60 | str(corpus('a', [delta([deltafrag(3, 1, 'b')])])) | |
|
64 | str(corpus('a', [delta([deltafrag(3, 1, 'b')])])), | |
|
61 | 65 | ) |
|
62 | 66 | zf.writestr( |
|
63 | 67 | "one_delta_ends_late", |
|
64 | str(corpus('a', [delta([deltafrag(0, 20, 'b')])])) | |
|
68 | str(corpus('a', [delta([deltafrag(0, 20, 'b')])])), | |
|
65 | 69 | ) |
|
66 | 70 | |
|
67 | 71 | try: |
@@ -70,8 +74,7 b' with zipfile.ZipFile(args.out[0], "w", z' | |||
|
70 | 74 | fl = r.file('mercurial/manifest.py') |
|
71 | 75 | rl = getattr(fl, '_revlog', fl) |
|
72 | 76 | bins = rl._chunks(rl._deltachain(10)[0]) |
|
73 | zf.writestr('manifest_py_rev_10', | |
|
74 | str(corpus(bins[0], bins[1:]))) | |
|
77 | zf.writestr('manifest_py_rev_10', str(corpus(bins[0], bins[1:]))) | |
|
75 | 78 | except: # skip this, so no re-raises |
|
76 | 79 | print('skipping seed file from repo data') |
|
77 | 80 | # Automatically discovered by running the fuzzer |
@@ -81,7 +84,8 b' with zipfile.ZipFile(args.out[0], "w", z' | |||
|
81 | 84 | # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=8876 |
|
82 | 85 | zf.writestr( |
|
83 | 86 | "mpatch_ossfuzz_getbe32_ubsan", |
|
84 |
"\x02\x00\x00\x00\x0c \xff\xff\xff\xff " |
|
|
87 | "\x02\x00\x00\x00\x0c \xff\xff\xff\xff ", | |
|
88 | ) | |
|
85 | 89 | zf.writestr( |
|
86 | 90 | "mpatch_apply_over_memcpy", |
|
87 | 91 | '\x13\x01\x00\x05\xd0\x00\x00\x00\x00\x00\x00\x00\x00\n \x00\x00\x00' |
@@ -342,4 +346,5 b' with zipfile.ZipFile(args.out[0], "w", z' | |||
|
342 | 346 | '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
343 | 347 | '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00se\x00\x00' |
|
344 | 348 | '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
345 |
'\x00\x00\x00\x00' |
|
|
349 | '\x00\x00\x00\x00', | |
|
350 | ) |
@@ -8,13 +8,13 b' ap = argparse.ArgumentParser()' | |||
|
8 | 8 | ap.add_argument("out", metavar="some.zip", type=str, nargs=1) |
|
9 | 9 | args = ap.parse_args() |
|
10 | 10 | |
|
11 | reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), | |
|
12 | '..', '..')) | |
|
11 | reporoot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..')) | |
|
13 | 12 | # typically a standalone index |
|
14 | 13 | changelog = os.path.join(reporoot, '.hg', 'store', '00changelog.i') |
|
15 | 14 | # an inline revlog with only a few revisions |
|
16 | 15 | contributing = os.path.join( |
|
17 |
reporoot, '.hg', 'store', 'data', 'contrib', 'fuzz', 'mpatch.cc.i' |
|
|
16 | reporoot, '.hg', 'store', 'data', 'contrib', 'fuzz', 'mpatch.cc.i' | |
|
17 | ) | |
|
18 | 18 | |
|
19 | 19 | print(changelog, os.path.exists(changelog)) |
|
20 | 20 | print(contributing, os.path.exists(contributing)) |
@@ -7,21 +7,25 b' import subprocess' | |||
|
7 | 7 | import sys |
|
8 | 8 | |
|
9 | 9 | # Always load hg libraries from the hg we can find on $PATH. |
|
10 | hglib = subprocess.check_output( | |
|
11 | ['hg', 'debuginstall', '-T', '{hgmodules}']) | |
|
10 | hglib = subprocess.check_output(['hg', 'debuginstall', '-T', '{hgmodules}']) | |
|
12 | 11 | sys.path.insert(0, os.path.dirname(hglib)) |
|
13 | 12 | |
|
14 | 13 | from mercurial import util |
|
15 | 14 | |
|
16 | 15 | ap = argparse.ArgumentParser() |
|
17 |
ap.add_argument( |
|
|
16 | ap.add_argument( | |
|
17 | '--paranoid', | |
|
18 | 18 |
|
|
19 | help=("Be paranoid about how version numbers compare and " | |
|
19 | help=( | |
|
20 | "Be paranoid about how version numbers compare and " | |
|
20 | 21 |
|
|
21 |
|
|
|
22 | "reasonably." | |
|
23 | ), | |
|
24 | ) | |
|
22 | 25 | ap.add_argument('--selftest', action='store_true', help='Run self-tests.') |
|
23 | 26 | ap.add_argument('versionfile', help='Path to a valid mercurial __version__.py') |
|
24 | 27 | |
|
28 | ||
|
25 | 29 | def paranoidver(ver): |
|
26 | 30 | """Given an hg version produce something that distutils can sort. |
|
27 | 31 | |
@@ -108,10 +112,12 b' def paranoidver(ver):' | |||
|
108 | 112 | extra = '' |
|
109 | 113 | return '%d.%d.%d%s' % (major, minor, micro, extra) |
|
110 | 114 | |
|
115 | ||
|
111 | 116 | def main(argv): |
|
112 | 117 | opts = ap.parse_args(argv[1:]) |
|
113 | 118 | if opts.selftest: |
|
114 | 119 | import doctest |
|
120 | ||
|
115 | 121 | doctest.testmod() |
|
116 | 122 | return |
|
117 | 123 | with open(opts.versionfile) as f: |
@@ -125,5 +131,6 b' def main(argv):' | |||
|
125 | 131 | else: |
|
126 | 132 | print(ver) |
|
127 | 133 | |
|
134 | ||
|
128 | 135 | if __name__ == '__main__': |
|
129 | 136 | main(sys.argv) |
@@ -16,17 +16,22 b' if sys.version_info[0] >= 3:' | |||
|
16 | 16 | stdout = sys.stdout.buffer |
|
17 | 17 | stderr = sys.stderr.buffer |
|
18 | 18 | stringio = io.BytesIO |
|
19 | ||
|
19 | 20 | def bprint(*args): |
|
20 | 21 | # remove b'' as well for ease of test migration |
|
21 | 22 | pargs = [re.sub(br'''\bb(['"])''', br'\1', b'%s' % a) for a in args] |
|
22 | 23 | stdout.write(b' '.join(pargs) + b'\n') |
|
24 | ||
|
25 | ||
|
23 | 26 | else: |
|
24 | 27 | import cStringIO |
|
28 | ||
|
25 | 29 | stdout = sys.stdout |
|
26 | 30 | stderr = sys.stderr |
|
27 | 31 | stringio = cStringIO.StringIO |
|
28 | 32 | bprint = print |
|
29 | 33 | |
|
34 | ||
|
30 | 35 | def connectpipe(path=None, extraargs=()): |
|
31 | 36 | cmdline = [b'hg', b'serve', b'--cmdserver', b'pipe'] |
|
32 | 37 | if path: |
@@ -38,11 +43,13 b' def connectpipe(path=None, extraargs=())' | |||
|
38 | 43 | return cmdline |
|
39 | 44 | return [arg.decode("utf-8") for arg in cmdline] |
|
40 | 45 | |
|
41 | server = subprocess.Popen(tonative(cmdline), stdin=subprocess.PIPE, | |
|
42 | stdout=subprocess.PIPE) | |
|
46 | server = subprocess.Popen( | |
|
47 | tonative(cmdline), stdin=subprocess.PIPE, stdout=subprocess.PIPE | |
|
48 | ) | |
|
43 | 49 | |
|
44 | 50 | return server |
|
45 | 51 | |
|
52 | ||
|
46 | 53 | class unixconnection(object): |
|
47 | 54 | def __init__(self, sockpath): |
|
48 | 55 | self.sock = sock = socket.socket(socket.AF_UNIX) |
@@ -55,6 +62,7 b' class unixconnection(object):' | |||
|
55 | 62 | self.stdout.close() |
|
56 | 63 | self.sock.close() |
|
57 | 64 | |
|
65 | ||
|
58 | 66 | class unixserver(object): |
|
59 | 67 | def __init__(self, sockpath, logpath=None, repopath=None): |
|
60 | 68 | self.sockpath = sockpath |
@@ -80,11 +88,13 b' class unixserver(object):' | |||
|
80 | 88 | os.kill(self.server.pid, signal.SIGTERM) |
|
81 | 89 | self.server.wait() |
|
82 | 90 | |
|
91 | ||
|
83 | 92 | def writeblock(server, data): |
|
84 | 93 | server.stdin.write(struct.pack(b'>I', len(data))) |
|
85 | 94 | server.stdin.write(data) |
|
86 | 95 | server.stdin.flush() |
|
87 | 96 | |
|
97 | ||
|
88 | 98 | def readchannel(server): |
|
89 | 99 | data = server.stdout.read(5) |
|
90 | 100 | if not data: |
@@ -95,11 +105,14 b' def readchannel(server):' | |||
|
95 | 105 | else: |
|
96 | 106 | return channel, server.stdout.read(length) |
|
97 | 107 | |
|
108 | ||
|
98 | 109 | def sep(text): |
|
99 | 110 | return text.replace(b'\\', b'/') |
|
100 | 111 | |
|
101 | def runcommand(server, args, output=stdout, error=stderr, input=None, | |
|
102 | outfilter=lambda x: x): | |
|
112 | ||
|
113 | def runcommand( | |
|
114 | server, args, output=stdout, error=stderr, input=None, outfilter=lambda x: x | |
|
115 | ): | |
|
103 | 116 | bprint(b'*** runcommand', b' '.join(args)) |
|
104 | 117 | stdout.flush() |
|
105 | 118 | server.stdin.write(b'runcommand\n') |
@@ -123,7 +136,7 b' def runcommand(server, args, output=stdo' | |||
|
123 | 136 | elif ch == b'm': |
|
124 | 137 | bprint(b"message: %r" % data) |
|
125 | 138 | elif ch == b'r': |
|
126 | ret, = struct.unpack('>i', data) | |
|
139 | (ret,) = struct.unpack('>i', data) | |
|
127 | 140 | if ret != 0: |
|
128 | 141 | bprint(b' [%d]' % ret) |
|
129 | 142 | return ret |
@@ -132,6 +145,7 b' def runcommand(server, args, output=stdo' | |||
|
132 | 145 | if ch.isupper(): |
|
133 | 146 | return |
|
134 | 147 | |
|
148 | ||
|
135 | 149 | def check(func, connect=connectpipe): |
|
136 | 150 | stdout.flush() |
|
137 | 151 | server = connect() |
@@ -141,7 +155,9 b' def check(func, connect=connectpipe):' | |||
|
141 | 155 | server.stdin.close() |
|
142 | 156 | server.wait() |
|
143 | 157 | |
|
158 | ||
|
144 | 159 | def checkwith(connect=connectpipe, **kwargs): |
|
145 | 160 | def wrap(func): |
|
146 | 161 | return check(func, lambda: connect(**kwargs)) |
|
162 | ||
|
147 | 163 | return wrap |
@@ -13,6 +13,7 b' prints it to ``stderr`` on exit.' | |||
|
13 | 13 | |
|
14 | 14 | from __future__ import absolute_import |
|
15 | 15 | |
|
16 | ||
|
16 | 17 | def memusage(ui): |
|
17 | 18 | """Report memory usage of the current process.""" |
|
18 | 19 | result = {'peak': 0, 'rss': 0} |
@@ -24,8 +25,13 b' def memusage(ui):' | |||
|
24 | 25 | key = parts[0][2:-1].lower() |
|
25 | 26 | if key in result: |
|
26 | 27 | result[key] = int(parts[1]) |
|
27 | ui.write_err(", ".join(["%s: %.1f MiB" % (k, v / 1024.0) | |
|
28 | for k, v in result.iteritems()]) + "\n") | |
|
28 | ui.write_err( | |
|
29 | ", ".join( | |
|
30 | ["%s: %.1f MiB" % (k, v / 1024.0) for k, v in result.iteritems()] | |
|
31 | ) | |
|
32 | + "\n" | |
|
33 | ) | |
|
34 | ||
|
29 | 35 | |
|
30 | 36 | def extsetup(ui): |
|
31 | 37 | ui.atexit(memusage, ui) |
@@ -98,7 +98,10 b' def secure_download_stream(url, size, sh' | |||
|
98 | 98 | length = 0 |
|
99 | 99 | |
|
100 | 100 | with urllib.request.urlopen(url) as fh: |
|
101 | if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip': | |
|
101 | if ( | |
|
102 | not url.endswith('.gz') | |
|
103 | and fh.info().get('Content-Encoding') == 'gzip' | |
|
104 | ): | |
|
102 | 105 | fh = gzip.GzipFile(fileobj=fh) |
|
103 | 106 | |
|
104 | 107 | while True: |
@@ -114,12 +117,14 b' def secure_download_stream(url, size, sh' | |||
|
114 | 117 | digest = h.hexdigest() |
|
115 | 118 | |
|
116 | 119 | if length != size: |
|
117 | raise IntegrityError('size mismatch on %s: wanted %d; got %d' % ( | |
|
118 | url, size, length)) | |
|
120 | raise IntegrityError( | |
|
121 | 'size mismatch on %s: wanted %d; got %d' % (url, size, length) | |
|
122 | ) | |
|
119 | 123 | |
|
120 | 124 | if digest != sha256: |
|
121 | raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % ( | |
|
122 | url, sha256, digest)) | |
|
125 | raise IntegrityError( | |
|
126 | 'sha256 mismatch on %s: wanted %s; got %s' % (url, sha256, digest) | |
|
127 | ) | |
|
123 | 128 | |
|
124 | 129 | |
|
125 | 130 | def download_to_path(url: str, path: pathlib.Path, size: int, sha256: str): |
@@ -162,7 +167,9 b' def download_to_path(url: str, path: pat' | |||
|
162 | 167 | print('successfully downloaded %s' % url) |
|
163 | 168 | |
|
164 | 169 | |
|
165 | def download_entry(name: dict, dest_path: pathlib.Path, local_name=None) -> pathlib.Path: | |
|
170 | def download_entry( | |
|
171 | name: dict, dest_path: pathlib.Path, local_name=None | |
|
172 | ) -> pathlib.Path: | |
|
166 | 173 | entry = DOWNLOADS[name] |
|
167 | 174 | |
|
168 | 175 | url = entry['url'] |
@@ -12,12 +12,8 b' import pathlib' | |||
|
12 | 12 | import shutil |
|
13 | 13 | import subprocess |
|
14 | 14 | |
|
15 |
from .py2exe import |
|
|
16 | build_py2exe, | |
|
17 | ) | |
|
18 | from .util import ( | |
|
19 | find_vc_runtime_files, | |
|
20 | ) | |
|
15 | from .py2exe import build_py2exe | |
|
16 | from .util import find_vc_runtime_files | |
|
21 | 17 | |
|
22 | 18 | |
|
23 | 19 | EXTRA_PACKAGES = { |
@@ -28,9 +24,13 b' EXTRA_PACKAGES = {' | |||
|
28 | 24 | } |
|
29 | 25 | |
|
30 | 26 | |
|
31 | def build(source_dir: pathlib.Path, build_dir: pathlib.Path, | |
|
32 | python_exe: pathlib.Path, iscc_exe: pathlib.Path, | |
|
33 | version=None): | |
|
27 | def build( | |
|
28 | source_dir: pathlib.Path, | |
|
29 | build_dir: pathlib.Path, | |
|
30 | python_exe: pathlib.Path, | |
|
31 | iscc_exe: pathlib.Path, | |
|
32 | version=None, | |
|
33 | ): | |
|
34 | 34 | """Build the Inno installer. |
|
35 | 35 | |
|
36 | 36 | Build files will be placed in ``build_dir``. |
@@ -44,11 +44,18 b' def build(source_dir: pathlib.Path, buil' | |||
|
44 | 44 | |
|
45 | 45 | vc_x64 = r'\x64' in os.environ.get('LIB', '') |
|
46 | 46 | |
|
47 | requirements_txt = (source_dir / 'contrib' / 'packaging' / | |
|
48 |
|
|
|
47 | requirements_txt = ( | |
|
48 | source_dir / 'contrib' / 'packaging' / 'inno' / 'requirements.txt' | |
|
49 | ) | |
|
49 | 50 | |
|
50 | build_py2exe(source_dir, build_dir, python_exe, 'inno', | |
|
51 | requirements_txt, extra_packages=EXTRA_PACKAGES) | |
|
51 | build_py2exe( | |
|
52 | source_dir, | |
|
53 | build_dir, | |
|
54 | python_exe, | |
|
55 | 'inno', | |
|
56 | requirements_txt, | |
|
57 | extra_packages=EXTRA_PACKAGES, | |
|
58 | ) | |
|
52 | 59 | |
|
53 | 60 | # hg.exe depends on VC9 runtime DLLs. Copy those into place. |
|
54 | 61 | for f in find_vc_runtime_files(vc_x64): |
@@ -11,9 +11,7 b' import os' | |||
|
11 | 11 | import pathlib |
|
12 | 12 | import subprocess |
|
13 | 13 | |
|
14 |
from .downloads import |
|
|
15 | download_entry, | |
|
16 | ) | |
|
14 | from .downloads import download_entry | |
|
17 | 15 | from .util import ( |
|
18 | 16 | extract_tar_to_directory, |
|
19 | 17 | extract_zip_to_directory, |
@@ -21,12 +19,17 b' from .util import (' | |||
|
21 | 19 | ) |
|
22 | 20 | |
|
23 | 21 | |
|
24 | def build_py2exe(source_dir: pathlib.Path, build_dir: pathlib.Path, | |
|
25 | python_exe: pathlib.Path, build_name: str, | |
|
22 | def build_py2exe( | |
|
23 | source_dir: pathlib.Path, | |
|
24 | build_dir: pathlib.Path, | |
|
25 | python_exe: pathlib.Path, | |
|
26 | build_name: str, | |
|
26 | 27 |
|
|
27 | extra_packages=None, extra_excludes=None, | |
|
28 | extra_packages=None, | |
|
29 | extra_excludes=None, | |
|
28 | 30 |
|
|
29 |
|
|
|
31 | extra_packages_script=None, | |
|
32 | ): | |
|
30 | 33 | """Build Mercurial with py2exe. |
|
31 | 34 | |
|
32 | 35 | Build files will be placed in ``build_dir``. |
@@ -36,9 +39,11 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
36 | 39 | to already be configured with an active toolchain. |
|
37 | 40 | """ |
|
38 | 41 | if 'VCINSTALLDIR' not in os.environ: |
|
39 | raise Exception('not running from a Visual C++ build environment; ' | |
|
42 | raise Exception( | |
|
43 | 'not running from a Visual C++ build environment; ' | |
|
40 | 44 |
|
|
41 |
|
|
|
45 | 'application shortcut or a vcsvarsall.bat file' | |
|
46 | ) | |
|
42 | 47 | |
|
43 | 48 | # Identity x86/x64 and validate the environment matches the Python |
|
44 | 49 | # architecture. |
@@ -48,12 +53,16 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
48 | 53 | |
|
49 | 54 | if vc_x64: |
|
50 | 55 | if py_info['arch'] != '64bit': |
|
51 | raise Exception('architecture mismatch: Visual C++ environment ' | |
|
52 | 'is configured for 64-bit but Python is 32-bit') | |
|
56 | raise Exception( | |
|
57 | 'architecture mismatch: Visual C++ environment ' | |
|
58 | 'is configured for 64-bit but Python is 32-bit' | |
|
59 | ) | |
|
53 | 60 | else: |
|
54 | 61 | if py_info['arch'] != '32bit': |
|
55 | raise Exception('architecture mismatch: Visual C++ environment ' | |
|
56 | 'is configured for 32-bit but Python is 64-bit') | |
|
62 | raise Exception( | |
|
63 | 'architecture mismatch: Visual C++ environment ' | |
|
64 | 'is configured for 32-bit but Python is 64-bit' | |
|
65 | ) | |
|
57 | 66 | |
|
58 | 67 | if py_info['py3']: |
|
59 | 68 | raise Exception('Only Python 2 is currently supported') |
@@ -65,11 +74,11 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
65 | 74 | virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir) |
|
66 | 75 | py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir) |
|
67 | 76 | |
|
68 |
venv_path = build_dir / ( |
|
|
69 | 'x64' if vc_x64 else 'x86')) | |
|
77 | venv_path = build_dir / ( | |
|
78 | 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86') | |
|
79 | ) | |
|
70 | 80 | |
|
71 | gettext_root = build_dir / ( | |
|
72 | 'gettext-win-%s' % gettext_entry['version']) | |
|
81 | gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version']) | |
|
73 | 82 | |
|
74 | 83 | if not gettext_root.exists(): |
|
75 | 84 | extract_zip_to_directory(gettext_pkg, gettext_root) |
@@ -77,7 +86,8 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
77 | 86 | |
|
78 | 87 | # This assumes Python 2. We don't need virtualenv on Python 3. |
|
79 | 88 | virtualenv_src_path = build_dir / ( |
|
80 |
'virtualenv-%s' % virtualenv_entry['version'] |
|
|
89 | 'virtualenv-%s' % virtualenv_entry['version'] | |
|
90 | ) | |
|
81 | 91 | virtualenv_py = virtualenv_src_path / 'virtualenv.py' |
|
82 | 92 | |
|
83 | 93 | if not virtualenv_src_path.exists(): |
@@ -91,14 +101,15 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
91 | 101 | if not venv_path.exists(): |
|
92 | 102 | print('creating virtualenv with dependencies') |
|
93 | 103 | subprocess.run( |
|
94 | [str(python_exe), str(virtualenv_py), str(venv_path)], | |
|
95 | check=True) | |
|
104 | [str(python_exe), str(virtualenv_py), str(venv_path)], check=True | |
|
105 | ) | |
|
96 | 106 | |
|
97 | 107 | venv_python = venv_path / 'Scripts' / 'python.exe' |
|
98 | 108 | venv_pip = venv_path / 'Scripts' / 'pip.exe' |
|
99 | 109 | |
|
100 | subprocess.run([str(venv_pip), 'install', '-r', str(venv_requirements_txt)], | |
|
101 | check=True) | |
|
110 | subprocess.run( | |
|
111 | [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True | |
|
112 | ) | |
|
102 | 113 | |
|
103 | 114 | # Force distutils to use VC++ settings from environment, which was |
|
104 | 115 | # validated above. |
@@ -107,9 +118,13 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
107 | 118 | env['MSSdk'] = '1' |
|
108 | 119 | |
|
109 | 120 | if extra_packages_script: |
|
110 |
more_packages = set( |
|
|
111 | extra_packages_script, | |
|
112 | cwd=build_dir).split(b'\0')[-1].strip().decode('utf-8').splitlines()) | |
|
121 | more_packages = set( | |
|
122 | subprocess.check_output(extra_packages_script, cwd=build_dir) | |
|
123 | .split(b'\0')[-1] | |
|
124 | .strip() | |
|
125 | .decode('utf-8') | |
|
126 | .splitlines() | |
|
127 | ) | |
|
113 | 128 | if more_packages: |
|
114 | 129 | if not extra_packages: |
|
115 | 130 | extra_packages = more_packages |
@@ -119,32 +134,38 b' def build_py2exe(source_dir: pathlib.Pat' | |||
|
119 | 134 | if extra_packages: |
|
120 | 135 | env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages)) |
|
121 | 136 | hgext3rd_extras = sorted( |
|
122 |
e for e in extra_packages if e.startswith('hgext3rd.') |
|
|
137 | e for e in extra_packages if e.startswith('hgext3rd.') | |
|
138 | ) | |
|
123 | 139 | if hgext3rd_extras: |
|
124 | 140 | env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras) |
|
125 | 141 | if extra_excludes: |
|
126 | 142 | env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes)) |
|
127 | 143 | if extra_dll_excludes: |
|
128 | 144 | env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join( |
|
129 |
sorted(extra_dll_excludes) |
|
|
145 | sorted(extra_dll_excludes) | |
|
146 | ) | |
|
130 | 147 | |
|
131 | 148 | py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe' |
|
132 | 149 | if not py2exe_py_path.exists(): |
|
133 | 150 | print('building py2exe') |
|
134 | subprocess.run([str(venv_python), 'setup.py', 'install'], | |
|
151 | subprocess.run( | |
|
152 | [str(venv_python), 'setup.py', 'install'], | |
|
135 | 153 |
|
|
136 | 154 |
|
|
137 |
|
|
|
155 | check=True, | |
|
156 | ) | |
|
138 | 157 | |
|
139 | 158 | # Register location of msgfmt and other binaries. |
|
140 | 159 | env['PATH'] = '%s%s%s' % ( |
|
141 | env['PATH'], os.pathsep, str(gettext_root / 'bin')) | |
|
160 | env['PATH'], | |
|
161 | os.pathsep, | |
|
162 | str(gettext_root / 'bin'), | |
|
163 | ) | |
|
142 | 164 | |
|
143 | 165 | print('building Mercurial') |
|
144 | 166 | subprocess.run( |
|
145 | [str(venv_python), 'setup.py', | |
|
146 | 'py2exe', | |
|
147 | 'build_doc', '--html'], | |
|
167 | [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'], | |
|
148 | 168 | cwd=str(source_dir), |
|
149 | 169 | env=env, |
|
150 |
check=True |
|
|
170 | check=True, | |
|
171 | ) |
@@ -32,8 +32,11 b' def find_vc_runtime_files(x64=False):' | |||
|
32 | 32 | |
|
33 | 33 | prefix = 'amd64' if x64 else 'x86' |
|
34 | 34 | |
|
35 |
candidates = sorted( |
|
|
36 | if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)) | |
|
35 | candidates = sorted( | |
|
36 | p | |
|
37 | for p in os.listdir(winsxs) | |
|
38 | if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix) | |
|
39 | ) | |
|
37 | 40 | |
|
38 | 41 | for p in candidates: |
|
39 | 42 | print('found candidate VC runtime: %s' % p) |
@@ -72,7 +75,7 b' def windows_10_sdk_info():' | |||
|
72 | 75 | 'version': version, |
|
73 | 76 | 'bin_root': bin_version, |
|
74 | 77 | 'bin_x86': bin_version / 'x86', |
|
75 | 'bin_x64': bin_version / 'x64' | |
|
78 | 'bin_x64': bin_version / 'x64', | |
|
76 | 79 | } |
|
77 | 80 | |
|
78 | 81 | |
@@ -89,9 +92,14 b' def find_signtool():' | |||
|
89 | 92 | raise Exception('could not find signtool.exe in Windows 10 SDK') |
|
90 | 93 | |
|
91 | 94 | |
|
92 | def sign_with_signtool(file_path, description, subject_name=None, | |
|
93 | cert_path=None, cert_password=None, | |
|
94 | timestamp_url=None): | |
|
95 | def sign_with_signtool( | |
|
96 | file_path, | |
|
97 | description, | |
|
98 | subject_name=None, | |
|
99 | cert_path=None, | |
|
100 | cert_password=None, | |
|
101 | timestamp_url=None, | |
|
102 | ): | |
|
95 | 103 | """Digitally sign a file with signtool.exe. |
|
96 | 104 | |
|
97 | 105 | ``file_path`` is file to sign. |
@@ -114,10 +122,13 b' def sign_with_signtool(file_path, descri' | |||
|
114 | 122 | cert_password = getpass.getpass('password for %s: ' % cert_path) |
|
115 | 123 | |
|
116 | 124 | args = [ |
|
117 |
str(find_signtool()), |
|
|
125 | str(find_signtool()), | |
|
126 | 'sign', | |
|
118 | 127 | '/v', |
|
119 |
'/fd', |
|
|
120 | '/d', description, | |
|
128 | '/fd', | |
|
129 | 'sha256', | |
|
130 | '/d', | |
|
131 | description, | |
|
121 | 132 | ] |
|
122 | 133 | |
|
123 | 134 | if cert_path: |
@@ -15,12 +15,8 b' import tempfile' | |||
|
15 | 15 | import typing |
|
16 | 16 | import xml.dom.minidom |
|
17 | 17 | |
|
18 |
from .downloads import |
|
|
19 | download_entry, | |
|
20 | ) | |
|
21 | from .py2exe import ( | |
|
22 | build_py2exe, | |
|
23 | ) | |
|
18 | from .downloads import download_entry | |
|
19 | from .py2exe import build_py2exe | |
|
24 | 20 | from .util import ( |
|
25 | 21 | extract_zip_to_directory, |
|
26 | 22 | sign_with_signtool, |
@@ -84,17 +80,29 b' def normalize_version(version):' | |||
|
84 | 80 | |
|
85 | 81 | def ensure_vc90_merge_modules(build_dir): |
|
86 | 82 | x86 = ( |
|
87 |
download_entry( |
|
|
88 | local_name='microsoft.vcxx.crt.x86_msm.msm')[0], | |
|
89 | download_entry('vc9-crt-x86-msm-policy', build_dir, | |
|
90 |
|
|
|
83 | download_entry( | |
|
84 | 'vc9-crt-x86-msm', | |
|
85 | build_dir, | |
|
86 | local_name='microsoft.vcxx.crt.x86_msm.msm', | |
|
87 | )[0], | |
|
88 | download_entry( | |
|
89 | 'vc9-crt-x86-msm-policy', | |
|
90 | build_dir, | |
|
91 | local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm', | |
|
92 | )[0], | |
|
91 | 93 | ) |
|
92 | 94 | |
|
93 | 95 | x64 = ( |
|
94 |
download_entry( |
|
|
95 | local_name='microsoft.vcxx.crt.x64_msm.msm')[0], | |
|
96 | download_entry('vc9-crt-x64-msm-policy', build_dir, | |
|
97 |
|
|
|
96 | download_entry( | |
|
97 | 'vc9-crt-x64-msm', | |
|
98 | build_dir, | |
|
99 | local_name='microsoft.vcxx.crt.x64_msm.msm', | |
|
100 | )[0], | |
|
101 | download_entry( | |
|
102 | 'vc9-crt-x64-msm-policy', | |
|
103 | build_dir, | |
|
104 | local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm', | |
|
105 | )[0], | |
|
98 | 106 | ) |
|
99 | 107 | return { |
|
100 | 108 | 'x86': x86, |
@@ -116,17 +124,26 b' def run_candle(wix, cwd, wxs, source_dir' | |||
|
116 | 124 | subprocess.run(args, cwd=str(cwd), check=True) |
|
117 | 125 | |
|
118 | 126 | |
|
119 |
def make_post_build_signing_fn( |
|
|
120 | cert_password=None, timestamp_url=None): | |
|
127 | def make_post_build_signing_fn( | |
|
128 | name, | |
|
129 | subject_name=None, | |
|
130 | cert_path=None, | |
|
131 | cert_password=None, | |
|
132 | timestamp_url=None, | |
|
133 | ): | |
|
121 | 134 | """Create a callable that will use signtool to sign hg.exe.""" |
|
122 | 135 | |
|
123 | 136 | def post_build_sign(source_dir, build_dir, dist_dir, version): |
|
124 | 137 | description = '%s %s' % (name, version) |
|
125 | 138 | |
|
126 |
sign_with_signtool( |
|
|
127 | subject_name=subject_name, cert_path=cert_path, | |
|
139 | sign_with_signtool( | |
|
140 | dist_dir / 'hg.exe', | |
|
141 | description, | |
|
142 | subject_name=subject_name, | |
|
143 | cert_path=cert_path, | |
|
128 | 144 |
|
|
129 |
|
|
|
145 | timestamp_url=timestamp_url, | |
|
146 | ) | |
|
130 | 147 | |
|
131 | 148 | return post_build_sign |
|
132 | 149 | |
@@ -155,7 +172,8 b' def make_libraries_xml(wix_dir: pathlib.' | |||
|
155 | 172 | # We can't use ElementTree because it doesn't handle the |
|
156 | 173 | # <?include ?> directives. |
|
157 | 174 | doc = xml.dom.minidom.parseString( |
|
158 |
LIBRARIES_XML.format(wix_dir=str(wix_dir)) |
|
|
175 | LIBRARIES_XML.format(wix_dir=str(wix_dir)) | |
|
176 | ) | |
|
159 | 177 | |
|
160 | 178 | component = doc.getElementsByTagName('Component')[0] |
|
161 | 179 | |
@@ -177,11 +195,16 b' def make_libraries_xml(wix_dir: pathlib.' | |||
|
177 | 195 | return doc.toprettyxml() |
|
178 | 196 | |
|
179 | 197 | |
|
180 | def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | |
|
181 | msi_name='mercurial', version=None, post_build_fn=None, | |
|
198 | def build_installer( | |
|
199 | source_dir: pathlib.Path, | |
|
200 | python_exe: pathlib.Path, | |
|
201 | msi_name='mercurial', | |
|
202 | version=None, | |
|
203 | post_build_fn=None, | |
|
182 | 204 |
|
|
183 | 205 |
|
|
184 |
|
|
|
206 | extra_features: typing.Optional[typing.List[str]] = None, | |
|
207 | ): | |
|
185 | 208 | """Build a WiX MSI installer. |
|
186 | 209 | |
|
187 | 210 | ``source_dir`` is the path to the Mercurial source tree to use. |
@@ -209,10 +232,15 b' def build_installer(source_dir: pathlib.' | |||
|
209 | 232 | |
|
210 | 233 | requirements_txt = wix_dir / 'requirements.txt' |
|
211 | 234 | |
|
212 | build_py2exe(source_dir, hg_build_dir, | |
|
213 | python_exe, 'wix', requirements_txt, | |
|
235 | build_py2exe( | |
|
236 | source_dir, | |
|
237 | hg_build_dir, | |
|
238 | python_exe, | |
|
239 | 'wix', | |
|
240 | requirements_txt, | |
|
214 | 241 |
|
|
215 |
|
|
|
242 | extra_packages_script=extra_packages_script, | |
|
243 | ) | |
|
216 | 244 | |
|
217 | 245 | version = version or normalize_version(find_version(source_dir)) |
|
218 | 246 | print('using version string: %s' % version) |
@@ -265,16 +293,19 b' def build_installer(source_dir: pathlib.' | |||
|
265 | 293 | |
|
266 | 294 | run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) |
|
267 | 295 | |
|
268 | msi_path = source_dir / 'dist' / ( | |
|
269 | '%s-%s-%s.msi' % (msi_name, version, arch)) | |
|
296 | msi_path = ( | |
|
297 | source_dir / 'dist' / ('%s-%s-%s.msi' % (msi_name, version, arch)) | |
|
298 | ) | |
|
270 | 299 | |
|
271 | 300 | args = [ |
|
272 | 301 | str(wix_path / 'light.exe'), |
|
273 | 302 | '-nologo', |
|
274 |
'-ext', |
|
|
303 | '-ext', | |
|
304 | 'WixUIExtension', | |
|
275 | 305 | '-sw1076', |
|
276 | 306 | '-spdb', |
|
277 |
'-o', |
|
|
307 | '-o', | |
|
308 | str(msi_path), | |
|
278 | 309 | ] |
|
279 | 310 | |
|
280 | 311 | for source, rel_path in SUPPORT_WXS: |
@@ -286,10 +317,12 b' def build_installer(source_dir: pathlib.' | |||
|
286 | 317 | source = os.path.basename(source) |
|
287 | 318 | args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) |
|
288 | 319 | |
|
289 |
args.extend( |
|
|
320 | args.extend( | |
|
321 | [ | |
|
290 | 322 | str(build_dir / 'library.wixobj'), |
|
291 | 323 | str(build_dir / 'mercurial.wixobj'), |
|
292 |
] |
|
|
324 | ] | |
|
325 | ) | |
|
293 | 326 | |
|
294 | 327 | subprocess.run(args, cwd=str(source_dir), check=True) |
|
295 | 328 | |
@@ -300,11 +333,19 b' def build_installer(source_dir: pathlib.' | |||
|
300 | 333 | } |
|
301 | 334 | |
|
302 | 335 | |
|
303 | def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | |
|
304 | name: str, version=None, subject_name=None, | |
|
305 | cert_path=None, cert_password=None, | |
|
306 | timestamp_url=None, extra_packages_script=None, | |
|
307 | extra_wxs=None, extra_features=None): | |
|
336 | def build_signed_installer( | |
|
337 | source_dir: pathlib.Path, | |
|
338 | python_exe: pathlib.Path, | |
|
339 | name: str, | |
|
340 | version=None, | |
|
341 | subject_name=None, | |
|
342 | cert_path=None, | |
|
343 | cert_password=None, | |
|
344 | timestamp_url=None, | |
|
345 | extra_packages_script=None, | |
|
346 | extra_wxs=None, | |
|
347 | extra_features=None, | |
|
348 | ): | |
|
308 | 349 | """Build an installer with signed executables.""" |
|
309 | 350 | |
|
310 | 351 | post_build_fn = make_post_build_signing_fn( |
@@ -312,16 +353,27 b' def build_signed_installer(source_dir: p' | |||
|
312 | 353 | subject_name=subject_name, |
|
313 | 354 | cert_path=cert_path, |
|
314 | 355 | cert_password=cert_password, |
|
315 |
timestamp_url=timestamp_url |
|
|
356 | timestamp_url=timestamp_url, | |
|
357 | ) | |
|
316 | 358 | |
|
317 |
info = build_installer( |
|
|
318 | msi_name=name.lower(), version=version, | |
|
359 | info = build_installer( | |
|
360 | source_dir, | |
|
361 | python_exe=python_exe, | |
|
362 | msi_name=name.lower(), | |
|
363 | version=version, | |
|
319 | 364 |
|
|
320 | 365 |
|
|
321 | extra_wxs=extra_wxs, extra_features=extra_features) | |
|
366 | extra_wxs=extra_wxs, | |
|
367 | extra_features=extra_features, | |
|
368 | ) | |
|
322 | 369 | |
|
323 | 370 | description = '%s %s' % (name, version) |
|
324 | 371 | |
|
325 |
sign_with_signtool( |
|
|
326 | subject_name=subject_name, cert_path=cert_path, | |
|
327 | cert_password=cert_password, timestamp_url=timestamp_url) | |
|
372 | sign_with_signtool( | |
|
373 | info['msi_path'], | |
|
374 | description, | |
|
375 | subject_name=subject_name, | |
|
376 | cert_path=cert_path, | |
|
377 | cert_password=cert_password, | |
|
378 | timestamp_url=timestamp_url, | |
|
379 | ) |
@@ -19,14 +19,15 b' import sys' | |||
|
19 | 19 | if __name__ == '__main__': |
|
20 | 20 | parser = argparse.ArgumentParser() |
|
21 | 21 | |
|
22 |
parser.add_argument( |
|
|
23 | required=True, | |
|
24 | help='path to python.exe to use') | |
|
25 | parser.add_argument('--iscc', | |
|
26 | help='path to iscc.exe to use') | |
|
27 | parser.add_argument('--version', | |
|
22 | parser.add_argument( | |
|
23 | '--python', required=True, help='path to python.exe to use' | |
|
24 | ) | |
|
25 | parser.add_argument('--iscc', help='path to iscc.exe to use') | |
|
26 | parser.add_argument( | |
|
27 | '--version', | |
|
28 | 28 |
|
|
29 |
|
|
|
29 | '(detected from __version__.py if not defined', | |
|
30 | ) | |
|
30 | 31 | |
|
31 | 32 | args = parser.parse_args() |
|
32 | 33 | |
@@ -36,8 +37,11 b" if __name__ == '__main__':" | |||
|
36 | 37 | if args.iscc: |
|
37 | 38 | iscc = pathlib.Path(args.iscc) |
|
38 | 39 | else: |
|
39 | iscc = (pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Inno Setup 5' / | |
|
40 | 'ISCC.exe') | |
|
40 | iscc = ( | |
|
41 | pathlib.Path(os.environ['ProgramFiles(x86)']) | |
|
42 | / 'Inno Setup 5' | |
|
43 | / 'ISCC.exe' | |
|
44 | ) | |
|
41 | 45 | |
|
42 | 46 | here = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) |
|
43 | 47 | source_dir = here.parent.parent.parent |
@@ -47,5 +51,10 b" if __name__ == '__main__':" | |||
|
47 | 51 | |
|
48 | 52 | from hgpackaging.inno import build |
|
49 | 53 | |
|
50 | build(source_dir, build_dir, pathlib.Path(args.python), iscc, | |
|
51 | version=args.version) | |
|
54 | build( | |
|
55 | source_dir, | |
|
56 | build_dir, | |
|
57 | pathlib.Path(args.python), | |
|
58 | iscc, | |
|
59 | version=args.version, | |
|
60 | ) |
@@ -17,31 +17,42 b' import sys' | |||
|
17 | 17 | if __name__ == '__main__': |
|
18 | 18 | parser = argparse.ArgumentParser() |
|
19 | 19 | |
|
20 | parser.add_argument('--name', | |
|
21 | help='Application name', | |
|
22 | default='Mercurial') | |
|
23 | parser.add_argument('--python', | |
|
24 | help='Path to Python executable to use', | |
|
25 | required=True) | |
|
26 | parser.add_argument('--sign-sn', | |
|
20 | parser.add_argument('--name', help='Application name', default='Mercurial') | |
|
21 | parser.add_argument( | |
|
22 | '--python', help='Path to Python executable to use', required=True | |
|
23 | ) | |
|
24 | parser.add_argument( | |
|
25 | '--sign-sn', | |
|
27 | 26 |
|
|
28 |
|
|
|
29 | parser.add_argument('--sign-cert', | |
|
30 | help='Path to certificate to use for signing') | |
|
31 | parser.add_argument('--sign-password', | |
|
32 | help='Password for signing certificate') | |
|
33 |
parser.add_argument( |
|
|
34 | help='URL of timestamp server to use for signing') | |
|
35 | parser.add_argument('--version', | |
|
36 | help='Version string to use') | |
|
37 | parser.add_argument('--extra-packages-script', | |
|
38 | help=('Script to execute to include extra packages in ' | |
|
39 | 'py2exe binary.')) | |
|
40 |
parser.add_argument('--e |
|
|
41 | help='CSV of path_to_wxs_file=working_dir_for_wxs_file') | |
|
42 | parser.add_argument('--extra-features', | |
|
43 | help=('CSV of extra feature names to include ' | |
|
44 | 'in the installer from the extra wxs files')) | |
|
27 | 'to use for signing', | |
|
28 | ) | |
|
29 | parser.add_argument( | |
|
30 | '--sign-cert', help='Path to certificate to use for signing' | |
|
31 | ) | |
|
32 | parser.add_argument( | |
|
33 | '--sign-password', help='Password for signing certificate' | |
|
34 | ) | |
|
35 | parser.add_argument( | |
|
36 | '--sign-timestamp-url', | |
|
37 | help='URL of timestamp server to use for signing', | |
|
38 | ) | |
|
39 | parser.add_argument('--version', help='Version string to use') | |
|
40 | parser.add_argument( | |
|
41 | '--extra-packages-script', | |
|
42 | help=( | |
|
43 | 'Script to execute to include extra packages in ' 'py2exe binary.' | |
|
44 | ), | |
|
45 | ) | |
|
46 | parser.add_argument( | |
|
47 | '--extra-wxs', help='CSV of path_to_wxs_file=working_dir_for_wxs_file' | |
|
48 | ) | |
|
49 | parser.add_argument( | |
|
50 | '--extra-features', | |
|
51 | help=( | |
|
52 | 'CSV of extra feature names to include ' | |
|
53 | 'in the installer from the extra wxs files' | |
|
54 | ), | |
|
55 | ) | |
|
45 | 56 | |
|
46 | 57 | args = parser.parse_args() |
|
47 | 58 | |
@@ -69,7 +80,8 b" if __name__ == '__main__':" | |||
|
69 | 80 | kwargs['extra_packages_script'] = args.extra_packages_script |
|
70 | 81 | if args.extra_wxs: |
|
71 | 82 | kwargs['extra_wxs'] = dict( |
|
72 |
thing.split("=") for thing in args.extra_wxs.split(',') |
|
|
83 | thing.split("=") for thing in args.extra_wxs.split(',') | |
|
84 | ) | |
|
73 | 85 | if args.extra_features: |
|
74 | 86 | kwargs['extra_features'] = args.extra_features.split(',') |
|
75 | 87 |
@@ -44,18 +44,12 b' def plot(data, title=None):' | |||
|
44 | 44 | comb_plt = fig.add_subplot(211) |
|
45 | 45 | other_plt = fig.add_subplot(212) |
|
46 | 46 | |
|
47 |
comb_plt.plot( |
|
|
48 | np.cumsum(ary[1]), | |
|
49 | color='red', | |
|
50 | linewidth=1, | |
|
51 | label='comb') | |
|
47 | comb_plt.plot( | |
|
48 | ary[0], np.cumsum(ary[1]), color='red', linewidth=1, label='comb' | |
|
49 | ) | |
|
52 | 50 | |
|
53 | 51 | plots = [] |
|
54 | p = other_plt.plot(ary[0], | |
|
55 | ary[1], | |
|
56 | color='red', | |
|
57 | linewidth=1, | |
|
58 | label='wall') | |
|
52 | p = other_plt.plot(ary[0], ary[1], color='red', linewidth=1, label='wall') | |
|
59 | 53 | plots.append(p) |
|
60 | 54 | |
|
61 | 55 | colors = { |
@@ -64,20 +58,24 b' def plot(data, title=None):' | |||
|
64 | 58 | 1000: ('purple', 'xkcd:dark pink'), |
|
65 | 59 | } |
|
66 | 60 | for n, color in colors.items(): |
|
67 | avg_n = np.convolve(ary[1], np.full(n, 1. / n), 'valid') | |
|
68 |
p = other_plt.plot( |
|
|
61 | avg_n = np.convolve(ary[1], np.full(n, 1.0 / n), 'valid') | |
|
62 | p = other_plt.plot( | |
|
63 | ary[0][n - 1 :], | |
|
69 | 64 |
|
|
70 | 65 |
|
|
71 | 66 |
|
|
72 |
|
|
|
67 | label='avg time last %d' % n, | |
|
68 | ) | |
|
73 | 69 | plots.append(p) |
|
74 | 70 | |
|
75 | 71 | med_n = scipy.signal.medfilt(ary[1], n + 1) |
|
76 |
p = other_plt.plot( |
|
|
72 | p = other_plt.plot( | |
|
73 | ary[0], | |
|
77 | 74 |
|
|
78 | 75 |
|
|
79 | 76 |
|
|
80 |
|
|
|
77 | label='median time last %d' % n, | |
|
78 | ) | |
|
81 | 79 | plots.append(p) |
|
82 | 80 | |
|
83 | 81 | formatter = mticker.ScalarFormatter() |
@@ -108,6 +106,7 b' def plot(data, title=None):' | |||
|
108 | 106 | else: |
|
109 | 107 | legline.set_alpha(0.2) |
|
110 | 108 | fig.canvas.draw() |
|
109 | ||
|
111 | 110 | if title is not None: |
|
112 | 111 | fig.canvas.set_window_title(title) |
|
113 | 112 | fig.canvas.mpl_connect('pick_event', onpick) |
This diff has been collapsed as it changes many lines, (1114 lines changed) Show them Hide them | |||
@@ -93,6 +93,7 b' except ImportError:' | |||
|
93 | 93 | pass |
|
94 | 94 | try: |
|
95 | 95 | from mercurial import registrar # since 3.7 (or 37d50250b696) |
|
96 | ||
|
96 | 97 | dir(registrar) # forcibly load it |
|
97 | 98 | except ImportError: |
|
98 | 99 | registrar = None |
@@ -118,11 +119,14 b' try:' | |||
|
118 | 119 | except ImportError: |
|
119 | 120 | profiling = None |
|
120 | 121 | |
|
122 | ||
|
121 | 123 | def identity(a): |
|
122 | 124 | return a |
|
123 | 125 | |
|
126 | ||
|
124 | 127 | try: |
|
125 | 128 | from mercurial import pycompat |
|
129 | ||
|
126 | 130 | getargspec = pycompat.getargspec # added to module after 4.5 |
|
127 | 131 | _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802) |
|
128 | 132 |
_sysstr = pycompat.sysstr |
@@ -135,6 +139,7 b' try:' | |||
|
135 | 139 | _maxint = sys.maxint |
|
136 | 140 | except (NameError, ImportError, AttributeError): |
|
137 | 141 | import inspect |
|
142 | ||
|
138 | 143 | getargspec = inspect.getargspec |
|
139 | 144 | _byteskwargs = identity |
|
140 | 145 | _bytestr = str |
@@ -155,6 +160,7 b' except (NameError, AttributeError, Impor' | |||
|
155 | 160 | |
|
156 | 161 | try: |
|
157 | 162 | from mercurial import logcmdutil |
|
163 | ||
|
158 | 164 | makelogtemplater = logcmdutil.maketemplater |
|
159 | 165 | except (AttributeError, ImportError): |
|
160 | 166 | try: |
@@ -166,8 +172,12 b' except (AttributeError, ImportError):' | |||
|
166 | 172 | # define util.safehasattr forcibly, because util.safehasattr has been |
|
167 | 173 | # available since 1.9.3 (or 94b200a11cf7) |
|
168 | 174 | _undefined = object() |
|
175 | ||
|
176 | ||
|
169 | 177 | def safehasattr(thing, attr): |
|
170 | 178 | return getattr(thing, _sysstr(attr), _undefined) is not _undefined |
|
179 | ||
|
180 | ||
|
171 | 181 | setattr(util, 'safehasattr', safehasattr) |
|
172 | 182 | |
|
173 | 183 | # for "historical portability": |
@@ -185,20 +195,28 b' else:' | |||
|
185 | 195 | # available, because commands.formatteropts has been available since |
|
186 | 196 | # 3.2 (or 7a7eed5176a4), even though formatting itself has been |
|
187 | 197 | # available since 2.2 (or ae5f92e154d3) |
|
188 |
formatteropts = getattr( |
|
|
189 |
|
|
|
198 | formatteropts = getattr( | |
|
199 | cmdutil, "formatteropts", getattr(commands, "formatteropts", []) | |
|
200 | ) | |
|
190 | 201 | |
|
191 | 202 | # for "historical portability": |
|
192 | 203 | # use locally defined option list, if debugrevlogopts isn't available, |
|
193 | 204 | # because commands.debugrevlogopts has been available since 3.7 (or |
|
194 | 205 | # 5606f7d0d063), even though cmdutil.openrevlog() has been available |
|
195 | 206 | # since 1.9 (or a79fea6b3e77). |
|
196 |
revlogopts = getattr( |
|
|
197 | getattr(commands, "debugrevlogopts", [ | |
|
198 | (b'c', b'changelog', False, (b'open changelog')), | |
|
199 | (b'm', b'manifest', False, (b'open manifest')), | |
|
200 | (b'', b'dir', False, (b'open directory manifest')), | |
|
201 | ])) | |
|
207 | revlogopts = getattr( | |
|
208 | cmdutil, | |
|
209 | "debugrevlogopts", | |
|
210 | getattr( | |
|
211 | commands, | |
|
212 | "debugrevlogopts", | |
|
213 | [ | |
|
214 | (b'c', b'changelog', False, b'open changelog'), | |
|
215 | (b'm', b'manifest', False, b'open manifest'), | |
|
216 | (b'', b'dir', False, b'open directory manifest'), | |
|
217 | ], | |
|
218 | ), | |
|
219 | ) | |
|
202 | 220 | |
|
203 | 221 | cmdtable = {} |
|
204 | 222 | |
@@ -208,6 +226,7 b' cmdtable = {}' | |||
|
208 | 226 | def parsealiases(cmd): |
|
209 | 227 | return cmd.split(b"|") |
|
210 | 228 | |
|
229 | ||
|
211 | 230 | if safehasattr(registrar, 'command'): |
|
212 | 231 | command = registrar.command(cmdtable) |
|
213 | 232 | elif safehasattr(cmdutil, 'command'): |
@@ -217,10 +236,13 b" elif safehasattr(cmdutil, 'command'):" | |||
|
217 | 236 | # wrap original cmdutil.command, because "norepo" option has |
|
218 | 237 | # been available since 3.1 (or 75a96326cecb) |
|
219 | 238 | _command = command |
|
239 | ||
|
220 | 240 | def command(name, options=(), synopsis=None, norepo=False): |
|
221 | 241 | if norepo: |
|
222 | 242 | commands.norepo += b' %s' % b' '.join(parsealiases(name)) |
|
223 | 243 | return _command(name, list(options), synopsis) |
|
244 | ||
|
245 | ||
|
224 | 246 | else: |
|
225 | 247 | # for "historical portability": |
|
226 | 248 | # define "@command" annotation locally, because cmdutil.command |
@@ -234,36 +256,51 b' else:' | |||
|
234 | 256 | if norepo: |
|
235 | 257 | commands.norepo += b' %s' % b' '.join(parsealiases(name)) |
|
236 | 258 | return func |
|
259 | ||
|
237 | 260 | return decorator |
|
238 | 261 | |
|
262 | ||
|
239 | 263 | try: |
|
240 | 264 | import mercurial.registrar |
|
241 | 265 | import mercurial.configitems |
|
266 | ||
|
242 | 267 | configtable = {} |
|
243 | 268 | configitem = mercurial.registrar.configitem(configtable) |
|
244 | configitem(b'perf', b'presleep', | |
|
269 | configitem( | |
|
270 | b'perf', | |
|
271 | b'presleep', | |
|
245 | 272 | default=mercurial.configitems.dynamicdefault, |
|
246 | 273 | experimental=True, |
|
247 | 274 | ) |
|
248 |
configitem( |
|
|
275 | configitem( | |
|
276 | b'perf', | |
|
277 | b'stub', | |
|
249 | 278 | default=mercurial.configitems.dynamicdefault, |
|
250 | 279 | experimental=True, |
|
251 | 280 | ) |
|
252 | configitem(b'perf', b'parentscount', | |
|
281 | configitem( | |
|
282 | b'perf', | |
|
283 | b'parentscount', | |
|
253 | 284 | default=mercurial.configitems.dynamicdefault, |
|
254 | 285 | experimental=True, |
|
255 | 286 | ) |
|
256 | configitem(b'perf', b'all-timing', | |
|
287 | configitem( | |
|
288 | b'perf', | |
|
289 | b'all-timing', | |
|
257 | 290 | default=mercurial.configitems.dynamicdefault, |
|
258 | 291 | experimental=True, |
|
259 | 292 | ) |
|
260 |
configitem( |
|
|
293 | configitem( | |
|
294 | b'perf', b'pre-run', default=mercurial.configitems.dynamicdefault, | |
|
295 | ) | |
|
296 | configitem( | |
|
297 | b'perf', | |
|
298 | b'profile-benchmark', | |
|
261 | 299 | default=mercurial.configitems.dynamicdefault, |
|
262 | 300 | ) |
|
263 | configitem(b'perf', b'profile-benchmark', | |
|
264 | default=mercurial.configitems.dynamicdefault, | |
|
265 | ) | |
|
266 | configitem(b'perf', b'run-limits', | |
|
301 | configitem( | |
|
302 | b'perf', | |
|
303 | b'run-limits', | |
|
267 | 304 | default=mercurial.configitems.dynamicdefault, |
|
268 | 305 | experimental=True, |
|
269 | 306 | ) |
@@ -272,42 +309,50 b' except (ImportError, AttributeError):' | |||
|
272 | 309 | except TypeError: |
|
273 | 310 | # compatibility fix for a11fd395e83f |
|
274 | 311 | # hg version: 5.2 |
|
275 | configitem(b'perf', b'presleep', | |
|
276 | default=mercurial.configitems.dynamicdefault, | |
|
312 | configitem( | |
|
313 | b'perf', b'presleep', default=mercurial.configitems.dynamicdefault, | |
|
314 | ) | |
|
315 | configitem( | |
|
316 | b'perf', b'stub', default=mercurial.configitems.dynamicdefault, | |
|
317 | ) | |
|
318 | configitem( | |
|
319 | b'perf', b'parentscount', default=mercurial.configitems.dynamicdefault, | |
|
277 | 320 | ) |
|
278 |
configitem( |
|
|
279 | default=mercurial.configitems.dynamicdefault, | |
|
321 | configitem( | |
|
322 | b'perf', b'all-timing', default=mercurial.configitems.dynamicdefault, | |
|
280 | 323 | ) |
|
281 | configitem(b'perf', b'parentscount', | |
|
324 | configitem( | |
|
325 | b'perf', b'pre-run', default=mercurial.configitems.dynamicdefault, | |
|
326 | ) | |
|
327 | configitem( | |
|
328 | b'perf', | |
|
329 | b'profile-benchmark', | |
|
282 | 330 | default=mercurial.configitems.dynamicdefault, |
|
283 | 331 | ) |
|
284 | configitem(b'perf', b'all-timing', | |
|
285 | default=mercurial.configitems.dynamicdefault, | |
|
286 | ) | |
|
287 | configitem(b'perf', b'pre-run', | |
|
288 | default=mercurial.configitems.dynamicdefault, | |
|
332 | configitem( | |
|
333 | b'perf', b'run-limits', default=mercurial.configitems.dynamicdefault, | |
|
289 | 334 | ) |
|
290 | configitem(b'perf', b'profile-benchmark', | |
|
291 | default=mercurial.configitems.dynamicdefault, | |
|
292 | ) | |
|
293 | configitem(b'perf', b'run-limits', | |
|
294 | default=mercurial.configitems.dynamicdefault, | |
|
295 | ) | |
|
335 | ||
|
296 | 336 | |
|
297 | 337 | def getlen(ui): |
|
298 | 338 | if ui.configbool(b"perf", b"stub", False): |
|
299 | 339 | return lambda x: 1 |
|
300 | 340 | return len |
|
301 | 341 | |
|
342 | ||
|
302 | 343 | class noop(object): |
|
303 | 344 | """dummy context manager""" |
|
345 | ||
|
304 | 346 | def __enter__(self): |
|
305 | 347 | pass |
|
348 | ||
|
306 | 349 | def __exit__(self, *args): |
|
307 | 350 | pass |
|
308 | 351 | |
|
352 | ||
|
309 | 353 | NOOPCTX = noop() |
|
310 | 354 | |
|
355 | ||
|
311 | 356 | def gettimer(ui, opts=None): |
|
312 | 357 | """return a timer function and formatter: (timer, formatter) |
|
313 | 358 | |
@@ -338,31 +383,42 b' def gettimer(ui, opts=None):' | |||
|
338 | 383 | # define formatter locally, because ui.formatter has been |
|
339 | 384 | # available since 2.2 (or ae5f92e154d3) |
|
340 | 385 | from mercurial import node |
|
386 | ||
|
341 | 387 | class defaultformatter(object): |
|
342 | 388 | """Minimized composition of baseformatter and plainformatter |
|
343 | 389 | """ |
|
390 | ||
|
344 | 391 | def __init__(self, ui, topic, opts): |
|
345 | 392 | self._ui = ui |
|
346 | 393 | if ui.debugflag: |
|
347 | 394 | self.hexfunc = node.hex |
|
348 | 395 | else: |
|
349 | 396 | self.hexfunc = node.short |
|
397 | ||
|
350 | 398 | def __nonzero__(self): |
|
351 | 399 | return False |
|
400 | ||
|
352 | 401 | __bool__ = __nonzero__ |
|
402 | ||
|
353 | 403 | def startitem(self): |
|
354 | 404 | pass |
|
405 | ||
|
355 | 406 | def data(self, **data): |
|
356 | 407 | pass |
|
408 | ||
|
357 | 409 | def write(self, fields, deftext, *fielddata, **opts): |
|
358 | 410 | self._ui.write(deftext % fielddata, **opts) |
|
411 | ||
|
359 | 412 | def condwrite(self, cond, fields, deftext, *fielddata, **opts): |
|
360 | 413 | if cond: |
|
361 | 414 | self._ui.write(deftext % fielddata, **opts) |
|
415 | ||
|
362 | 416 | def plain(self, text, **opts): |
|
363 | 417 | self._ui.write(text, **opts) |
|
418 | ||
|
364 | 419 | def end(self): |
|
365 | 420 | pass |
|
421 | ||
|
366 | 422 | fm = defaultformatter(ui, b'perf', opts) |
|
367 | 423 | |
|
368 | 424 | # stub function, runs code only once instead of in a loop |
@@ -379,20 +435,27 b' def gettimer(ui, opts=None):' | |||
|
379 | 435 | for item in limitspec: |
|
380 | 436 | parts = item.split(b'-', 1) |
|
381 | 437 | if len(parts) < 2: |
|
382 | ui.warn((b'malformatted run limit entry, missing "-": %s\n' | |
|
383 | % item)) | |
|
438 | ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item)) | |
|
384 | 439 | continue |
|
385 | 440 | try: |
|
386 | 441 | time_limit = float(_sysstr(parts[0])) |
|
387 | 442 | except ValueError as e: |
|
388 | ui.warn((b'malformatted run limit entry, %s: %s\n' | |
|
389 | % (_bytestr(e), item))) | |
|
443 | ui.warn( | |
|
444 | ( | |
|
445 | b'malformatted run limit entry, %s: %s\n' | |
|
446 | % (_bytestr(e), item) | |
|
447 | ) | |
|
448 | ) | |
|
390 | 449 | continue |
|
391 | 450 | try: |
|
392 | 451 | run_limit = int(_sysstr(parts[1])) |
|
393 | 452 | except ValueError as e: |
|
394 | ui.warn((b'malformatted run limit entry, %s: %s\n' | |
|
395 | % (_bytestr(e), item))) | |
|
453 | ui.warn( | |
|
454 | ( | |
|
455 | b'malformatted run limit entry, %s: %s\n' | |
|
456 | % (_bytestr(e), item) | |
|
457 | ) | |
|
458 | ) | |
|
396 | 459 | continue |
|
397 | 460 | limits.append((time_limit, run_limit)) |
|
398 | 461 | if not limits: |
@@ -404,15 +467,23 b' def gettimer(ui, opts=None):' | |||
|
404 | 467 | profiler = profiling.profile(ui) |
|
405 | 468 | |
|
406 | 469 | prerun = getint(ui, b"perf", b"pre-run", 0) |
|
407 | t = functools.partial(_timer, fm, displayall=displayall, limits=limits, | |
|
408 | prerun=prerun, profiler=profiler) | |
|
470 | t = functools.partial( | |
|
471 | _timer, | |
|
472 | fm, | |
|
473 | displayall=displayall, | |
|
474 | limits=limits, | |
|
475 | prerun=prerun, | |
|
476 | profiler=profiler, | |
|
477 | ) | |
|
409 | 478 | return t, fm |
|
410 | 479 | |
|
480 | ||
|
411 | 481 | def stub_timer(fm, func, setup=None, title=None): |
|
412 | 482 | if setup is not None: |
|
413 | 483 | setup() |
|
414 | 484 | func() |
|
415 | 485 | |
|
486 | ||
|
416 | 487 | @contextlib.contextmanager |
|
417 | 488 | def timeone(): |
|
418 | 489 | r = [] |
@@ -431,8 +502,17 b' DEFAULTLIMITS = (' | |||
|
431 | 502 | (10.0, 3), |
|
432 | 503 | ) |
|
433 | 504 | |
|
434 | def _timer(fm, func, setup=None, title=None, displayall=False, | |
|
435 | limits=DEFAULTLIMITS, prerun=0, profiler=None): | |
|
505 | ||
|
506 | def _timer( | |
|
507 | fm, | |
|
508 | func, | |
|
509 | setup=None, | |
|
510 | title=None, | |
|
511 | displayall=False, | |
|
512 | limits=DEFAULTLIMITS, | |
|
513 | prerun=0, | |
|
514 | profiler=None, | |
|
515 | ): | |
|
436 | 516 | gc.collect() |
|
437 | 517 | results = [] |
|
438 | 518 | begin = util.timer() |
@@ -461,8 +541,8 b' def _timer(fm, func, setup=None, title=N' | |||
|
461 | 541 | keepgoing = False |
|
462 | 542 | break |
|
463 | 543 | |
|
464 | formatone(fm, results, title=title, result=r, | |
|
465 | displayall=displayall) | |
|
544 | formatone(fm, results, title=title, result=r, displayall=displayall) | |
|
545 | ||
|
466 | 546 | |
|
467 | 547 | def formatone(fm, timings, title=None, result=None, displayall=False): |
|
468 | 548 | |
@@ -474,6 +554,7 b' def formatone(fm, timings, title=None, r' | |||
|
474 | 554 | fm.write(b'title', b'! %s\n', title) |
|
475 | 555 | if result: |
|
476 | 556 | fm.write(b'result', b'! result: %s\n', result) |
|
557 | ||
|
477 | 558 | def display(role, entry): |
|
478 | 559 | prefix = b'' |
|
479 | 560 | if role != b'best': |
@@ -485,6 +566,7 b' def formatone(fm, timings, title=None, r' | |||
|
485 | 566 |
fm.write(prefix + b'sys', |
|
486 | 567 |
fm.write(prefix + b'count', |
|
487 | 568 | fm.plain(b'\n') |
|
569 | ||
|
488 | 570 | timings.sort() |
|
489 | 571 | min_val = timings[0] |
|
490 | 572 | display(b'best', min_val) |
@@ -496,8 +578,10 b' def formatone(fm, timings, title=None, r' | |||
|
496 | 578 | median = timings[len(timings) // 2] |
|
497 | 579 | display(b'median', median) |
|
498 | 580 | |
|
581 | ||
|
499 | 582 | # utilities for historical portability |
|
500 | 583 | |
|
584 | ||
|
501 | 585 | def getint(ui, section, name, default): |
|
502 | 586 | # for "historical portability": |
|
503 | 587 | # ui.configint has been available since 1.9 (or fa2b596db182) |
@@ -507,8 +591,10 b' def getint(ui, section, name, default):' | |||
|
507 | 591 | try: |
|
508 | 592 | return int(v) |
|
509 | 593 | except ValueError: |
|
510 |
raise error.ConfigError( |
|
|
511 |
|
|
|
594 | raise error.ConfigError( | |
|
595 | b"%s.%s is not an integer ('%s')" % (section, name, v) | |
|
596 | ) | |
|
597 | ||
|
512 | 598 | |
|
513 | 599 | def safeattrsetter(obj, name, ignoremissing=False): |
|
514 | 600 | """Ensure that 'obj' has 'name' attribute before subsequent setattr |
@@ -528,20 +614,29 b' def safeattrsetter(obj, name, ignoremiss' | |||
|
528 | 614 | if not util.safehasattr(obj, name): |
|
529 | 615 | if ignoremissing: |
|
530 | 616 | return None |
|
531 | raise error.Abort((b"missing attribute %s of %s might break assumption" | |
|
532 | b" of performance measurement") % (name, obj)) | |
|
617 | raise error.Abort( | |
|
618 | ( | |
|
619 | b"missing attribute %s of %s might break assumption" | |
|
620 | b" of performance measurement" | |
|
621 | ) | |
|
622 | % (name, obj) | |
|
623 | ) | |
|
533 | 624 | |
|
534 | 625 | origvalue = getattr(obj, _sysstr(name)) |
|
626 | ||
|
535 | 627 | class attrutil(object): |
|
536 | 628 | def set(self, newvalue): |
|
537 | 629 | setattr(obj, _sysstr(name), newvalue) |
|
630 | ||
|
538 | 631 | def restore(self): |
|
539 | 632 | setattr(obj, _sysstr(name), origvalue) |
|
540 | 633 | |
|
541 | 634 | return attrutil() |
|
542 | 635 | |
|
636 | ||
|
543 | 637 | # utilities to examine each internal API changes |
|
544 | 638 | |
|
639 | ||
|
545 | 640 | def getbranchmapsubsettable(): |
|
546 | 641 | # for "historical portability": |
|
547 | 642 | # subsettable is defined in: |
@@ -556,8 +651,11 b' def getbranchmapsubsettable():' | |||
|
556 | 651 | # bisecting in bcee63733aad::59a9f18d4587 can reach here (both |
|
557 | 652 | # branchmap and repoview modules exist, but subsettable attribute |
|
558 | 653 | # doesn't) |
|
559 | raise error.Abort((b"perfbranchmap not available with this Mercurial"), | |
|
560 | hint=b"use 2.5 or later") | |
|
654 | raise error.Abort( | |
|
655 | b"perfbranchmap not available with this Mercurial", | |
|
656 | hint=b"use 2.5 or later", | |
|
657 | ) | |
|
658 | ||
|
561 | 659 | |
|
562 | 660 | def getsvfs(repo): |
|
563 | 661 | """Return appropriate object to access files under .hg/store |
@@ -570,6 +668,7 b' def getsvfs(repo):' | |||
|
570 | 668 | else: |
|
571 | 669 | return getattr(repo, 'sopener') |
|
572 | 670 | |
|
671 | ||
|
573 | 672 | def getvfs(repo): |
|
574 | 673 | """Return appropriate object to access files under .hg |
|
575 | 674 | """ |
@@ -581,6 +680,7 b' def getvfs(repo):' | |||
|
581 | 680 | else: |
|
582 | 681 | return getattr(repo, 'opener') |
|
583 | 682 | |
|
683 | ||
|
584 | 684 | def repocleartagscachefunc(repo): |
|
585 | 685 | """Return the function to clear tags cache according to repo internal API |
|
586 | 686 | """ |
@@ -593,6 +693,7 b' def repocleartagscachefunc(repo):' | |||
|
593 | 693 | # 98c867ac1330), and delattr() can't work in such case |
|
594 | 694 | if b'_tagscache' in vars(repo): |
|
595 | 695 | del repo.__dict__[b'_tagscache'] |
|
696 | ||
|
596 | 697 | return clearcache |
|
597 | 698 | |
|
598 | 699 | repotags = safeattrsetter(repo, b'_tags', ignoremissing=True) |
@@ -608,10 +709,12 b' def repocleartagscachefunc(repo):' | |||
|
608 | 709 | # - repo.tags of such Mercurial isn't "callable", and repo.tags() |
|
609 | 710 | # in perftags() causes failure soon |
|
610 | 711 | # - perf.py itself has been available since 1.1 (or eb240755386d) |
|
611 |
raise error.Abort( |
|
|
712 | raise error.Abort(b"tags API of this hg command is unknown") | |
|
713 | ||
|
612 | 714 | |
|
613 | 715 | # utilities to clear cache |
|
614 | 716 | |
|
717 | ||
|
615 | 718 | def clearfilecache(obj, attrname): |
|
616 | 719 | unfiltered = getattr(obj, 'unfiltered', None) |
|
617 | 720 | if unfiltered is not None: |
@@ -620,23 +723,32 b' def clearfilecache(obj, attrname):' | |||
|
620 | 723 | delattr(obj, attrname) |
|
621 | 724 | obj._filecache.pop(attrname, None) |
|
622 | 725 | |
|
726 | ||
|
623 | 727 | def clearchangelog(repo): |
|
624 | 728 | if repo is not repo.unfiltered(): |
|
625 | 729 | object.__setattr__(repo, r'_clcachekey', None) |
|
626 | 730 | object.__setattr__(repo, r'_clcache', None) |
|
627 | 731 | clearfilecache(repo.unfiltered(), 'changelog') |
|
628 | 732 | |
|
733 | ||
|
629 | 734 | # perf commands |
|
630 | 735 | |
|
736 | ||
|
631 | 737 | @command(b'perfwalk', formatteropts) |
|
632 | 738 | def perfwalk(ui, repo, *pats, **opts): |
|
633 | 739 | opts = _byteskwargs(opts) |
|
634 | 740 | timer, fm = gettimer(ui, opts) |
|
635 | 741 | m = scmutil.match(repo[None], pats, {}) |
|
636 | timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True, | |
|
637 | ignored=False)))) | |
|
742 | timer( | |
|
743 | lambda: len( | |
|
744 | list( | |
|
745 | repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False) | |
|
746 | ) | |
|
747 | ) | |
|
748 | ) | |
|
638 | 749 | fm.end() |
|
639 | 750 | |
|
751 | ||
|
640 | 752 | @command(b'perfannotate', formatteropts) |
|
641 | 753 | def perfannotate(ui, repo, f, **opts): |
|
642 | 754 | opts = _byteskwargs(opts) |
@@ -645,9 +757,12 b' def perfannotate(ui, repo, f, **opts):' | |||
|
645 | 757 | timer(lambda: len(fc.annotate(True))) |
|
646 | 758 | fm.end() |
|
647 | 759 | |
|
648 | @command(b'perfstatus', | |
|
649 | [(b'u', b'unknown', False, | |
|
650 | b'ask status to look for unknown files')] + formatteropts) | |
|
760 | ||
|
761 | @command( | |
|
762 | b'perfstatus', | |
|
763 | [(b'u', b'unknown', False, b'ask status to look for unknown files')] | |
|
764 | + formatteropts, | |
|
765 | ) | |
|
651 | 766 | def perfstatus(ui, repo, **opts): |
|
652 | 767 | opts = _byteskwargs(opts) |
|
653 | 768 | #m = match.always(repo.root, repo.getcwd()) |
@@ -657,6 +772,7 b' def perfstatus(ui, repo, **opts):' | |||
|
657 | 772 | timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown'])))) |
|
658 | 773 | fm.end() |
|
659 | 774 | |
|
775 | ||
|
660 | 776 | @command(b'perfaddremove', formatteropts) |
|
661 | 777 | def perfaddremove(ui, repo, **opts): |
|
662 | 778 | opts = _byteskwargs(opts) |
@@ -675,71 +791,89 b' def perfaddremove(ui, repo, **opts):' | |||
|
675 | 791 | repo.ui.quiet = oldquiet |
|
676 | 792 | fm.end() |
|
677 | 793 | |
|
794 | ||
|
678 | 795 | def clearcaches(cl): |
|
679 | 796 | # behave somewhat consistently across internal API changes |
|
680 | 797 | if util.safehasattr(cl, b'clearcaches'): |
|
681 | 798 | cl.clearcaches() |
|
682 | 799 | elif util.safehasattr(cl, b'_nodecache'): |
|
683 | 800 | from mercurial.node import nullid, nullrev |
|
801 | ||
|
684 | 802 | cl._nodecache = {nullid: nullrev} |
|
685 | 803 | cl._nodepos = None |
|
686 | 804 | |
|
805 | ||
|
687 | 806 | @command(b'perfheads', formatteropts) |
|
688 | 807 | def perfheads(ui, repo, **opts): |
|
689 | 808 | """benchmark the computation of a changelog heads""" |
|
690 | 809 | opts = _byteskwargs(opts) |
|
691 | 810 | timer, fm = gettimer(ui, opts) |
|
692 | 811 | cl = repo.changelog |
|
812 | ||
|
693 | 813 | def s(): |
|
694 | 814 | clearcaches(cl) |
|
815 | ||
|
695 | 816 | def d(): |
|
696 | 817 | len(cl.headrevs()) |
|
818 | ||
|
697 | 819 | timer(d, setup=s) |
|
698 | 820 | fm.end() |
|
699 | 821 | |
|
700 | @command(b'perftags', formatteropts+ | |
|
701 | [ | |
|
702 | (b'', b'clear-revlogs', False, b'refresh changelog and manifest'), | |
|
703 | ]) | |
|
822 | ||
|
823 | @command( | |
|
824 | b'perftags', | |
|
825 | formatteropts | |
|
826 | + [(b'', b'clear-revlogs', False, b'refresh changelog and manifest'),], | |
|
827 | ) | |
|
704 | 828 | def perftags(ui, repo, **opts): |
|
705 | 829 | opts = _byteskwargs(opts) |
|
706 | 830 | timer, fm = gettimer(ui, opts) |
|
707 | 831 | repocleartagscache = repocleartagscachefunc(repo) |
|
708 | 832 | clearrevlogs = opts[b'clear_revlogs'] |
|
833 | ||
|
709 | 834 | def s(): |
|
710 | 835 | if clearrevlogs: |
|
711 | 836 | clearchangelog(repo) |
|
712 | 837 | clearfilecache(repo.unfiltered(), 'manifest') |
|
713 | 838 | repocleartagscache() |
|
839 | ||
|
714 | 840 | def t(): |
|
715 | 841 | return len(repo.tags()) |
|
842 | ||
|
716 | 843 | timer(t, setup=s) |
|
717 | 844 | fm.end() |
|
718 | 845 | |
|
846 | ||
|
719 | 847 | @command(b'perfancestors', formatteropts) |
|
720 | 848 | def perfancestors(ui, repo, **opts): |
|
721 | 849 | opts = _byteskwargs(opts) |
|
722 | 850 | timer, fm = gettimer(ui, opts) |
|
723 | 851 | heads = repo.changelog.headrevs() |
|
852 | ||
|
724 | 853 | def d(): |
|
725 | 854 | for a in repo.changelog.ancestors(heads): |
|
726 | 855 | pass |
|
856 | ||
|
727 | 857 | timer(d) |
|
728 | 858 | fm.end() |
|
729 | 859 | |
|
860 | ||
|
730 | 861 | @command(b'perfancestorset', formatteropts) |
|
731 | 862 | def perfancestorset(ui, repo, revset, **opts): |
|
732 | 863 | opts = _byteskwargs(opts) |
|
733 | 864 | timer, fm = gettimer(ui, opts) |
|
734 | 865 | revs = repo.revs(revset) |
|
735 | 866 | heads = repo.changelog.headrevs() |
|
867 | ||
|
736 | 868 | def d(): |
|
737 | 869 | s = repo.changelog.ancestors(heads) |
|
738 | 870 | for rev in revs: |
|
739 | 871 | rev in s |
|
872 | ||
|
740 | 873 | timer(d) |
|
741 | 874 | fm.end() |
|
742 | 875 | |
|
876 | ||
|
743 | 877 | @command(b'perfdiscovery', formatteropts, b'PATH') |
|
744 | 878 | def perfdiscovery(ui, repo, path, **opts): |
|
745 | 879 | """benchmark discovery between local repo and the peer at given path |
@@ -750,30 +884,38 b' def perfdiscovery(ui, repo, path, **opts' | |||
|
750 | 884 | |
|
751 | 885 | def s(): |
|
752 | 886 | repos[1] = hg.peer(ui, opts, path) |
|
887 | ||
|
753 | 888 | def d(): |
|
754 | 889 | setdiscovery.findcommonheads(ui, *repos) |
|
890 | ||
|
755 | 891 | timer(d, setup=s) |
|
756 | 892 | fm.end() |
|
757 | 893 | |
|
758 | @command(b'perfbookmarks', formatteropts + | |
|
759 | [ | |
|
760 | (b'', b'clear-revlogs', False, b'refresh changelog and manifest'), | |
|
761 | ]) | |
|
894 | ||
|
895 | @command( | |
|
896 | b'perfbookmarks', | |
|
897 | formatteropts | |
|
898 | + [(b'', b'clear-revlogs', False, b'refresh changelog and manifest'),], | |
|
899 | ) | |
|
762 | 900 | def perfbookmarks(ui, repo, **opts): |
|
763 | 901 | """benchmark parsing bookmarks from disk to memory""" |
|
764 | 902 | opts = _byteskwargs(opts) |
|
765 | 903 | timer, fm = gettimer(ui, opts) |
|
766 | 904 | |
|
767 | 905 | clearrevlogs = opts[b'clear_revlogs'] |
|
906 | ||
|
768 | 907 | def s(): |
|
769 | 908 | if clearrevlogs: |
|
770 | 909 | clearchangelog(repo) |
|
771 | 910 | clearfilecache(repo, b'_bookmarks') |
|
911 | ||
|
772 | 912 | def d(): |
|
773 | 913 | repo._bookmarks |
|
914 | ||
|
774 | 915 | timer(d, setup=s) |
|
775 | 916 | fm.end() |
|
776 | 917 | |
|
918 | ||
|
777 | 919 | @command(b'perfbundleread', formatteropts, b'BUNDLE') |
|
778 | 920 | def perfbundleread(ui, repo, bundlepath, **opts): |
|
779 | 921 | """Benchmark reading of bundle files. |
@@ -863,25 +1005,32 b' def perfbundleread(ui, repo, bundlepath,' | |||
|
863 | 1005 | bundle = exchange.readbundle(ui, fh, bundlepath) |
|
864 | 1006 | |
|
865 | 1007 | if isinstance(bundle, changegroup.cg1unpacker): |
|
866 |
benches.extend( |
|
|
1008 | benches.extend( | |
|
1009 | [ | |
|
867 | 1010 | (makebench(deltaiter), b'cg1 deltaiter()'), |
|
868 | 1011 | (makebench(iterchunks), b'cg1 getchunks()'), |
|
869 | 1012 | (makereadnbytes(8192), b'cg1 read(8k)'), |
|
870 | 1013 | (makereadnbytes(16384), b'cg1 read(16k)'), |
|
871 | 1014 | (makereadnbytes(32768), b'cg1 read(32k)'), |
|
872 | 1015 | (makereadnbytes(131072), b'cg1 read(128k)'), |
|
873 |
] |
|
|
1016 | ] | |
|
1017 | ) | |
|
874 | 1018 | elif isinstance(bundle, bundle2.unbundle20): |
|
875 |
benches.extend( |
|
|
1019 | benches.extend( | |
|
1020 | [ | |
|
876 | 1021 | (makebench(forwardchunks), b'bundle2 forwardchunks()'), |
|
877 | 1022 | (makebench(iterparts), b'bundle2 iterparts()'), |
|
878 | (makebench(iterpartsseekable), b'bundle2 iterparts() seekable'), | |
|
1023 | ( | |
|
1024 | makebench(iterpartsseekable), | |
|
1025 | b'bundle2 iterparts() seekable', | |
|
1026 | ), | |
|
879 | 1027 | (makebench(seek), b'bundle2 part seek()'), |
|
880 | 1028 | (makepartreadnbytes(8192), b'bundle2 part read(8k)'), |
|
881 | 1029 | (makepartreadnbytes(16384), b'bundle2 part read(16k)'), |
|
882 | 1030 | (makepartreadnbytes(32768), b'bundle2 part read(32k)'), |
|
883 | 1031 | (makepartreadnbytes(131072), b'bundle2 part read(128k)'), |
|
884 |
] |
|
|
1032 | ] | |
|
1033 | ) | |
|
885 | 1034 | elif isinstance(bundle, streamclone.streamcloneapplier): |
|
886 | 1035 | raise error.Abort(b'stream clone bundles not supported') |
|
887 | 1036 | else: |
@@ -892,9 +1041,15 b' def perfbundleread(ui, repo, bundlepath,' | |||
|
892 | 1041 | timer(fn, title=title) |
|
893 | 1042 | fm.end() |
|
894 | 1043 | |
|
895 | @command(b'perfchangegroupchangelog', formatteropts + | |
|
896 | [(b'', b'cgversion', b'02', b'changegroup version'), | |
|
897 | (b'r', b'rev', b'', b'revisions to add to changegroup')]) | |
|
1044 | ||
|
1045 | @command( | |
|
1046 | b'perfchangegroupchangelog', | |
|
1047 | formatteropts | |
|
1048 | + [ | |
|
1049 | (b'', b'cgversion', b'02', b'changegroup version'), | |
|
1050 | (b'r', b'rev', b'', b'revisions to add to changegroup'), | |
|
1051 | ], | |
|
1052 | ) | |
|
898 | 1053 | def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts): |
|
899 | 1054 | """Benchmark producing a changelog group for a changegroup. |
|
900 | 1055 | |
@@ -923,77 +1078,96 b' def perfchangegroupchangelog(ui, repo, c' | |||
|
923 | 1078 | |
|
924 | 1079 | fm.end() |
|
925 | 1080 | |
|
1081 | ||
|
926 | 1082 | @command(b'perfdirs', formatteropts) |
|
927 | 1083 | def perfdirs(ui, repo, **opts): |
|
928 | 1084 | opts = _byteskwargs(opts) |
|
929 | 1085 | timer, fm = gettimer(ui, opts) |
|
930 | 1086 | dirstate = repo.dirstate |
|
931 | 1087 | b'a' in dirstate |
|
1088 | ||
|
932 | 1089 | def d(): |
|
933 | 1090 | dirstate.hasdir(b'a') |
|
934 | 1091 | del dirstate._map._dirs |
|
1092 | ||
|
935 | 1093 | timer(d) |
|
936 | 1094 | fm.end() |
|
937 | 1095 | |
|
1096 | ||
|
938 | 1097 | @command(b'perfdirstate', formatteropts) |
|
939 | 1098 | def perfdirstate(ui, repo, **opts): |
|
940 | 1099 | opts = _byteskwargs(opts) |
|
941 | 1100 | timer, fm = gettimer(ui, opts) |
|
942 | 1101 | b"a" in repo.dirstate |
|
1102 | ||
|
943 | 1103 | def d(): |
|
944 | 1104 | repo.dirstate.invalidate() |
|
945 | 1105 | b"a" in repo.dirstate |
|
1106 | ||
|
946 | 1107 | timer(d) |
|
947 | 1108 | fm.end() |
|
948 | 1109 | |
|
1110 | ||
|
949 | 1111 | @command(b'perfdirstatedirs', formatteropts) |
|
950 | 1112 | def perfdirstatedirs(ui, repo, **opts): |
|
951 | 1113 | opts = _byteskwargs(opts) |
|
952 | 1114 | timer, fm = gettimer(ui, opts) |
|
953 | 1115 | b"a" in repo.dirstate |
|
1116 | ||
|
954 | 1117 | def d(): |
|
955 | 1118 | repo.dirstate.hasdir(b"a") |
|
956 | 1119 | del repo.dirstate._map._dirs |
|
1120 | ||
|
957 | 1121 | timer(d) |
|
958 | 1122 | fm.end() |
|
959 | 1123 | |
|
1124 | ||
|
960 | 1125 | @command(b'perfdirstatefoldmap', formatteropts) |
|
961 | 1126 | def perfdirstatefoldmap(ui, repo, **opts): |
|
962 | 1127 | opts = _byteskwargs(opts) |
|
963 | 1128 | timer, fm = gettimer(ui, opts) |
|
964 | 1129 | dirstate = repo.dirstate |
|
965 | 1130 | b'a' in dirstate |
|
1131 | ||
|
966 | 1132 | def d(): |
|
967 | 1133 | dirstate._map.filefoldmap.get(b'a') |
|
968 | 1134 | del dirstate._map.filefoldmap |
|
1135 | ||
|
969 | 1136 | timer(d) |
|
970 | 1137 | fm.end() |
|
971 | 1138 | |
|
1139 | ||
|
972 | 1140 | @command(b'perfdirfoldmap', formatteropts) |
|
973 | 1141 | def perfdirfoldmap(ui, repo, **opts): |
|
974 | 1142 | opts = _byteskwargs(opts) |
|
975 | 1143 | timer, fm = gettimer(ui, opts) |
|
976 | 1144 | dirstate = repo.dirstate |
|
977 | 1145 | b'a' in dirstate |
|
1146 | ||
|
978 | 1147 | def d(): |
|
979 | 1148 | dirstate._map.dirfoldmap.get(b'a') |
|
980 | 1149 | del dirstate._map.dirfoldmap |
|
981 | 1150 | del dirstate._map._dirs |
|
1151 | ||
|
982 | 1152 | timer(d) |
|
983 | 1153 | fm.end() |
|
984 | 1154 | |
|
1155 | ||
|
985 | 1156 | @command(b'perfdirstatewrite', formatteropts) |
|
986 | 1157 | def perfdirstatewrite(ui, repo, **opts): |
|
987 | 1158 | opts = _byteskwargs(opts) |
|
988 | 1159 | timer, fm = gettimer(ui, opts) |
|
989 | 1160 | ds = repo.dirstate |
|
990 | 1161 | b"a" in ds |
|
1162 | ||
|
991 | 1163 | def d(): |
|
992 | 1164 | ds._dirty = True |
|
993 | 1165 | ds.write(repo.currenttransaction()) |
|
1166 | ||
|
994 | 1167 | timer(d) |
|
995 | 1168 | fm.end() |
|
996 | 1169 | |
|
1170 | ||
|
997 | 1171 | def _getmergerevs(repo, opts): |
|
998 | 1172 | """parse command argument to return rev involved in merge |
|
999 | 1173 | |
@@ -1016,44 +1190,64 b' def _getmergerevs(repo, opts):' | |||
|
1016 | 1190 | ancestor = wctx.ancestor(rctx) |
|
1017 | 1191 | return (wctx, rctx, ancestor) |
|
1018 | 1192 | |
|
1019 | @command(b'perfmergecalculate', | |
|
1193 | ||
|
1194 | @command( | |
|
1195 | b'perfmergecalculate', | |
|
1020 | 1196 |
|
|
1021 | 1197 |
|
|
1022 | 1198 |
|
|
1023 | 1199 |
|
|
1024 | ] + formatteropts) | |
|
1200 | ] | |
|
1201 | + formatteropts, | |
|
1202 | ) | |
|
1025 | 1203 | def perfmergecalculate(ui, repo, **opts): |
|
1026 | 1204 | opts = _byteskwargs(opts) |
|
1027 | 1205 | timer, fm = gettimer(ui, opts) |
|
1028 | 1206 | |
|
1029 | 1207 | wctx, rctx, ancestor = _getmergerevs(repo, opts) |
|
1208 | ||
|
1030 | 1209 | def d(): |
|
1031 | 1210 | # acceptremote is True because we don't want prompts in the middle of |
|
1032 | 1211 | # our benchmark |
|
1033 | merge.calculateupdates(repo, wctx, rctx, [ancestor], branchmerge=False, | |
|
1034 | force=False, acceptremote=True, | |
|
1035 | followcopies=True) | |
|
1212 | merge.calculateupdates( | |
|
1213 | repo, | |
|
1214 | wctx, | |
|
1215 | rctx, | |
|
1216 | [ancestor], | |
|
1217 | branchmerge=False, | |
|
1218 | force=False, | |
|
1219 | acceptremote=True, | |
|
1220 | followcopies=True, | |
|
1221 | ) | |
|
1222 | ||
|
1036 | 1223 | timer(d) |
|
1037 | 1224 | fm.end() |
|
1038 | 1225 | |
|
1039 | @command(b'perfmergecopies', | |
|
1226 | ||
|
1227 | @command( | |
|
1228 | b'perfmergecopies', | |
|
1040 | 1229 |
|
|
1041 | 1230 |
|
|
1042 | 1231 |
|
|
1043 | 1232 |
|
|
1044 | ] + formatteropts) | |
|
1233 | ] | |
|
1234 | + formatteropts, | |
|
1235 | ) | |
|
1045 | 1236 | def perfmergecopies(ui, repo, **opts): |
|
1046 | 1237 | """measure runtime of `copies.mergecopies`""" |
|
1047 | 1238 | opts = _byteskwargs(opts) |
|
1048 | 1239 | timer, fm = gettimer(ui, opts) |
|
1049 | 1240 | wctx, rctx, ancestor = _getmergerevs(repo, opts) |
|
1241 | ||
|
1050 | 1242 | def d(): |
|
1051 | 1243 | # acceptremote is True because we don't want prompts in the middle of |
|
1052 | 1244 | # our benchmark |
|
1053 | 1245 | copies.mergecopies(repo, wctx, rctx, ancestor) |
|
1246 | ||
|
1054 | 1247 | timer(d) |
|
1055 | 1248 | fm.end() |
|
1056 | 1249 | |
|
1250 | ||
|
1057 | 1251 | @command(b'perfpathcopies', [], b"REV REV") |
|
1058 | 1252 | def perfpathcopies(ui, repo, rev1, rev2, **opts): |
|
1059 | 1253 | """benchmark the copy tracing logic""" |
@@ -1061,20 +1255,26 b' def perfpathcopies(ui, repo, rev1, rev2,' | |||
|
1061 | 1255 | timer, fm = gettimer(ui, opts) |
|
1062 | 1256 | ctx1 = scmutil.revsingle(repo, rev1, rev1) |
|
1063 | 1257 | ctx2 = scmutil.revsingle(repo, rev2, rev2) |
|
1258 | ||
|
1064 | 1259 | def d(): |
|
1065 | 1260 | copies.pathcopies(ctx1, ctx2) |
|
1261 | ||
|
1066 | 1262 | timer(d) |
|
1067 | 1263 | fm.end() |
|
1068 | 1264 | |
|
1069 | @command(b'perfphases', | |
|
1070 | [(b'', b'full', False, b'include file reading time too'), | |
|
1071 | ], b"") | |
|
1265 | ||
|
1266 | @command( | |
|
1267 | b'perfphases', | |
|
1268 | [(b'', b'full', False, b'include file reading time too'),], | |
|
1269 | b"", | |
|
1270 | ) | |
|
1072 | 1271 | def perfphases(ui, repo, **opts): |
|
1073 | 1272 | """benchmark phasesets computation""" |
|
1074 | 1273 | opts = _byteskwargs(opts) |
|
1075 | 1274 | timer, fm = gettimer(ui, opts) |
|
1076 | 1275 | _phases = repo._phasecache |
|
1077 | 1276 | full = opts.get(b'full') |
|
1277 | ||
|
1078 | 1278 | def d(): |
|
1079 | 1279 | phases = _phases |
|
1080 | 1280 | if full: |
@@ -1082,30 +1282,32 b' def perfphases(ui, repo, **opts):' | |||
|
1082 | 1282 | phases = repo._phasecache |
|
1083 | 1283 | phases.invalidate() |
|
1084 | 1284 | phases.loadphaserevs(repo) |
|
1285 | ||
|
1085 | 1286 | timer(d) |
|
1086 | 1287 | fm.end() |
|
1087 | 1288 | |
|
1088 | @command(b'perfphasesremote', | |
|
1089 | [], b"[DEST]") | |
|
1289 | ||
|
1290 | @command(b'perfphasesremote', [], b"[DEST]") | |
|
1090 | 1291 | def perfphasesremote(ui, repo, dest=None, **opts): |
|
1091 | 1292 | """benchmark time needed to analyse phases of the remote server""" |
|
1092 |
from mercurial.node import |
|
|
1093 | bin, | |
|
1094 | ) | |
|
1293 | from mercurial.node import bin | |
|
1095 | 1294 | from mercurial import ( |
|
1096 | 1295 | exchange, |
|
1097 | 1296 | hg, |
|
1098 | 1297 | phases, |
|
1099 | 1298 | ) |
|
1299 | ||
|
1100 | 1300 | opts = _byteskwargs(opts) |
|
1101 | 1301 | timer, fm = gettimer(ui, opts) |
|
1102 | 1302 | |
|
1103 | 1303 | path = ui.paths.getpath(dest, default=(b'default-push', b'default')) |
|
1104 | 1304 | if not path: |
|
1105 | raise error.Abort((b'default repository not configured!'), | |
|
1106 | hint=(b"see 'hg help config.paths'")) | |
|
1305 | raise error.Abort( | |
|
1306 | b'default repository not configured!', | |
|
1307 | hint=b"see 'hg help config.paths'", | |
|
1308 | ) | |
|
1107 | 1309 | dest = path.pushloc or path.loc |
|
1108 |
ui.status |
|
|
1310 | ui.status(b'analysing phase of %s\n' % util.hidepassword(dest)) | |
|
1109 | 1311 | other = hg.peer(repo, opts, dest) |
|
1110 | 1312 | |
|
1111 | 1313 | # easier to perform discovery through the operation |
@@ -1115,14 +1317,15 b' def perfphasesremote(ui, repo, dest=None' | |||
|
1115 | 1317 | remotesubset = op.fallbackheads |
|
1116 | 1318 | |
|
1117 | 1319 | with other.commandexecutor() as e: |
|
1118 |
remotephases = e.callcommand( |
|
|
1119 |
|
|
|
1320 | remotephases = e.callcommand( | |
|
1321 | b'listkeys', {b'namespace': b'phases'} | |
|
1322 | ).result() | |
|
1120 | 1323 | del other |
|
1121 | 1324 | publishing = remotephases.get(b'publishing', False) |
|
1122 | 1325 | if publishing: |
|
1123 |
ui.status |
|
|
1326 | ui.status(b'publishing: yes\n') | |
|
1124 | 1327 | else: |
|
1125 |
ui.status |
|
|
1328 | ui.status(b'publishing: no\n') | |
|
1126 | 1329 | |
|
1127 | 1330 | nodemap = repo.changelog.nodemap |
|
1128 | 1331 | nonpublishroots = 0 |
@@ -1132,19 +1335,25 b' def perfphasesremote(ui, repo, dest=None' | |||
|
1132 | 1335 | node = bin(nhex) |
|
1133 | 1336 | if node in nodemap and int(phase): |
|
1134 | 1337 | nonpublishroots += 1 |
|
1135 |
ui.status |
|
|
1136 |
ui.status |
|
|
1338 | ui.status(b'number of roots: %d\n' % len(remotephases)) | |
|
1339 | ui.status(b'number of known non public roots: %d\n' % nonpublishroots) | |
|
1340 | ||
|
1137 | 1341 | def d(): |
|
1138 | phases.remotephasessummary(repo, | |
|
1139 | remotesubset, | |
|
1140 | remotephases) | |
|
1342 | phases.remotephasessummary(repo, remotesubset, remotephases) | |
|
1343 | ||
|
1141 | 1344 | timer(d) |
|
1142 | 1345 | fm.end() |
|
1143 | 1346 | |
|
1144 | @command(b'perfmanifest',[ | |
|
1347 | ||
|
1348 | @command( | |
|
1349 | b'perfmanifest', | |
|
1350 | [ | |
|
1145 | 1351 |
|
|
1146 | 1352 |
|
|
1147 | ] + formatteropts, b'REV|NODE') | |
|
1353 | ] | |
|
1354 | + formatteropts, | |
|
1355 | b'REV|NODE', | |
|
1356 | ) | |
|
1148 | 1357 | def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts): |
|
1149 | 1358 | """benchmark the time to read a manifest from disk and return a usable |
|
1150 | 1359 | dict-like object |
@@ -1169,25 +1378,32 b' def perfmanifest(ui, repo, rev, manifest' | |||
|
1169 | 1378 | else: |
|
1170 | 1379 | t = repo.manifestlog._revlog.lookup(rev) |
|
1171 | 1380 | except ValueError: |
|
1172 |
raise error.Abort( |
|
|
1173 | b'node') | |
|
1381 | raise error.Abort( | |
|
1382 | b'manifest revision must be integer or full ' b'node' | |
|
1383 | ) | |
|
1384 | ||
|
1174 | 1385 | def d(): |
|
1175 | 1386 | repo.manifestlog.clearcaches(clear_persisted_data=clear_disk) |
|
1176 | 1387 | repo.manifestlog[t].read() |
|
1388 | ||
|
1177 | 1389 | timer(d) |
|
1178 | 1390 | fm.end() |
|
1179 | 1391 | |
|
1392 | ||
|
1180 | 1393 | @command(b'perfchangeset', formatteropts) |
|
1181 | 1394 | def perfchangeset(ui, repo, rev, **opts): |
|
1182 | 1395 | opts = _byteskwargs(opts) |
|
1183 | 1396 | timer, fm = gettimer(ui, opts) |
|
1184 | 1397 | n = scmutil.revsingle(repo, rev).node() |
|
1398 | ||
|
1185 | 1399 | def d(): |
|
1186 | 1400 | repo.changelog.read(n) |
|
1187 | 1401 | #repo.changelog._cache = None |
|
1402 | ||
|
1188 | 1403 | timer(d) |
|
1189 | 1404 | fm.end() |
|
1190 | 1405 | |
|
1406 | ||
|
1191 | 1407 | @command(b'perfignore', formatteropts) |
|
1192 | 1408 | def perfignore(ui, repo, **opts): |
|
1193 | 1409 | """benchmark operation related to computing ignore""" |
@@ -1205,10 +1421,15 b' def perfignore(ui, repo, **opts):' | |||
|
1205 | 1421 | timer(runone, setup=setupone, title=b"load") |
|
1206 | 1422 | fm.end() |
|
1207 | 1423 | |
|
1208 | @command(b'perfindex', [ | |
|
1424 | ||
|
1425 | @command( | |
|
1426 | b'perfindex', | |
|
1427 | [ | |
|
1209 | 1428 |
|
|
1210 | 1429 |
|
|
1211 | ] + formatteropts) | |
|
1430 | ] | |
|
1431 | + formatteropts, | |
|
1432 | ) | |
|
1212 | 1433 | def perfindex(ui, repo, **opts): |
|
1213 | 1434 | """benchmark index creation time followed by a lookup |
|
1214 | 1435 | |
@@ -1231,6 +1452,7 b' def perfindex(ui, repo, **opts):' | |||
|
1231 | 1452 | It is not currently possible to check for lookup of a missing node. For |
|
1232 | 1453 | deeper lookup benchmarking, checkout the `perfnodemap` command.""" |
|
1233 | 1454 | import mercurial.revlog |
|
1455 | ||
|
1234 | 1456 | opts = _byteskwargs(opts) |
|
1235 | 1457 | timer, fm = gettimer(ui, opts) |
|
1236 | 1458 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg |
@@ -1249,20 +1471,28 b' def perfindex(ui, repo, **opts):' | |||
|
1249 | 1471 | # find the filecache func directly |
|
1250 | 1472 | # This avoid polluting the benchmark with the filecache logic |
|
1251 | 1473 | makecl = unfi.__class__.changelog.func |
|
1474 | ||
|
1252 | 1475 | def setup(): |
|
1253 | 1476 | # probably not necessary, but for good measure |
|
1254 | 1477 | clearchangelog(unfi) |
|
1478 | ||
|
1255 | 1479 | def d(): |
|
1256 | 1480 | cl = makecl(unfi) |
|
1257 | 1481 | for n in nodes: |
|
1258 | 1482 | cl.rev(n) |
|
1483 | ||
|
1259 | 1484 | timer(d, setup=setup) |
|
1260 | 1485 | fm.end() |
|
1261 | 1486 | |
|
1262 | @command(b'perfnodemap', [ | |
|
1487 | ||
|
1488 | @command( | |
|
1489 | b'perfnodemap', | |
|
1490 | [ | |
|
1263 | 1491 |
|
|
1264 | 1492 |
|
|
1265 | ] + formatteropts) | |
|
1493 | ] | |
|
1494 | + formatteropts, | |
|
1495 | ) | |
|
1266 | 1496 | def perfnodemap(ui, repo, **opts): |
|
1267 | 1497 | """benchmark the time necessary to look up revision from a cold nodemap |
|
1268 | 1498 | |
@@ -1281,6 +1511,7 b' def perfnodemap(ui, repo, **opts):' | |||
|
1281 | 1511 | hexlookup, prefix lookup and missing lookup would also be valuable. |
|
1282 | 1512 | """ |
|
1283 | 1513 | import mercurial.revlog |
|
1514 | ||
|
1284 | 1515 | opts = _byteskwargs(opts) |
|
1285 | 1516 | timer, fm = gettimer(ui, opts) |
|
1286 | 1517 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg |
@@ -1298,6 +1529,7 b' def perfnodemap(ui, repo, **opts):' | |||
|
1298 | 1529 | |
|
1299 | 1530 | # use a list to pass reference to a nodemap from one closure to the next |
|
1300 | 1531 | nodeget = [None] |
|
1532 | ||
|
1301 | 1533 | def setnodeget(): |
|
1302 | 1534 | # probably not necessary, but for good measure |
|
1303 | 1535 | clearchangelog(unfi) |
@@ -1310,28 +1542,35 b' def perfnodemap(ui, repo, **opts):' | |||
|
1310 | 1542 | |
|
1311 | 1543 | setup = None |
|
1312 | 1544 | if clearcaches: |
|
1545 | ||
|
1313 | 1546 | def setup(): |
|
1314 | 1547 | setnodeget() |
|
1548 | ||
|
1315 | 1549 | else: |
|
1316 | 1550 | setnodeget() |
|
1317 | 1551 | d() # prewarm the data structure |
|
1318 | 1552 | timer(d, setup=setup) |
|
1319 | 1553 | fm.end() |
|
1320 | 1554 | |
|
1555 | ||
|
1321 | 1556 | @command(b'perfstartup', formatteropts) |
|
1322 | 1557 | def perfstartup(ui, repo, **opts): |
|
1323 | 1558 | opts = _byteskwargs(opts) |
|
1324 | 1559 | timer, fm = gettimer(ui, opts) |
|
1560 | ||
|
1325 | 1561 | def d(): |
|
1326 | 1562 | if os.name != r'nt': |
|
1327 | os.system(b"HGRCPATH= %s version -q > /dev/null" % | |
|
1328 |
fsencode(sys.argv[0]) |
|
|
1563 | os.system( | |
|
1564 | b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0]) | |
|
1565 | ) | |
|
1329 | 1566 | else: |
|
1330 | 1567 | os.environ[r'HGRCPATH'] = r' ' |
|
1331 | 1568 | os.system(r"%s version -q > NUL" % sys.argv[0]) |
|
1569 | ||
|
1332 | 1570 | timer(d) |
|
1333 | 1571 | fm.end() |
|
1334 | 1572 | |
|
1573 | ||
|
1335 | 1574 | @command(b'perfparents', formatteropts) |
|
1336 | 1575 | def perfparents(ui, repo, **opts): |
|
1337 | 1576 | """benchmark the time necessary to fetch one changeset's parents. |
@@ -1350,33 +1589,42 b' def perfparents(ui, repo, **opts):' | |||
|
1350 | 1589 | raise error.Abort(b"repo needs %d commits for this test" % count) |
|
1351 | 1590 | repo = repo.unfiltered() |
|
1352 | 1591 | nl = [repo.changelog.node(i) for i in _xrange(count)] |
|
1592 | ||
|
1353 | 1593 | def d(): |
|
1354 | 1594 | for n in nl: |
|
1355 | 1595 | repo.changelog.parents(n) |
|
1596 | ||
|
1356 | 1597 | timer(d) |
|
1357 | 1598 | fm.end() |
|
1358 | 1599 | |
|
1600 | ||
|
1359 | 1601 | @command(b'perfctxfiles', formatteropts) |
|
1360 | 1602 | def perfctxfiles(ui, repo, x, **opts): |
|
1361 | 1603 | opts = _byteskwargs(opts) |
|
1362 | 1604 | x = int(x) |
|
1363 | 1605 | timer, fm = gettimer(ui, opts) |
|
1606 | ||
|
1364 | 1607 | def d(): |
|
1365 | 1608 | len(repo[x].files()) |
|
1609 | ||
|
1366 | 1610 | timer(d) |
|
1367 | 1611 | fm.end() |
|
1368 | 1612 | |
|
1613 | ||
|
1369 | 1614 | @command(b'perfrawfiles', formatteropts) |
|
1370 | 1615 | def perfrawfiles(ui, repo, x, **opts): |
|
1371 | 1616 | opts = _byteskwargs(opts) |
|
1372 | 1617 | x = int(x) |
|
1373 | 1618 | timer, fm = gettimer(ui, opts) |
|
1374 | 1619 | cl = repo.changelog |
|
1620 | ||
|
1375 | 1621 | def d(): |
|
1376 | 1622 | len(cl.read(x)[3]) |
|
1623 | ||
|
1377 | 1624 | timer(d) |
|
1378 | 1625 | fm.end() |
|
1379 | 1626 | |
|
1627 | ||
|
1380 | 1628 | @command(b'perflookup', formatteropts) |
|
1381 | 1629 | def perflookup(ui, repo, rev, **opts): |
|
1382 | 1630 | opts = _byteskwargs(opts) |
@@ -1384,10 +1632,15 b' def perflookup(ui, repo, rev, **opts):' | |||
|
1384 | 1632 | timer(lambda: len(repo.lookup(rev))) |
|
1385 | 1633 | fm.end() |
|
1386 | 1634 | |
|
1387 | @command(b'perflinelogedits', | |
|
1388 | [(b'n', b'edits', 10000, b'number of edits'), | |
|
1635 | ||
|
1636 | @command( | |
|
1637 | b'perflinelogedits', | |
|
1638 | [ | |
|
1639 | (b'n', b'edits', 10000, b'number of edits'), | |
|
1389 | 1640 |
|
|
1390 | ], norepo=True) | |
|
1641 | ], | |
|
1642 | norepo=True, | |
|
1643 | ) | |
|
1391 | 1644 | def perflinelogedits(ui, **opts): |
|
1392 | 1645 | from mercurial import linelog |
|
1393 | 1646 | |
@@ -1418,6 +1671,7 b' def perflinelogedits(ui, **opts):' | |||
|
1418 | 1671 | timer(d) |
|
1419 | 1672 | fm.end() |
|
1420 | 1673 | |
|
1674 | ||
|
1421 | 1675 | @command(b'perfrevrange', formatteropts) |
|
1422 | 1676 | def perfrevrange(ui, repo, *specs, **opts): |
|
1423 | 1677 | opts = _byteskwargs(opts) |
@@ -1426,34 +1680,44 b' def perfrevrange(ui, repo, *specs, **opt' | |||
|
1426 | 1680 | timer(lambda: len(revrange(repo, specs))) |
|
1427 | 1681 | fm.end() |
|
1428 | 1682 | |
|
1683 | ||
|
1429 | 1684 | @command(b'perfnodelookup', formatteropts) |
|
1430 | 1685 | def perfnodelookup(ui, repo, rev, **opts): |
|
1431 | 1686 | opts = _byteskwargs(opts) |
|
1432 | 1687 | timer, fm = gettimer(ui, opts) |
|
1433 | 1688 | import mercurial.revlog |
|
1689 | ||
|
1434 | 1690 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg |
|
1435 | 1691 | n = scmutil.revsingle(repo, rev).node() |
|
1436 | 1692 | cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i") |
|
1693 | ||
|
1437 | 1694 | def d(): |
|
1438 | 1695 | cl.rev(n) |
|
1439 | 1696 | clearcaches(cl) |
|
1697 | ||
|
1440 | 1698 | timer(d) |
|
1441 | 1699 | fm.end() |
|
1442 | 1700 | |
|
1443 | @command(b'perflog', | |
|
1444 | [(b'', b'rename', False, b'ask log to follow renames') | |
|
1445 | ] + formatteropts) | |
|
1701 | ||
|
1702 | @command( | |
|
1703 | b'perflog', | |
|
1704 | [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts, | |
|
1705 | ) | |
|
1446 | 1706 | def perflog(ui, repo, rev=None, **opts): |
|
1447 | 1707 | opts = _byteskwargs(opts) |
|
1448 | 1708 | if rev is None: |
|
1449 | 1709 | rev=[] |
|
1450 | 1710 | timer, fm = gettimer(ui, opts) |
|
1451 | 1711 | ui.pushbuffer() |
|
1452 | timer(lambda: commands.log(ui, repo, rev=rev, date=b'', user=b'', | |
|
1453 | copies=opts.get(b'rename'))) | |
|
1712 | timer( | |
|
1713 | lambda: commands.log( | |
|
1714 | ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename') | |
|
1715 | ) | |
|
1716 | ) | |
|
1454 | 1717 | ui.popbuffer() |
|
1455 | 1718 | fm.end() |
|
1456 | 1719 | |
|
1720 | ||
|
1457 | 1721 | @command(b'perfmoonwalk', formatteropts) |
|
1458 | 1722 | def perfmoonwalk(ui, repo, **opts): |
|
1459 | 1723 | """benchmark walking the changelog backwards |
@@ -1462,21 +1726,27 b' def perfmoonwalk(ui, repo, **opts):' | |||
|
1462 | 1726 | """ |
|
1463 | 1727 | opts = _byteskwargs(opts) |
|
1464 | 1728 | timer, fm = gettimer(ui, opts) |
|
1729 | ||
|
1465 | 1730 | def moonwalk(): |
|
1466 | 1731 | for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1): |
|
1467 | 1732 | ctx = repo[i] |
|
1468 | 1733 | ctx.branch() # read changelog data (in addition to the index) |
|
1734 | ||
|
1469 | 1735 | timer(moonwalk) |
|
1470 | 1736 | fm.end() |
|
1471 | 1737 | |
|
1472 | @command(b'perftemplating', | |
|
1473 | [(b'r', b'rev', [], b'revisions to run the template on'), | |
|
1474 | ] + formatteropts) | |
|
1738 | ||
|
1739 | @command( | |
|
1740 | b'perftemplating', | |
|
1741 | [(b'r', b'rev', [], b'revisions to run the template on'),] + formatteropts, | |
|
1742 | ) | |
|
1475 | 1743 | def perftemplating(ui, repo, testedtemplate=None, **opts): |
|
1476 | 1744 | """test the rendering time of a given template""" |
|
1477 | 1745 | if makelogtemplater is None: |
|
1478 | raise error.Abort((b"perftemplating not available with this Mercurial"), | |
|
1479 | hint=b"use 4.3 or later") | |
|
1746 | raise error.Abort( | |
|
1747 | b"perftemplating not available with this Mercurial", | |
|
1748 | hint=b"use 4.3 or later", | |
|
1749 | ) | |
|
1480 | 1750 | |
|
1481 | 1751 | opts = _byteskwargs(opts) |
|
1482 | 1752 | |
@@ -1488,11 +1758,14 b' def perftemplating(ui, repo, testedtempl' | |||
|
1488 | 1758 | revs = [b'all()'] |
|
1489 | 1759 | revs = list(scmutil.revrange(repo, revs)) |
|
1490 | 1760 | |
|
1491 | defaulttemplate = (b'{date|shortdate} [{rev}:{node|short}]' | |
|
1492 | b' {author|person}: {desc|firstline}\n') | |
|
1761 | defaulttemplate = ( | |
|
1762 | b'{date|shortdate} [{rev}:{node|short}]' | |
|
1763 | b' {author|person}: {desc|firstline}\n' | |
|
1764 | ) | |
|
1493 | 1765 | if testedtemplate is None: |
|
1494 | 1766 | testedtemplate = defaulttemplate |
|
1495 | 1767 | displayer = makelogtemplater(nullui, repo, testedtemplate) |
|
1768 | ||
|
1496 | 1769 | def format(): |
|
1497 | 1770 | for r in revs: |
|
1498 | 1771 | ctx = repo[r] |
@@ -1503,6 +1776,7 b' def perftemplating(ui, repo, testedtempl' | |||
|
1503 | 1776 | timer(format) |
|
1504 | 1777 | fm.end() |
|
1505 | 1778 | |
|
1779 | ||
|
1506 | 1780 | def _displaystats(ui, opts, entries, data): |
|
1507 | 1781 | pass |
|
1508 | 1782 | # use a second formatter because the data are quite different, not sure |
@@ -1549,12 +1823,16 b' def _displaystats(ui, opts, entries, dat' | |||
|
1549 | 1823 | fm.plain('%s: %s\n' % (l, stats[l])) |
|
1550 | 1824 | fm.end() |
|
1551 | 1825 | |
|
1552 | @command(b'perfhelper-mergecopies', formatteropts + | |
|
1553 | [ | |
|
1826 | ||
|
1827 | @command( | |
|
1828 | b'perfhelper-mergecopies', | |
|
1829 | formatteropts | |
|
1830 | + [ | |
|
1554 | 1831 |
|
|
1555 | 1832 |
|
|
1556 | 1833 |
|
|
1557 | ]) | |
|
1834 | ], | |
|
1835 | ) | |
|
1558 | 1836 | def perfhelpermergecopies(ui, repo, revs=[], **opts): |
|
1559 | 1837 | """find statistics about potential parameters for `perfmergecopies` |
|
1560 | 1838 | |
@@ -1591,8 +1869,11 b' def perfhelpermergecopies(ui, repo, revs' | |||
|
1591 | 1869 | ("total.time", "%(time)12.3f"), |
|
1592 | 1870 |
|
|
1593 | 1871 | if not dotiming: |
|
1594 |
output_template = [ |
|
|
1595 | if not ('time' in i[0] or 'renames' in i[0])] | |
|
1872 | output_template = [ | |
|
1873 | i | |
|
1874 | for i in output_template | |
|
1875 | if not ('time' in i[0] or 'renames' in i[0]) | |
|
1876 | ] | |
|
1596 | 1877 | header_names = [h for (h, v) in output_template] |
|
1597 | 1878 | output = ' '.join([v for (h, v) in output_template]) + '\n' |
|
1598 | 1879 | header = ' '.join(['%12s'] * len(header_names)) + '\n' |
@@ -1634,27 +1915,19 b' def perfhelpermergecopies(ui, repo, revs' | |||
|
1634 | 1915 | } |
|
1635 | 1916 | if dostats: |
|
1636 | 1917 | if p1missing: |
|
1637 |
alldata['nbrevs'].append( |
|
|
1638 | data['p1.nbrevs'], | |
|
1639 |
|
|
|
1640 | p1.hex() | |
|
1641 | )) | |
|
1642 | alldata['nbmissingfiles'].append(( | |
|
1643 | data['p1.nbmissingfiles'], | |
|
1644 | b.hex(), | |
|
1645 | p1.hex() | |
|
1646 | )) | |
|
1918 | alldata['nbrevs'].append( | |
|
1919 | (data['p1.nbrevs'], b.hex(), p1.hex()) | |
|
1920 | ) | |
|
1921 | alldata['nbmissingfiles'].append( | |
|
1922 | (data['p1.nbmissingfiles'], b.hex(), p1.hex()) | |
|
1923 | ) | |
|
1647 | 1924 | if p2missing: |
|
1648 |
alldata['nbrevs'].append( |
|
|
1649 | data['p2.nbrevs'], | |
|
1650 |
|
|
|
1651 | p2.hex() | |
|
1652 | )) | |
|
1653 | alldata['nbmissingfiles'].append(( | |
|
1654 | data['p2.nbmissingfiles'], | |
|
1655 | b.hex(), | |
|
1656 | p2.hex() | |
|
1657 | )) | |
|
1925 | alldata['nbrevs'].append( | |
|
1926 | (data['p2.nbrevs'], b.hex(), p2.hex()) | |
|
1927 | ) | |
|
1928 | alldata['nbmissingfiles'].append( | |
|
1929 | (data['p2.nbmissingfiles'], b.hex(), p2.hex()) | |
|
1930 | ) | |
|
1658 | 1931 | if dotiming: |
|
1659 | 1932 | begin = util.timer() |
|
1660 | 1933 | mergedata = copies.mergecopies(repo, p1, p2, b) |
@@ -1682,40 +1955,31 b' def perfhelpermergecopies(ui, repo, revs' | |||
|
1682 | 1955 | |
|
1683 | 1956 | if dostats: |
|
1684 | 1957 | if p1missing: |
|
1685 |
alldata['parentnbrenames'].append( |
|
|
1686 | data['p1.renamedfiles'], | |
|
1687 |
|
|
|
1688 |
|
|
|
1689 | )) | |
|
1690 |
|
|
|
1691 | data['p1.time'], | |
|
1692 | b.hex(), | |
|
1693 | p1.hex() | |
|
1694 | )) | |
|
1958 | alldata['parentnbrenames'].append( | |
|
1959 | (data['p1.renamedfiles'], b.hex(), p1.hex()) | |
|
1960 | ) | |
|
1961 | alldata['parenttime'].append( | |
|
1962 | (data['p1.time'], b.hex(), p1.hex()) | |
|
1963 | ) | |
|
1695 | 1964 | if p2missing: |
|
1696 |
alldata['parentnbrenames'].append( |
|
|
1697 | data['p2.renamedfiles'], | |
|
1698 |
|
|
|
1699 |
|
|
|
1700 | )) | |
|
1701 |
|
|
|
1702 | data['p2.time'], | |
|
1703 | b.hex(), | |
|
1704 | p2.hex() | |
|
1705 | )) | |
|
1965 | alldata['parentnbrenames'].append( | |
|
1966 | (data['p2.renamedfiles'], b.hex(), p2.hex()) | |
|
1967 | ) | |
|
1968 | alldata['parenttime'].append( | |
|
1969 | (data['p2.time'], b.hex(), p2.hex()) | |
|
1970 | ) | |
|
1706 | 1971 | if p1missing or p2missing: |
|
1707 |
alldata['totalnbrenames'].append( |
|
|
1972 | alldata['totalnbrenames'].append( | |
|
1973 | ( | |
|
1708 | 1974 | data['nbrenamedfiles'], |
|
1709 | 1975 | b.hex(), |
|
1710 | 1976 | p1.hex(), |
|
1711 | p2.hex() | |
|
1712 |
|
|
|
1713 |
|
|
|
1714 |
|
|
|
1715 | b.hex(), | |
|
1716 |
|
|
|
1717 | p2.hex() | |
|
1718 | )) | |
|
1977 | p2.hex(), | |
|
1978 | ) | |
|
1979 | ) | |
|
1980 | alldata['totaltime'].append( | |
|
1981 | (data['time'], b.hex(), p1.hex(), p2.hex()) | |
|
1982 | ) | |
|
1719 | 1983 | fm.startitem() |
|
1720 | 1984 | fm.data(**data) |
|
1721 | 1985 | # make node pretty for the human output |
@@ -1734,20 +1998,24 b' def perfhelpermergecopies(ui, repo, revs' | |||
|
1734 | 1998 | ('nbmissingfiles', 'number of missing files at head'), |
|
1735 | 1999 | ] |
|
1736 | 2000 | if dotiming: |
|
1737 |
entries.append( |
|
|
1738 |
|
|
|
2001 | entries.append( | |
|
2002 | ('parentnbrenames', 'rename from one parent to base') | |
|
2003 | ) | |
|
1739 | 2004 | entries.append(('totalnbrenames', 'total number of renames')) |
|
1740 | 2005 | entries.append(('parenttime', 'time for one parent')) |
|
1741 | 2006 | entries.append(('totaltime', 'time for both parents')) |
|
1742 | 2007 | _displaystats(ui, opts, entries, alldata) |
|
1743 | 2008 | |
|
1744 | 2009 | |
|
1745 | @command(b'perfhelper-pathcopies', formatteropts + | |
|
1746 | [ | |
|
2010 | @command( | |
|
2011 | b'perfhelper-pathcopies', | |
|
2012 | formatteropts | |
|
2013 | + [ | |
|
1747 | 2014 |
|
|
1748 | 2015 |
|
|
1749 | 2016 |
|
|
1750 | ]) | |
|
2017 | ], | |
|
2018 | ) | |
|
1751 | 2019 | def perfhelperpathcopies(ui, repo, revs=[], **opts): |
|
1752 | 2020 | """find statistic about potential parameters for the `perftracecopies` |
|
1753 | 2021 | |
@@ -1769,23 +2037,32 b' def perfhelperpathcopies(ui, repo, revs=' | |||
|
1769 | 2037 | |
|
1770 | 2038 | if dotiming: |
|
1771 | 2039 | header = '%12s %12s %12s %12s %12s %12s\n' |
|
1772 | output = ("%(source)12s %(destination)12s " | |
|
2040 | output = ( | |
|
2041 | "%(source)12s %(destination)12s " | |
|
1773 | 2042 |
|
|
1774 |
|
|
|
1775 | header_names = ("source", "destination", "nb-revs", "nb-files", | |
|
1776 | "nb-renames", "time") | |
|
2043 | "%(nbrenamedfiles)12d %(time)18.5f\n" | |
|
2044 | ) | |
|
2045 | header_names = ( | |
|
2046 | "source", | |
|
2047 | "destination", | |
|
2048 | "nb-revs", | |
|
2049 | "nb-files", | |
|
2050 | "nb-renames", | |
|
2051 | "time", | |
|
2052 | ) | |
|
1777 | 2053 | fm.plain(header % header_names) |
|
1778 | 2054 | else: |
|
1779 | 2055 | header = '%12s %12s %12s %12s\n' |
|
1780 | output = ("%(source)12s %(destination)12s " | |
|
1781 |
|
|
|
2056 | output = ( | |
|
2057 | "%(source)12s %(destination)12s " | |
|
2058 | "%(nbrevs)12d %(nbmissingfiles)12d\n" | |
|
2059 | ) | |
|
1782 | 2060 | fm.plain(header % ("source", "destination", "nb-revs", "nb-files")) |
|
1783 | 2061 | |
|
1784 | 2062 | if not revs: |
|
1785 | 2063 | revs = ['all()'] |
|
1786 | 2064 | revs = scmutil.revrange(repo, revs) |
|
1787 | 2065 | |
|
1788 | ||
|
1789 | 2066 | if dostats: |
|
1790 | 2067 | alldata = { |
|
1791 | 2068 | 'nbrevs': [], |
@@ -1815,16 +2092,12 b' def perfhelperpathcopies(ui, repo, revs=' | |||
|
1815 | 2092 | b'nbmissingfiles': len(missing), |
|
1816 | 2093 | } |
|
1817 | 2094 | if dostats: |
|
1818 |
alldata['nbrevs'].append( |
|
|
1819 | data['nbrevs'], | |
|
1820 |
|
|
|
1821 | parent.hex(), | |
|
1822 | )) | |
|
1823 | alldata['nbmissingfiles'].append(( | |
|
1824 | data['nbmissingfiles'], | |
|
1825 | base.hex(), | |
|
1826 | parent.hex(), | |
|
1827 | )) | |
|
2095 | alldata['nbrevs'].append( | |
|
2096 | (data['nbrevs'], base.hex(), parent.hex(),) | |
|
2097 | ) | |
|
2098 | alldata['nbmissingfiles'].append( | |
|
2099 | (data['nbmissingfiles'], base.hex(), parent.hex(),) | |
|
2100 | ) | |
|
1828 | 2101 | if dotiming: |
|
1829 | 2102 | begin = util.timer() |
|
1830 | 2103 | renames = copies.pathcopies(base, parent) |
@@ -1833,16 +2106,12 b' def perfhelperpathcopies(ui, repo, revs=' | |||
|
1833 | 2106 | data['time'] = end - begin |
|
1834 | 2107 | data['nbrenamedfiles'] = len(renames) |
|
1835 | 2108 | if dostats: |
|
1836 |
alldata['time'].append( |
|
|
1837 | data['time'], | |
|
1838 |
|
|
|
1839 |
|
|
|
1840 | )) | |
|
1841 |
|
|
|
1842 | data['nbrenamedfiles'], | |
|
1843 | base.hex(), | |
|
1844 | parent.hex(), | |
|
1845 | )) | |
|
2109 | alldata['time'].append( | |
|
2110 | (data['time'], base.hex(), parent.hex(),) | |
|
2111 | ) | |
|
2112 | alldata['nbrenames'].append( | |
|
2113 | (data['nbrenamedfiles'], base.hex(), parent.hex(),) | |
|
2114 | ) | |
|
1846 | 2115 | fm.startitem() |
|
1847 | 2116 | fm.data(**data) |
|
1848 | 2117 | out = data.copy() |
@@ -1860,11 +2129,11 b' def perfhelperpathcopies(ui, repo, revs=' | |||
|
1860 | 2129 | ('nbmissingfiles', 'number of missing files at head'), |
|
1861 | 2130 | ] |
|
1862 | 2131 | if dotiming: |
|
1863 | entries.append(('nbrenames', | |
|
1864 | 'renamed files')) | |
|
2132 | entries.append(('nbrenames', 'renamed files')) | |
|
1865 | 2133 | entries.append(('time', 'time')) |
|
1866 | 2134 | _displaystats(ui, opts, entries, alldata) |
|
1867 | 2135 | |
|
2136 | ||
|
1868 | 2137 | @command(b'perfcca', formatteropts) |
|
1869 | 2138 | def perfcca(ui, repo, **opts): |
|
1870 | 2139 | opts = _byteskwargs(opts) |
@@ -1872,16 +2141,20 b' def perfcca(ui, repo, **opts):' | |||
|
1872 | 2141 | timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate)) |
|
1873 | 2142 | fm.end() |
|
1874 | 2143 | |
|
2144 | ||
|
1875 | 2145 | @command(b'perffncacheload', formatteropts) |
|
1876 | 2146 | def perffncacheload(ui, repo, **opts): |
|
1877 | 2147 | opts = _byteskwargs(opts) |
|
1878 | 2148 | timer, fm = gettimer(ui, opts) |
|
1879 | 2149 | s = repo.store |
|
2150 | ||
|
1880 | 2151 | def d(): |
|
1881 | 2152 | s.fncache._load() |
|
2153 | ||
|
1882 | 2154 | timer(d) |
|
1883 | 2155 | fm.end() |
|
1884 | 2156 | |
|
2157 | ||
|
1885 | 2158 | @command(b'perffncachewrite', formatteropts) |
|
1886 | 2159 | def perffncachewrite(ui, repo, **opts): |
|
1887 | 2160 | opts = _byteskwargs(opts) |
@@ -1891,26 +2164,32 b' def perffncachewrite(ui, repo, **opts):' | |||
|
1891 | 2164 | s.fncache._load() |
|
1892 | 2165 | tr = repo.transaction(b'perffncachewrite') |
|
1893 | 2166 | tr.addbackup(b'fncache') |
|
2167 | ||
|
1894 | 2168 | def d(): |
|
1895 | 2169 | s.fncache._dirty = True |
|
1896 | 2170 | s.fncache.write(tr) |
|
2171 | ||
|
1897 | 2172 | timer(d) |
|
1898 | 2173 | tr.close() |
|
1899 | 2174 | lock.release() |
|
1900 | 2175 | fm.end() |
|
1901 | 2176 | |
|
2177 | ||
|
1902 | 2178 | @command(b'perffncacheencode', formatteropts) |
|
1903 | 2179 | def perffncacheencode(ui, repo, **opts): |
|
1904 | 2180 | opts = _byteskwargs(opts) |
|
1905 | 2181 | timer, fm = gettimer(ui, opts) |
|
1906 | 2182 | s = repo.store |
|
1907 | 2183 | s.fncache._load() |
|
2184 | ||
|
1908 | 2185 | def d(): |
|
1909 | 2186 | for p in s.fncache.entries: |
|
1910 | 2187 | s.encode(p) |
|
2188 | ||
|
1911 | 2189 | timer(d) |
|
1912 | 2190 | fm.end() |
|
1913 | 2191 | |
|
2192 | ||
|
1914 | 2193 | def _bdiffworker(q, blocks, xdiff, ready, done): |
|
1915 | 2194 | while not done.is_set(): |
|
1916 | 2195 | pair = q.get() |
@@ -1927,6 +2206,7 b' def _bdiffworker(q, blocks, xdiff, ready' | |||
|
1927 | 2206 | with ready: |
|
1928 | 2207 | ready.wait() |
|
1929 | 2208 | |
|
2209 | ||
|
1930 | 2210 | def _manifestrevision(repo, mnode): |
|
1931 | 2211 | ml = repo.manifestlog |
|
1932 | 2212 | |
@@ -1937,15 +2217,25 b' def _manifestrevision(repo, mnode):' | |||
|
1937 | 2217 | |
|
1938 | 2218 | return store.revision(mnode) |
|
1939 | 2219 | |
|
1940 | @command(b'perfbdiff', revlogopts + formatteropts + [ | |
|
1941 | (b'', b'count', 1, b'number of revisions to test (when using --startrev)'), | |
|
2220 | ||
|
2221 | @command( | |
|
2222 | b'perfbdiff', | |
|
2223 | revlogopts | |
|
2224 | + formatteropts | |
|
2225 | + [ | |
|
2226 | ( | |
|
2227 | b'', | |
|
2228 | b'count', | |
|
2229 | 1, | |
|
2230 | b'number of revisions to test (when using --startrev)', | |
|
2231 | ), | |
|
1942 | 2232 | (b'', b'alldata', False, b'test bdiffs for all associated revisions'), |
|
1943 | 2233 | (b'', b'threads', 0, b'number of thread to use (disable with 0)'), |
|
1944 | 2234 | (b'', b'blocks', False, b'test computing diffs into blocks'), |
|
1945 | 2235 | (b'', b'xdiff', False, b'use xdiff algorithm'), |
|
1946 | 2236 | ], |
|
1947 | ||
|
1948 | b'-c|-m|FILE REV') | |
|
2237 | b'-c|-m|FILE REV', | |
|
2238 | ) | |
|
1949 | 2239 | def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts): |
|
1950 | 2240 | """benchmark a bdiff between revisions |
|
1951 | 2241 | |
@@ -2001,6 +2291,7 b' def perfbdiff(ui, repo, file_, rev=None,' | |||
|
2001 | 2291 | |
|
2002 | 2292 | withthreads = threads > 0 |
|
2003 | 2293 | if not withthreads: |
|
2294 | ||
|
2004 | 2295 | def d(): |
|
2005 | 2296 | for pair in textpairs: |
|
2006 | 2297 | if xdiff: |
@@ -2009,6 +2300,7 b' def perfbdiff(ui, repo, file_, rev=None,' | |||
|
2009 | 2300 | mdiff.bdiff.blocks(*pair) |
|
2010 | 2301 | else: |
|
2011 | 2302 | mdiff.textdiff(*pair) |
|
2303 | ||
|
2012 | 2304 | else: |
|
2013 | 2305 | q = queue() |
|
2014 | 2306 | for i in _xrange(threads): |
@@ -2016,9 +2308,11 b' def perfbdiff(ui, repo, file_, rev=None,' | |||
|
2016 | 2308 | ready = threading.Condition() |
|
2017 | 2309 | done = threading.Event() |
|
2018 | 2310 | for i in _xrange(threads): |
|
2019 |
threading.Thread( |
|
|
2020 |
|
|
|
2311 | threading.Thread( | |
|
2312 | target=_bdiffworker, args=(q, blocks, xdiff, ready, done) | |
|
2313 | ).start() | |
|
2021 | 2314 | q.join() |
|
2315 | ||
|
2022 | 2316 | def d(): |
|
2023 | 2317 | for pair in textpairs: |
|
2024 | 2318 | q.put(pair) |
@@ -2027,6 +2321,7 b' def perfbdiff(ui, repo, file_, rev=None,' | |||
|
2027 | 2321 | with ready: |
|
2028 | 2322 | ready.notify_all() |
|
2029 | 2323 | q.join() |
|
2324 | ||
|
2030 | 2325 | timer, fm = gettimer(ui, opts) |
|
2031 | 2326 | timer(d) |
|
2032 | 2327 | fm.end() |
@@ -2038,10 +2333,22 b' def perfbdiff(ui, repo, file_, rev=None,' | |||
|
2038 | 2333 | with ready: |
|
2039 | 2334 | ready.notify_all() |
|
2040 | 2335 | |
|
2041 | @command(b'perfunidiff', revlogopts + formatteropts + [ | |
|
2042 | (b'', b'count', 1, b'number of revisions to test (when using --startrev)'), | |
|
2336 | ||
|
2337 | @command( | |
|
2338 | b'perfunidiff', | |
|
2339 | revlogopts | |
|
2340 | + formatteropts | |
|
2341 | + [ | |
|
2342 | ( | |
|
2343 | b'', | |
|
2344 | b'count', | |
|
2345 | 1, | |
|
2346 | b'number of revisions to test (when using --startrev)', | |
|
2347 | ), | |
|
2043 | 2348 | (b'', b'alldata', False, b'test unidiffs for all associated revisions'), |
|
2044 | ], b'-c|-m|FILE REV') | |
|
2349 | ], | |
|
2350 | b'-c|-m|FILE REV', | |
|
2351 | ) | |
|
2045 | 2352 | def perfunidiff(ui, repo, file_, rev=None, count=None, **opts): |
|
2046 | 2353 | """benchmark a unified diff between revisions |
|
2047 | 2354 | |
@@ -2096,14 +2403,17 b' def perfunidiff(ui, repo, file_, rev=Non' | |||
|
2096 | 2403 | for left, right in textpairs: |
|
2097 | 2404 | # The date strings don't matter, so we pass empty strings. |
|
2098 | 2405 | headerlines, hunks = mdiff.unidiff( |
|
2099 |
left, b'', right, b'', b'left', b'right', binary=False |
|
|
2406 | left, b'', right, b'', b'left', b'right', binary=False | |
|
2407 | ) | |
|
2100 | 2408 | # consume iterators in roughly the way patch.py does |
|
2101 | 2409 | b'\n'.join(headerlines) |
|
2102 | 2410 | b''.join(sum((list(hlines) for hrange, hlines in hunks), [])) |
|
2411 | ||
|
2103 | 2412 | timer, fm = gettimer(ui, opts) |
|
2104 | 2413 | timer(d) |
|
2105 | 2414 | fm.end() |
|
2106 | 2415 | |
|
2416 | ||
|
2107 | 2417 | @command(b'perfdiffwd', formatteropts) |
|
2108 | 2418 | def perfdiffwd(ui, repo, **opts): |
|
2109 | 2419 | """Profile diff of working directory changes""" |
@@ -2117,17 +2427,19 b' def perfdiffwd(ui, repo, **opts):' | |||
|
2117 | 2427 | |
|
2118 | 2428 | for diffopt in ('', 'w', 'b', 'B', 'wB'): |
|
2119 | 2429 | opts = dict((options[c], b'1') for c in diffopt) |
|
2430 | ||
|
2120 | 2431 | def d(): |
|
2121 | 2432 | ui.pushbuffer() |
|
2122 | 2433 | commands.diff(ui, repo, **opts) |
|
2123 | 2434 | ui.popbuffer() |
|
2435 | ||
|
2124 | 2436 | diffopt = diffopt.encode('ascii') |
|
2125 | 2437 | title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none') |
|
2126 | 2438 | timer(d, title=title) |
|
2127 | 2439 | fm.end() |
|
2128 | 2440 | |
|
2129 | @command(b'perfrevlogindex', revlogopts + formatteropts, | |
|
2130 | b'-c|-m|FILE') | |
|
2441 | ||
|
2442 | @command(b'perfrevlogindex', revlogopts + formatteropts, b'-c|-m|FILE') | |
|
2131 | 2443 | def perfrevlogindex(ui, repo, file_=None, **opts): |
|
2132 | 2444 | """Benchmark operations against a revlog index. |
|
2133 | 2445 | |
@@ -2150,7 +2462,7 b' def perfrevlogindex(ui, repo, file_=None' | |||
|
2150 | 2462 | revlogio = revlog.revlogio() |
|
2151 | 2463 | inline = header & (1 << 16) |
|
2152 | 2464 | else: |
|
2153 |
raise error.Abort( |
|
|
2465 | raise error.Abort(b'unsupported revlog version: %d' % version) | |
|
2154 | 2466 | |
|
2155 | 2467 | rllen = len(rl) |
|
2156 | 2468 | |
@@ -2221,22 +2533,26 b' def perfrevlogindex(ui, repo, file_=None' | |||
|
2221 | 2533 | (lambda: resolvenode(node75), b'look up node at 3/4 len'), |
|
2222 | 2534 | (lambda: resolvenode(node100), b'look up node at tip'), |
|
2223 | 2535 | # 2x variation is to measure caching impact. |
|
2224 | (lambda: resolvenodes(allnodes), | |
|
2225 | b'look up all nodes (forward)'), | |
|
2226 |
(lambda: resolvenodes(allnodes, |
|
|
2227 | b'look up all nodes 2x (forward)'), | |
|
2228 |
|
|
|
2229 |
b'look up all nodes (reverse)' |
|
|
2230 | (lambda: resolvenodes(allnodesrev, 2), | |
|
2231 | b'look up all nodes 2x (reverse)'), | |
|
2232 | (lambda: getentries(allrevs), | |
|
2233 | b'retrieve all index entries (forward)'), | |
|
2234 | (lambda: getentries(allrevs, 2), | |
|
2235 | b'retrieve all index entries 2x (forward)'), | |
|
2236 | (lambda: getentries(allrevsrev), | |
|
2237 | b'retrieve all index entries (reverse)'), | |
|
2238 | (lambda: getentries(allrevsrev, 2), | |
|
2239 | b'retrieve all index entries 2x (reverse)'), | |
|
2536 | (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'), | |
|
2537 | (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'), | |
|
2538 | (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'), | |
|
2539 | ( | |
|
2540 | lambda: resolvenodes(allnodesrev, 2), | |
|
2541 | b'look up all nodes 2x (reverse)', | |
|
2542 | ), | |
|
2543 | (lambda: getentries(allrevs), b'retrieve all index entries (forward)'), | |
|
2544 | ( | |
|
2545 | lambda: getentries(allrevs, 2), | |
|
2546 | b'retrieve all index entries 2x (forward)', | |
|
2547 | ), | |
|
2548 | ( | |
|
2549 | lambda: getentries(allrevsrev), | |
|
2550 | b'retrieve all index entries (reverse)', | |
|
2551 | ), | |
|
2552 | ( | |
|
2553 | lambda: getentries(allrevsrev, 2), | |
|
2554 | b'retrieve all index entries 2x (reverse)', | |
|
2555 | ), | |
|
2240 | 2556 | ] |
|
2241 | 2557 | |
|
2242 | 2558 | for fn, title in benches: |
@@ -2244,13 +2560,21 b' def perfrevlogindex(ui, repo, file_=None' | |||
|
2244 | 2560 | timer(fn, title=title) |
|
2245 | 2561 | fm.end() |
|
2246 | 2562 | |
|
2247 | @command(b'perfrevlogrevisions', revlogopts + formatteropts + | |
|
2248 | [(b'd', b'dist', 100, b'distance between the revisions'), | |
|
2563 | ||
|
2564 | @command( | |
|
2565 | b'perfrevlogrevisions', | |
|
2566 | revlogopts | |
|
2567 | + formatteropts | |
|
2568 | + [ | |
|
2569 | (b'd', b'dist', 100, b'distance between the revisions'), | |
|
2249 | 2570 |
|
|
2250 |
|
|
|
2251 | b'-c|-m|FILE') | |
|
2252 | def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False, | |
|
2253 | **opts): | |
|
2571 | (b'', b'reverse', False, b'read in reverse'), | |
|
2572 | ], | |
|
2573 | b'-c|-m|FILE', | |
|
2574 | ) | |
|
2575 | def perfrevlogrevisions( | |
|
2576 | ui, repo, file_=None, startrev=0, reverse=False, **opts | |
|
2577 | ): | |
|
2254 | 2578 | """Benchmark reading a series of revisions from a revlog. |
|
2255 | 2579 | |
|
2256 | 2580 | By default, we read every ``-d/--dist`` revision from 0 to tip of |
@@ -2286,8 +2610,13 b' def perfrevlogrevisions(ui, repo, file_=' | |||
|
2286 | 2610 | timer(d) |
|
2287 | 2611 | fm.end() |
|
2288 | 2612 | |
|
2289 | @command(b'perfrevlogwrite', revlogopts + formatteropts + | |
|
2290 | [(b's', b'startrev', 1000, b'revision to start writing at'), | |
|
2613 | ||
|
2614 | @command( | |
|
2615 | b'perfrevlogwrite', | |
|
2616 | revlogopts | |
|
2617 | + formatteropts | |
|
2618 | + [ | |
|
2619 | (b's', b'startrev', 1000, b'revision to start writing at'), | |
|
2291 | 2620 |
|
|
2292 | 2621 |
|
|
2293 | 2622 |
|
@@ -2295,7 +2624,8 b' def perfrevlogrevisions(ui, repo, file_=' | |||
|
2295 | 2624 |
|
|
2296 | 2625 |
|
|
2297 | 2626 |
|
|
2298 |
|
|
|
2627 | b'-c|-m|FILE', | |
|
2628 | ) | |
|
2299 | 2629 | def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts): |
|
2300 | 2630 | """Benchmark writing a series of revisions to a revlog. |
|
2301 | 2631 | |
@@ -2329,8 +2659,13 b' def perfrevlogwrite(ui, repo, file_=None' | |||
|
2329 | 2659 | lazydeltabase = opts['lazydeltabase'] |
|
2330 | 2660 | source = opts['source'] |
|
2331 | 2661 | clearcaches = opts['clear_caches'] |
|
2332 | validsource = (b'full', b'parent-1', b'parent-2', b'parent-smallest', | |
|
2333 | b'storage') | |
|
2662 | validsource = ( | |
|
2663 | b'full', | |
|
2664 | b'parent-1', | |
|
2665 | b'parent-2', | |
|
2666 | b'parent-smallest', | |
|
2667 | b'storage', | |
|
2668 | ) | |
|
2334 | 2669 | if source not in validsource: |
|
2335 | 2670 | raise error.Abort('invalid source type: %s' % source) |
|
2336 | 2671 | |
@@ -2340,9 +2675,16 b' def perfrevlogwrite(ui, repo, file_=None' | |||
|
2340 | 2675 | raise error.Abort('invalide run count: %d' % count) |
|
2341 | 2676 | allresults = [] |
|
2342 | 2677 | for c in range(count): |
|
2343 |
timing = _timeonewrite( |
|
|
2678 | timing = _timeonewrite( | |
|
2679 | ui, | |
|
2680 | rl, | |
|
2681 | source, | |
|
2682 | startrev, | |
|
2683 | stoprev, | |
|
2684 | c + 1, | |
|
2344 | 2685 |
|
|
2345 |
|
|
|
2686 | clearcaches=clearcaches, | |
|
2687 | ) | |
|
2346 | 2688 | allresults.append(timing) |
|
2347 | 2689 | |
|
2348 | 2690 | ### consolidate the results in a single list |
@@ -2396,20 +2738,37 b' def perfrevlogwrite(ui, repo, file_=None' | |||
|
2396 | 2738 | # for now |
|
2397 | 2739 | totaltime = [] |
|
2398 | 2740 | for item in allresults: |
|
2399 |
totaltime.append( |
|
|
2741 | totaltime.append( | |
|
2742 | ( | |
|
2743 | sum(x[1][0] for x in item), | |
|
2400 | 2744 |
|
|
2401 |
|
|
|
2745 | sum(x[1][2] for x in item), | |
|
2746 | ) | |
|
2402 | 2747 | ) |
|
2403 | formatone(fm, totaltime, title="total time (%d revs)" % resultcount, | |
|
2404 | displayall=displayall) | |
|
2748 | formatone( | |
|
2749 | fm, | |
|
2750 | totaltime, | |
|
2751 | title="total time (%d revs)" % resultcount, | |
|
2752 | displayall=displayall, | |
|
2753 | ) | |
|
2405 | 2754 | fm.end() |
|
2406 | 2755 | |
|
2756 | ||
|
2407 | 2757 | class _faketr(object): |
|
2408 | 2758 | def add(s, x, y, z=None): |
|
2409 | 2759 | return None |
|
2410 | 2760 | |
|
2411 | def _timeonewrite(ui, orig, source, startrev, stoprev, runidx=None, | |
|
2412 | lazydeltabase=True, clearcaches=True): | |
|
2761 | ||
|
2762 | def _timeonewrite( | |
|
2763 | ui, | |
|
2764 | orig, | |
|
2765 | source, | |
|
2766 | startrev, | |
|
2767 | stoprev, | |
|
2768 | runidx=None, | |
|
2769 | lazydeltabase=True, | |
|
2770 | clearcaches=True, | |
|
2771 | ): | |
|
2413 | 2772 | timings = [] |
|
2414 | 2773 | tr = _faketr() |
|
2415 | 2774 | with _temprevlog(ui, orig, startrev) as dest: |
@@ -2422,13 +2781,18 b' def _timeonewrite(ui, orig, source, star' | |||
|
2422 | 2781 |
|
|
2423 | 2782 | if util.safehasattr(ui, 'makeprogress'): |
|
2424 | 2783 | progress = ui.makeprogress(topic, unit='revs', total=total) |
|
2784 | ||
|
2425 | 2785 | def updateprogress(pos): |
|
2426 | 2786 | progress.update(pos) |
|
2787 | ||
|
2427 | 2788 | def completeprogress(): |
|
2428 | 2789 | progress.complete() |
|
2790 | ||
|
2429 | 2791 | else: |
|
2792 | ||
|
2430 | 2793 | def updateprogress(pos): |
|
2431 | 2794 | ui.progress(topic, pos, unit='revs', total=total) |
|
2795 | ||
|
2432 | 2796 | def completeprogress(): |
|
2433 | 2797 | ui.progress(topic, None, unit='revs', total=total) |
|
2434 | 2798 | |
@@ -2445,6 +2809,7 b' def _timeonewrite(ui, orig, source, star' | |||
|
2445 | 2809 | completeprogress() |
|
2446 | 2810 | return timings |
|
2447 | 2811 | |
|
2812 | ||
|
2448 | 2813 | def _getrevisionseed(orig, rev, tr, source): |
|
2449 | 2814 | from mercurial.node import nullid |
|
2450 | 2815 | |
@@ -2481,8 +2846,11 b' def _getrevisionseed(orig, rev, tr, sour' | |||
|
2481 | 2846 | baserev = orig.deltaparent(rev) |
|
2482 | 2847 | cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev)) |
|
2483 | 2848 | |
|
2484 | return ((text, tr, linkrev, p1, p2), | |
|
2485 | {'node': node, 'flags': flags, 'cachedelta': cachedelta}) | |
|
2849 | return ( | |
|
2850 | (text, tr, linkrev, p1, p2), | |
|
2851 | {'node': node, 'flags': flags, 'cachedelta': cachedelta}, | |
|
2852 | ) | |
|
2853 | ||
|
2486 | 2854 | |
|
2487 | 2855 | @contextlib.contextmanager |
|
2488 | 2856 | def _temprevlog(ui, orig, truncaterev): |
@@ -2523,9 +2891,9 b' def _temprevlog(ui, orig, truncaterev):' | |||
|
2523 | 2891 | vfs = vfsmod.vfs(tmpdir) |
|
2524 | 2892 | vfs.options = getattr(orig.opener, 'options', None) |
|
2525 | 2893 | |
|
2526 |
dest = revlog.revlog( |
|
|
2527 | indexfile=indexname, | |
|
2528 | datafile=dataname, **revlogkwargs) | |
|
2894 | dest = revlog.revlog( | |
|
2895 | vfs, indexfile=indexname, datafile=dataname, **revlogkwargs | |
|
2896 | ) | |
|
2529 | 2897 | if dest._inline: |
|
2530 | 2898 | raise error.Abort('not supporting inline revlog (yet)') |
|
2531 | 2899 | # make sure internals are initialized |
@@ -2535,10 +2903,17 b' def _temprevlog(ui, orig, truncaterev):' | |||
|
2535 | 2903 | finally: |
|
2536 | 2904 | shutil.rmtree(tmpdir, True) |
|
2537 | 2905 | |
|
2538 | @command(b'perfrevlogchunks', revlogopts + formatteropts + | |
|
2539 | [(b'e', b'engines', b'', b'compression engines to use'), | |
|
2540 | (b's', b'startrev', 0, b'revision to start at')], | |
|
2541 | b'-c|-m|FILE') | |
|
2906 | ||
|
2907 | @command( | |
|
2908 | b'perfrevlogchunks', | |
|
2909 | revlogopts | |
|
2910 | + formatteropts | |
|
2911 | + [ | |
|
2912 | (b'e', b'engines', b'', b'compression engines to use'), | |
|
2913 | (b's', b'startrev', 0, b'revision to start at'), | |
|
2914 | ], | |
|
2915 | b'-c|-m|FILE', | |
|
2916 | ) | |
|
2542 | 2917 | def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts): |
|
2543 | 2918 | """Benchmark operations on revlog chunks. |
|
2544 | 2919 | |
@@ -2645,17 +3020,26 b' def perfrevlogchunks(ui, repo, file_=Non' | |||
|
2645 | 3020 | |
|
2646 | 3021 | for engine in sorted(engines): |
|
2647 | 3022 | compressor = util.compengines[engine].revlogcompressor() |
|
2648 | benches.append((functools.partial(docompress, compressor), | |
|
2649 | b'compress w/ %s' % engine)) | |
|
3023 | benches.append( | |
|
3024 | ( | |
|
3025 | functools.partial(docompress, compressor), | |
|
3026 | b'compress w/ %s' % engine, | |
|
3027 | ) | |
|
3028 | ) | |
|
2650 | 3029 | |
|
2651 | 3030 | for fn, title in benches: |
|
2652 | 3031 | timer, fm = gettimer(ui, opts) |
|
2653 | 3032 | timer(fn, title=title) |
|
2654 | 3033 | fm.end() |
|
2655 | 3034 | |
|
2656 | @command(b'perfrevlogrevision', revlogopts + formatteropts + | |
|
2657 | [(b'', b'cache', False, b'use caches instead of clearing')], | |
|
2658 | b'-c|-m|FILE REV') | |
|
3035 | ||
|
3036 | @command( | |
|
3037 | b'perfrevlogrevision', | |
|
3038 | revlogopts | |
|
3039 | + formatteropts | |
|
3040 | + [(b'', b'cache', False, b'use caches instead of clearing')], | |
|
3041 | b'-c|-m|FILE REV', | |
|
3042 | ) | |
|
2659 | 3043 | def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts): |
|
2660 | 3044 | """Benchmark obtaining a revlog revision. |
|
2661 | 3045 | |
@@ -2777,22 +3161,30 b' def perfrevlogrevision(ui, repo, file_, ' | |||
|
2777 | 3161 | slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain') |
|
2778 | 3162 | benches.append(slicing) |
|
2779 | 3163 | |
|
2780 |
benches.extend( |
|
|
3164 | benches.extend( | |
|
3165 | [ | |
|
2781 | 3166 | (lambda: dorawchunks(data, slicedchain), b'rawchunks'), |
|
2782 | 3167 | (lambda: dodecompress(rawchunks), b'decompress'), |
|
2783 | 3168 | (lambda: dopatch(text, bins), b'patch'), |
|
2784 | 3169 | (lambda: dohash(text), b'hash'), |
|
2785 |
] |
|
|
3170 | ] | |
|
3171 | ) | |
|
2786 | 3172 | |
|
2787 | 3173 | timer, fm = gettimer(ui, opts) |
|
2788 | 3174 | for fn, title in benches: |
|
2789 | 3175 | timer(fn, title=title) |
|
2790 | 3176 | fm.end() |
|
2791 | 3177 | |
|
2792 | @command(b'perfrevset', | |
|
2793 | [(b'C', b'clear', False, b'clear volatile cache between each call.'), | |
|
2794 | (b'', b'contexts', False, b'obtain changectx for each revision')] | |
|
2795 | + formatteropts, b"REVSET") | |
|
3178 | ||
|
3179 | @command( | |
|
3180 | b'perfrevset', | |
|
3181 | [ | |
|
3182 | (b'C', b'clear', False, b'clear volatile cache between each call.'), | |
|
3183 | (b'', b'contexts', False, b'obtain changectx for each revision'), | |
|
3184 | ] | |
|
3185 | + formatteropts, | |
|
3186 | b"REVSET", | |
|
3187 | ) | |
|
2796 | 3188 | def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts): |
|
2797 | 3189 | """benchmark the execution time of a revset |
|
2798 | 3190 | |
@@ -2802,19 +3194,26 b' def perfrevset(ui, repo, expr, clear=Fal' | |||
|
2802 | 3194 | opts = _byteskwargs(opts) |
|
2803 | 3195 | |
|
2804 | 3196 | timer, fm = gettimer(ui, opts) |
|
3197 | ||
|
2805 | 3198 | def d(): |
|
2806 | 3199 | if clear: |
|
2807 | 3200 | repo.invalidatevolatilesets() |
|
2808 | 3201 | if contexts: |
|
2809 |
for ctx in repo.set(expr): |
|
|
3202 | for ctx in repo.set(expr): | |
|
3203 | pass | |
|
2810 | 3204 | else: |
|
2811 |
for r in repo.revs(expr): |
|
|
3205 | for r in repo.revs(expr): | |
|
3206 | pass | |
|
3207 | ||
|
2812 | 3208 | timer(d) |
|
2813 | 3209 | fm.end() |
|
2814 | 3210 | |
|
2815 | @command(b'perfvolatilesets', | |
|
2816 | [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'), | |
|
2817 | ] + formatteropts) | |
|
3211 | ||
|
3212 | @command( | |
|
3213 | b'perfvolatilesets', | |
|
3214 | [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),] | |
|
3215 | + formatteropts, | |
|
3216 | ) | |
|
2818 | 3217 | def perfvolatilesets(ui, repo, *names, **opts): |
|
2819 | 3218 | """benchmark the computation of various volatile set |
|
2820 | 3219 | |
@@ -2829,6 +3228,7 b' def perfvolatilesets(ui, repo, *names, *' | |||
|
2829 | 3228 | if opts[b'clear_obsstore']: |
|
2830 | 3229 | clearfilecache(repo, b'obsstore') |
|
2831 | 3230 | obsolete.getrevs(repo, name) |
|
3231 | ||
|
2832 | 3232 | return d |
|
2833 | 3233 | |
|
2834 | 3234 | allobs = sorted(obsolete.cachefuncs) |
@@ -2844,6 +3244,7 b' def perfvolatilesets(ui, repo, *names, *' | |||
|
2844 | 3244 | if opts[b'clear_obsstore']: |
|
2845 | 3245 | clearfilecache(repo, b'obsstore') |
|
2846 | 3246 | repoview.filterrevs(repo, name) |
|
3247 | ||
|
2847 | 3248 | return d |
|
2848 | 3249 | |
|
2849 | 3250 | allfilter = sorted(repoview.filtertable) |
@@ -2854,12 +3255,20 b' def perfvolatilesets(ui, repo, *names, *' | |||
|
2854 | 3255 | timer(getfiltered(name), title=name) |
|
2855 | 3256 | fm.end() |
|
2856 | 3257 | |
|
2857 | @command(b'perfbranchmap', | |
|
2858 | [(b'f', b'full', False, | |
|
2859 | b'Includes build time of subset'), | |
|
2860 | (b'', b'clear-revbranch', False, | |
|
2861 | b'purge the revbranch cache between computation'), | |
|
2862 | ] + formatteropts) | |
|
3258 | ||
|
3259 | @command( | |
|
3260 | b'perfbranchmap', | |
|
3261 | [ | |
|
3262 | (b'f', b'full', False, b'Includes build time of subset'), | |
|
3263 | ( | |
|
3264 | b'', | |
|
3265 | b'clear-revbranch', | |
|
3266 | False, | |
|
3267 | b'purge the revbranch cache between computation', | |
|
3268 | ), | |
|
3269 | ] | |
|
3270 | + formatteropts, | |
|
3271 | ) | |
|
2863 | 3272 | def perfbranchmap(ui, repo, *filternames, **opts): |
|
2864 | 3273 | """benchmark the update of a branchmap |
|
2865 | 3274 | |
@@ -2869,6 +3278,7 b' def perfbranchmap(ui, repo, *filternames' | |||
|
2869 | 3278 | full = opts.get(b"full", False) |
|
2870 | 3279 | clear_revbranch = opts.get(b"clear_revbranch", False) |
|
2871 | 3280 | timer, fm = gettimer(ui, opts) |
|
3281 | ||
|
2872 | 3282 | def getbranchmap(filtername): |
|
2873 | 3283 | """generate a benchmark function for the filtername""" |
|
2874 | 3284 | if filtername is None: |
@@ -2880,6 +3290,7 b' def perfbranchmap(ui, repo, *filternames' | |||
|
2880 | 3290 | else: |
|
2881 | 3291 | # older versions |
|
2882 | 3292 | filtered = view._branchcaches |
|
3293 | ||
|
2883 | 3294 | def d(): |
|
2884 | 3295 | if clear_revbranch: |
|
2885 | 3296 | repo.revbranchcache()._clear() |
@@ -2888,7 +3299,9 b' def perfbranchmap(ui, repo, *filternames' | |||
|
2888 | 3299 | else: |
|
2889 | 3300 | filtered.pop(filtername, None) |
|
2890 | 3301 | view.branchmap() |
|
3302 | ||
|
2891 | 3303 | return d |
|
3304 | ||
|
2892 | 3305 | # add filter in smaller subset to bigger subset |
|
2893 | 3306 | possiblefilters = set(repoview.filtertable) |
|
2894 | 3307 | if filternames: |
@@ -2933,11 +3346,16 b' def perfbranchmap(ui, repo, *filternames' | |||
|
2933 | 3346 | branchcachewrite.restore() |
|
2934 | 3347 | fm.end() |
|
2935 | 3348 | |
|
2936 | @command(b'perfbranchmapupdate', [ | |
|
3349 | ||
|
3350 | @command( | |
|
3351 | b'perfbranchmapupdate', | |
|
3352 | [ | |
|
2937 | 3353 | (b'', b'base', [], b'subset of revision to start from'), |
|
2938 | 3354 | (b'', b'target', [], b'subset of revision to end with'), |
|
2939 | (b'', b'clear-caches', False, b'clear cache between each runs') | |
|
2940 | ] + formatteropts) | |
|
3355 | (b'', b'clear-caches', False, b'clear cache between each runs'), | |
|
3356 | ] | |
|
3357 | + formatteropts, | |
|
3358 | ) | |
|
2941 | 3359 | def perfbranchmapupdate(ui, repo, base=(), target=(), **opts): |
|
2942 | 3360 | """benchmark branchmap update from for <base> revs to <target> revs |
|
2943 | 3361 | |
@@ -2956,6 +3374,7 b' def perfbranchmapupdate(ui, repo, base=(' | |||
|
2956 | 3374 | """ |
|
2957 | 3375 | from mercurial import branchmap |
|
2958 | 3376 | from mercurial import repoview |
|
3377 | ||
|
2959 | 3378 | opts = _byteskwargs(opts) |
|
2960 | 3379 | timer, fm = gettimer(ui, opts) |
|
2961 | 3380 | clearcaches = opts[b'clear_caches'] |
@@ -3037,12 +3456,16 b' def perfbranchmapupdate(ui, repo, base=(' | |||
|
3037 | 3456 | repoview.filtertable.pop(b'__perf_branchmap_update_base', None) |
|
3038 | 3457 | repoview.filtertable.pop(b'__perf_branchmap_update_target', None) |
|
3039 | 3458 | |
|
3040 | @command(b'perfbranchmapload', [ | |
|
3459 | ||
|
3460 | @command( | |
|
3461 | b'perfbranchmapload', | |
|
3462 | [ | |
|
3041 | 3463 | (b'f', b'filter', b'', b'Specify repoview filter'), |
|
3042 | 3464 | (b'', b'list', False, b'List brachmap filter caches'), |
|
3043 | 3465 | (b'', b'clear-revlogs', False, b'refresh changelog and manifest'), |
|
3044 | ||
|
3045 |
|
|
|
3466 | ] | |
|
3467 | + formatteropts, | |
|
3468 | ) | |
|
3046 | 3469 | def perfbranchmapload(ui, repo, filter=b'', list=False, **opts): |
|
3047 | 3470 | """benchmark reading the branchmap""" |
|
3048 | 3471 | opts = _byteskwargs(opts) |
@@ -3052,8 +3475,9 b' def perfbranchmapload(ui, repo, filter=b' | |||
|
3052 | 3475 | for name, kind, st in repo.cachevfs.readdir(stat=True): |
|
3053 | 3476 | if name.startswith(b'branch2'): |
|
3054 | 3477 | filtername = name.partition(b'-')[2] or b'unfiltered' |
|
3055 |
ui.status( |
|
|
3056 |
|
|
|
3478 | ui.status( | |
|
3479 | b'%s - %s\n' % (filtername, util.bytecount(st.st_size)) | |
|
3480 | ) | |
|
3057 | 3481 | return |
|
3058 | 3482 | if not filter: |
|
3059 | 3483 | filter = None |
@@ -3076,18 +3500,23 b' def perfbranchmapload(ui, repo, filter=b' | |||
|
3076 | 3500 | while fromfile(repo) is None: |
|
3077 | 3501 | currentfilter = subsettable.get(currentfilter) |
|
3078 | 3502 | if currentfilter is None: |
|
3079 |
raise error.Abort( |
|
|
3080 |
|
|
|
3503 | raise error.Abort( | |
|
3504 | b'No branchmap cached for %s repo' % (filter or b'unfiltered') | |
|
3505 | ) | |
|
3081 | 3506 | repo = repo.filtered(currentfilter) |
|
3082 | 3507 | timer, fm = gettimer(ui, opts) |
|
3508 | ||
|
3083 | 3509 | def setup(): |
|
3084 | 3510 | if clearrevlogs: |
|
3085 | 3511 | clearchangelog(repo) |
|
3512 | ||
|
3086 | 3513 | def bench(): |
|
3087 | 3514 | fromfile(repo) |
|
3515 | ||
|
3088 | 3516 | timer(bench, setup=setup) |
|
3089 | 3517 | fm.end() |
|
3090 | 3518 | |
|
3519 | ||
|
3091 | 3520 | @command(b'perfloadmarkers') |
|
3092 | 3521 | def perfloadmarkers(ui, repo): |
|
3093 | 3522 | """benchmark the time to parse the on-disk markers for a repo |
@@ -3098,18 +3527,39 b' def perfloadmarkers(ui, repo):' | |||
|
3098 | 3527 | timer(lambda: len(obsolete.obsstore(svfs))) |
|
3099 | 3528 | fm.end() |
|
3100 | 3529 | |
|
3101 | @command(b'perflrucachedict', formatteropts + | |
|
3102 | [(b'', b'costlimit', 0, b'maximum total cost of items in cache'), | |
|
3530 | ||
|
3531 | @command( | |
|
3532 | b'perflrucachedict', | |
|
3533 | formatteropts | |
|
3534 | + [ | |
|
3535 | (b'', b'costlimit', 0, b'maximum total cost of items in cache'), | |
|
3103 | 3536 | (b'', b'mincost', 0, b'smallest cost of items in cache'), |
|
3104 | 3537 | (b'', b'maxcost', 100, b'maximum cost of items in cache'), |
|
3105 | 3538 | (b'', b'size', 4, b'size of cache'), |
|
3106 | 3539 | (b'', b'gets', 10000, b'number of key lookups'), |
|
3107 | 3540 | (b'', b'sets', 10000, b'number of key sets'), |
|
3108 | 3541 | (b'', b'mixed', 10000, b'number of mixed mode operations'), |
|
3109 | (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')], | |
|
3110 | norepo=True) | |
|
3111 | def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4, | |
|
3112 | gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts): | |
|
3542 | ( | |
|
3543 | b'', | |
|
3544 | b'mixedgetfreq', | |
|
3545 | 50, | |
|
3546 | b'frequency of get vs set ops in mixed mode', | |
|
3547 | ), | |
|
3548 | ], | |
|
3549 | norepo=True, | |
|
3550 | ) | |
|
3551 | def perflrucache( | |
|
3552 | ui, | |
|
3553 | mincost=0, | |
|
3554 | maxcost=100, | |
|
3555 | costlimit=0, | |
|
3556 | size=4, | |
|
3557 | gets=10000, | |
|
3558 | sets=10000, | |
|
3559 | mixed=10000, | |
|
3560 | mixedgetfreq=50, | |
|
3561 | **opts | |
|
3562 | ): | |
|
3113 | 3563 | opts = _byteskwargs(opts) |
|
3114 | 3564 | |
|
3115 | 3565 | def doinit(): |
@@ -3178,9 +3628,9 b' def perflrucache(ui, mincost=0, maxcost=' | |||
|
3178 | 3628 | else: |
|
3179 | 3629 | op = 1 |
|
3180 | 3630 | |
|
3181 |
mixedops.append( |
|
|
3182 |
|
|
|
3183 | random.choice(costrange))) | |
|
3631 | mixedops.append( | |
|
3632 | (op, random.randint(0, size * 2), random.choice(costrange)) | |
|
3633 | ) | |
|
3184 | 3634 | |
|
3185 | 3635 | def domixed(): |
|
3186 | 3636 | d = util.lrucachedict(size) |
@@ -3211,24 +3661,29 b' def perflrucache(ui, mincost=0, maxcost=' | |||
|
3211 | 3661 | ] |
|
3212 | 3662 | |
|
3213 | 3663 | if costlimit: |
|
3214 |
benches.extend( |
|
|
3664 | benches.extend( | |
|
3665 | [ | |
|
3215 | 3666 | (dogetscost, b'gets w/ cost limit'), |
|
3216 | 3667 | (doinsertscost, b'inserts w/ cost limit'), |
|
3217 | 3668 | (domixedcost, b'mixed w/ cost limit'), |
|
3218 |
] |
|
|
3669 | ] | |
|
3670 | ) | |
|
3219 | 3671 | else: |
|
3220 |
benches.extend( |
|
|
3672 | benches.extend( | |
|
3673 | [ | |
|
3221 | 3674 | (dogets, b'gets'), |
|
3222 | 3675 | (doinserts, b'inserts'), |
|
3223 | 3676 | (dosets, b'sets'), |
|
3224 | (domixed, b'mixed') | |
|
3225 |
] |
|
|
3677 | (domixed, b'mixed'), | |
|
3678 | ] | |
|
3679 | ) | |
|
3226 | 3680 | |
|
3227 | 3681 | for fn, title in benches: |
|
3228 | 3682 | timer, fm = gettimer(ui, opts) |
|
3229 | 3683 | timer(fn, title=title) |
|
3230 | 3684 | fm.end() |
|
3231 | 3685 | |
|
3686 | ||
|
3232 | 3687 | @command(b'perfwrite', formatteropts) |
|
3233 | 3688 | def perfwrite(ui, repo, **opts): |
|
3234 | 3689 | """microbenchmark ui.write |
@@ -3236,15 +3691,19 b' def perfwrite(ui, repo, **opts):' | |||
|
3236 | 3691 | opts = _byteskwargs(opts) |
|
3237 | 3692 | |
|
3238 | 3693 | timer, fm = gettimer(ui, opts) |
|
3694 | ||
|
3239 | 3695 | def write(): |
|
3240 | 3696 | for i in range(100000): |
|
3241 |
ui.write |
|
|
3697 | ui.write(b'Testing write performance\n') | |
|
3698 | ||
|
3242 | 3699 | timer(write) |
|
3243 | 3700 | fm.end() |
|
3244 | 3701 | |
|
3702 | ||
|
3245 | 3703 | def uisetup(ui): |
|
3246 |
if |
|
|
3247 |
|
|
|
3704 | if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr( | |
|
3705 | commands, b'debugrevlogopts' | |
|
3706 | ): | |
|
3248 | 3707 | # for "historical portability": |
|
3249 | 3708 | # In this case, Mercurial should be 1.9 (or a79fea6b3e77) - |
|
3250 | 3709 | # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for |
@@ -3252,15 +3711,24 b' def uisetup(ui):' | |||
|
3252 | 3711 | # available since 3.5 (or 49c583ca48c4). |
|
3253 | 3712 | def openrevlog(orig, repo, cmd, file_, opts): |
|
3254 | 3713 | if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'): |
|
3255 |
raise error.Abort( |
|
|
3256 | hint=b"use 3.5 or later") | |
|
3714 | raise error.Abort( | |
|
3715 | b"This version doesn't support --dir option", | |
|
3716 | hint=b"use 3.5 or later", | |
|
3717 | ) | |
|
3257 | 3718 | return orig(repo, cmd, file_, opts) |
|
3719 | ||
|
3258 | 3720 | extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog) |
|
3259 | 3721 | |
|
3260 | @command(b'perfprogress', formatteropts + [ | |
|
3722 | ||
|
3723 | @command( | |
|
3724 | b'perfprogress', | |
|
3725 | formatteropts | |
|
3726 | + [ | |
|
3261 | 3727 | (b'', b'topic', b'topic', b'topic for progress messages'), |
|
3262 | 3728 | (b'c', b'total', 1000000, b'total value we are progressing to'), |
|
3263 | ], norepo=True) | |
|
3729 | ], | |
|
3730 | norepo=True, | |
|
3731 | ) | |
|
3264 | 3732 | def perfprogress(ui, topic=None, total=None, **opts): |
|
3265 | 3733 | """printing of progress bars""" |
|
3266 | 3734 | opts = _byteskwargs(opts) |
@@ -7,6 +7,7 b' from mercurial import (' | |||
|
7 | 7 | util, |
|
8 | 8 | ) |
|
9 | 9 | |
|
10 | ||
|
10 | 11 | def diffstat(ui, repo, **kwargs): |
|
11 | 12 | '''Example usage: |
|
12 | 13 |
@@ -25,65 +25,103 b' import subprocess' | |||
|
25 | 25 | import sys |
|
26 | 26 | |
|
27 | 27 | _hgenv = dict(os.environ) |
|
28 |
_hgenv.update( |
|
|
29 | 'HGPLAIN': '1', | |
|
30 | }) | |
|
28 | _hgenv.update( | |
|
29 | {'HGPLAIN': '1',} | |
|
30 | ) | |
|
31 | 31 | |
|
32 | 32 | _HG_FIRST_CHANGE = '9117c6561b0bd7792fa13b50d28239d51b78e51f' |
|
33 | 33 | |
|
34 | ||
|
34 | 35 | def _runhg(*args): |
|
35 | 36 | return subprocess.check_output(args, env=_hgenv) |
|
36 | 37 | |
|
38 | ||
|
37 | 39 | def _is_hg_repo(path): |
|
38 | return _runhg('hg', 'log', '-R', path, | |
|
39 |
|
|
|
40 | return ( | |
|
41 | _runhg('hg', 'log', '-R', path, '-r0', '--template={node}').strip() | |
|
42 | == _HG_FIRST_CHANGE | |
|
43 | ) | |
|
44 | ||
|
40 | 45 | |
|
41 | 46 | def _py3default(): |
|
42 | 47 | if sys.version_info[0] >= 3: |
|
43 | 48 | return sys.executable |
|
44 | 49 | return 'python3' |
|
45 | 50 | |
|
51 | ||
|
46 | 52 | def main(argv=()): |
|
47 | 53 | p = argparse.ArgumentParser() |
|
48 |
p.add_argument( |
|
|
49 |
|
|
|
50 | p.add_argument('--commit-to-repo', | |
|
51 | help='If set, commit newly fixed tests to the given repo') | |
|
52 | p.add_argument('-j', default=os.sysconf(r'SC_NPROCESSORS_ONLN'), type=int, | |
|
53 | help='Number of parallel tests to run.') | |
|
54 | p.add_argument('--python3', default=_py3default(), | |
|
55 | help='python3 interpreter to use for test run') | |
|
56 | p.add_argument('--commit-user', | |
|
54 | p.add_argument( | |
|
55 | '--working-tests', help='List of tests that already work in Python 3.' | |
|
56 | ) | |
|
57 | p.add_argument( | |
|
58 | '--commit-to-repo', | |
|
59 | help='If set, commit newly fixed tests to the given repo', | |
|
60 | ) | |
|
61 | p.add_argument( | |
|
62 | '-j', | |
|
63 | default=os.sysconf(r'SC_NPROCESSORS_ONLN'), | |
|
64 | type=int, | |
|
65 | help='Number of parallel tests to run.', | |
|
66 | ) | |
|
67 | p.add_argument( | |
|
68 | '--python3', | |
|
69 | default=_py3default(), | |
|
70 | help='python3 interpreter to use for test run', | |
|
71 | ) | |
|
72 | p.add_argument( | |
|
73 | '--commit-user', | |
|
57 | 74 |
|
|
58 |
|
|
|
75 | help='Username to specify when committing to a repo.', | |
|
76 | ) | |
|
59 | 77 | opts = p.parse_args(argv) |
|
60 | 78 | if opts.commit_to_repo: |
|
61 | 79 | if not _is_hg_repo(opts.commit_to_repo): |
|
62 | 80 | print('abort: specified repository is not the hg repository') |
|
63 | 81 | sys.exit(1) |
|
64 | 82 | if not opts.working_tests or not os.path.isfile(opts.working_tests): |
|
65 | print('abort: --working-tests must exist and be a file (got %r)' % | |
|
66 | opts.working_tests) | |
|
83 | print( | |
|
84 | 'abort: --working-tests must exist and be a file (got %r)' | |
|
85 | % opts.working_tests | |
|
86 | ) | |
|
67 | 87 | sys.exit(1) |
|
68 | 88 | elif opts.commit_to_repo: |
|
69 | 89 | root = _runhg('hg', 'root').strip() |
|
70 | 90 | if not opts.working_tests.startswith(root): |
|
71 | print('abort: if --commit-to-repo is given, ' | |
|
72 | '--working-tests must be from that repo') | |
|
91 | print( | |
|
92 | 'abort: if --commit-to-repo is given, ' | |
|
93 | '--working-tests must be from that repo' | |
|
94 | ) | |
|
73 | 95 | sys.exit(1) |
|
74 | 96 | try: |
|
75 |
subprocess.check_call( |
|
|
97 | subprocess.check_call( | |
|
98 | [ | |
|
99 | opts.python3, | |
|
100 | '-c', | |
|
76 | 101 |
|
|
77 | 102 |
|
|
78 |
|
|
|
103 | 'or sys.version_info >= (3, 6, 2))', | |
|
104 | ] | |
|
105 | ) | |
|
79 | 106 | except subprocess.CalledProcessError: |
|
80 | print('warning: Python 3.6.0 and 3.6.1 have ' | |
|
81 | 'a bug which breaks Mercurial') | |
|
107 | print( | |
|
108 | 'warning: Python 3.6.0 and 3.6.1 have ' | |
|
109 | 'a bug which breaks Mercurial' | |
|
110 | ) | |
|
82 | 111 | print('(see https://bugs.python.org/issue29714 for details)') |
|
83 | 112 | sys.exit(1) |
|
84 | 113 | |
|
85 | rt = subprocess.Popen([opts.python3, 'run-tests.py', '-j', str(opts.j), | |
|
86 | '--blacklist', opts.working_tests, '--json']) | |
|
114 | rt = subprocess.Popen( | |
|
115 | [ | |
|
116 | opts.python3, | |
|
117 | 'run-tests.py', | |
|
118 | '-j', | |
|
119 | str(opts.j), | |
|
120 | '--blacklist', | |
|
121 | opts.working_tests, | |
|
122 | '--json', | |
|
123 | ] | |
|
124 | ) | |
|
87 | 125 | rt.wait() |
|
88 | 126 | with open('report.json') as f: |
|
89 | 127 | data = f.read() |
@@ -104,12 +142,20 b' def main(argv=()):' | |||
|
104 | 142 | with open(opts.working_tests, 'w') as f: |
|
105 | 143 | for p in sorted(oldpass | newpass): |
|
106 | 144 | f.write('%s\n' % p) |
|
107 | _runhg('hg', 'commit', '-R', opts.commit_to_repo, | |
|
108 | '--user', opts.commit_user, | |
|
109 | '--message', 'python3: expand list of passing tests') | |
|
145 | _runhg( | |
|
146 | 'hg', | |
|
147 | 'commit', | |
|
148 | '-R', | |
|
149 | opts.commit_to_repo, | |
|
150 | '--user', | |
|
151 | opts.commit_user, | |
|
152 | '--message', | |
|
153 | 'python3: expand list of passing tests', | |
|
154 | ) | |
|
110 | 155 | else: |
|
111 | 156 | print('Newly passing tests:', '\n'.join(sorted(newpass))) |
|
112 | 157 | sys.exit(2) |
|
113 | 158 | |
|
159 | ||
|
114 | 160 | if __name__ == '__main__': |
|
115 | 161 | main(sys.argv[1:]) |
@@ -16,9 +16,20 b' import re' | |||
|
16 | 16 | import subprocess |
|
17 | 17 | import sys |
|
18 | 18 | |
|
19 | DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last', | |
|
20 | 'reverse', 'reverse+first', 'reverse+last', | |
|
21 | 'sort', 'sort+first', 'sort+last'] | |
|
19 | DEFAULTVARIANTS = [ | |
|
20 | 'plain', | |
|
21 | 'min', | |
|
22 | 'max', | |
|
23 | 'first', | |
|
24 | 'last', | |
|
25 | 'reverse', | |
|
26 | 'reverse+first', | |
|
27 | 'reverse+last', | |
|
28 | 'sort', | |
|
29 | 'sort+first', | |
|
30 | 'sort+last', | |
|
31 | ] | |
|
32 | ||
|
22 | 33 | |
|
23 | 34 | def check_output(*args, **kwargs): |
|
24 | 35 | kwargs.setdefault('stderr', subprocess.PIPE) |
@@ -29,12 +40,14 b' def check_output(*args, **kwargs):' | |||
|
29 | 40 | raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0])) |
|
30 | 41 | return output |
|
31 | 42 | |
|
43 | ||
|
32 | 44 | def update(rev): |
|
33 | 45 | """update the repo to a revision""" |
|
34 | 46 | try: |
|
35 | 47 | subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)]) |
|
36 |
check_output( |
|
|
37 | stderr=None) # suppress output except for error/warning | |
|
48 | check_output( | |
|
49 | ['make', 'local'], stderr=None | |
|
50 | ) # suppress output except for error/warning | |
|
38 | 51 | except subprocess.CalledProcessError as exc: |
|
39 | 52 | print('update to revision %s failed, aborting'%rev, file=sys.stderr) |
|
40 | 53 | sys.exit(exc.returncode) |
@@ -48,11 +61,14 b' def hg(cmd, repo=None):' | |||
|
48 | 61 | fullcmd = ['./hg'] |
|
49 | 62 | if repo is not None: |
|
50 | 63 | fullcmd += ['-R', repo] |
|
51 |
fullcmd += [ |
|
|
52 | 'extensions.perf=' + os.path.join(contribdir, 'perf.py')] | |
|
64 | fullcmd += [ | |
|
65 | '--config', | |
|
66 | 'extensions.perf=' + os.path.join(contribdir, 'perf.py'), | |
|
67 | ] | |
|
53 | 68 | fullcmd += cmd |
|
54 | 69 | return check_output(fullcmd, stderr=subprocess.STDOUT) |
|
55 | 70 | |
|
71 | ||
|
56 | 72 | def perf(revset, target=None, contexts=False): |
|
57 | 73 | """run benchmark for this very revset""" |
|
58 | 74 | try: |
@@ -64,15 +80,21 b' def perf(revset, target=None, contexts=F' | |||
|
64 | 80 | output = hg(args, repo=target) |
|
65 | 81 | return parseoutput(output) |
|
66 | 82 | except subprocess.CalledProcessError as exc: |
|
67 | print('abort: cannot run revset benchmark: %s'%exc.cmd, file=sys.stderr) | |
|
83 | print( | |
|
84 | 'abort: cannot run revset benchmark: %s' % exc.cmd, file=sys.stderr | |
|
85 | ) | |
|
68 | 86 | if getattr(exc, 'output', None) is None: # no output before 2.7 |
|
69 | 87 | print('(no output)', file=sys.stderr) |
|
70 | 88 | else: |
|
71 | 89 | print(exc.output, file=sys.stderr) |
|
72 | 90 | return None |
|
73 | 91 | |
|
74 | outputre = re.compile(br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) ' | |
|
75 | br'sys (\d+.\d+) \(best of (\d+)\)') | |
|
92 | ||
|
93 | outputre = re.compile( | |
|
94 | br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) ' | |
|
95 | br'sys (\d+.\d+) \(best of (\d+)\)' | |
|
96 | ) | |
|
97 | ||
|
76 | 98 | |
|
77 | 99 | def parseoutput(output): |
|
78 | 100 | """parse a textual output into a dict |
@@ -85,20 +107,30 b' def parseoutput(output):' | |||
|
85 | 107 | print('abort: invalid output:', file=sys.stderr) |
|
86 | 108 | print(output, file=sys.stderr) |
|
87 | 109 | sys.exit(1) |
|
88 | return {'comb': float(match.group(2)), | |
|
110 | return { | |
|
111 | 'comb': float(match.group(2)), | |
|
89 | 112 |
|
|
90 | 113 |
|
|
91 | 114 |
|
|
92 | 115 |
|
|
93 | 116 | } |
|
94 | 117 | |
|
118 | ||
|
95 | 119 | def printrevision(rev): |
|
96 | 120 | """print data about a revision""" |
|
97 | 121 | sys.stdout.write("Revision ") |
|
98 | 122 | sys.stdout.flush() |
|
99 | subprocess.check_call(['hg', 'log', '--rev', str(rev), '--template', | |
|
100 | '{if(tags, " ({tags})")} ' | |
|
101 | '{rev}:{node|short}: {desc|firstline}\n']) | |
|
123 | subprocess.check_call( | |
|
124 | [ | |
|
125 | 'hg', | |
|
126 | 'log', | |
|
127 | '--rev', | |
|
128 | str(rev), | |
|
129 | '--template', | |
|
130 | '{if(tags, " ({tags})")} ' '{rev}:{node|short}: {desc|firstline}\n', | |
|
131 | ] | |
|
132 | ) | |
|
133 | ||
|
102 | 134 | |
|
103 | 135 | def idxwidth(nbidx): |
|
104 | 136 | """return the max width of number used for index |
@@ -116,6 +148,7 b' def idxwidth(nbidx):' | |||
|
116 | 148 | idxwidth = 1 |
|
117 | 149 | return idxwidth |
|
118 | 150 | |
|
151 | ||
|
119 | 152 | def getfactor(main, other, field, sensitivity=0.05): |
|
120 | 153 | """return the relative factor between values for 'field' in main and other |
|
121 | 154 | |
@@ -125,10 +158,11 b' def getfactor(main, other, field, sensit' | |||
|
125 | 158 | if main is not None: |
|
126 | 159 | factor = other[field] / main[field] |
|
127 | 160 | low, high = 1 - sensitivity, 1 + sensitivity |
|
128 |
if |
|
|
161 | if low < factor < high: | |
|
129 | 162 | return None |
|
130 | 163 | return factor |
|
131 | 164 | |
|
165 | ||
|
132 | 166 | def formatfactor(factor): |
|
133 | 167 | """format a factor into a 4 char string |
|
134 | 168 | |
@@ -155,6 +189,7 b' def formatfactor(factor):' | |||
|
155 | 189 | factor //= 0 |
|
156 | 190 | return 'x%ix%i' % (factor, order) |
|
157 | 191 | |
|
192 | ||
|
158 | 193 | def formattiming(value): |
|
159 | 194 | """format a value to strictly 8 char, dropping some precision if needed""" |
|
160 | 195 | if value < 10**7: |
@@ -163,7 +198,10 b' def formattiming(value):' | |||
|
163 | 198 | # value is HUGE very unlikely to happen (4+ month run) |
|
164 | 199 | return '%i' % value |
|
165 | 200 | |
|
201 | ||
|
166 | 202 | _marker = object() |
|
203 | ||
|
204 | ||
|
167 | 205 | def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker): |
|
168 | 206 | """print a line of result to stdout""" |
|
169 | 207 | mask = '%%0%ii) %%s' % idxwidth(maxidx) |
@@ -187,6 +225,7 b' def printresult(variants, idx, data, max' | |||
|
187 | 225 |
out.append('%6d' |
|
188 | 226 | print(mask % (idx, ' '.join(out))) |
|
189 | 227 | |
|
228 | ||
|
190 | 229 | def printheader(variants, maxidx, verbose=False, relative=False): |
|
191 | 230 | header = [' ' * (idxwidth(maxidx) + 1)] |
|
192 | 231 | for var in variants: |
@@ -204,6 +243,7 b' def printheader(variants, maxidx, verbos' | |||
|
204 | 243 | header.append('%6s' % 'count') |
|
205 | 244 | print(' '.join(header)) |
|
206 | 245 | |
|
246 | ||
|
207 | 247 | def getrevs(spec): |
|
208 | 248 | """get the list of rev matched by a revset""" |
|
209 | 249 | try: |
@@ -221,31 +261,44 b' def applyvariants(revset, variant):' | |||
|
221 | 261 | revset = '%s(%s)' % (var, revset) |
|
222 | 262 | return revset |
|
223 | 263 | |
|
264 | ||
|
224 | 265 | helptext="""This script will run multiple variants of provided revsets using |
|
225 | 266 | different revisions in your mercurial repository. After the benchmark are run |
|
226 | 267 | summary output is provided. Use it to demonstrate speed improvements or pin |
|
227 | 268 | point regressions. Revsets to run are specified in a file (or from stdin), one |
|
228 | 269 | revsets per line. Line starting with '#' will be ignored, allowing insertion of |
|
229 | 270 | comments.""" |
|
230 |
parser = optparse.OptionParser( |
|
|
231 | description=helptext) | |
|
232 | parser.add_option("-f", "--file", | |
|
271 | parser = optparse.OptionParser( | |
|
272 | usage="usage: %prog [options] <revs>", description=helptext | |
|
273 | ) | |
|
274 | parser.add_option( | |
|
275 | "-f", | |
|
276 | "--file", | |
|
233 | 277 |
|
|
234 |
|
|
|
235 | parser.add_option("-R", "--repo", | |
|
236 |
|
|
|
278 | metavar="FILE", | |
|
279 | ) | |
|
280 | parser.add_option("-R", "--repo", help="run benchmark on REPO", metavar="REPO") | |
|
237 | 281 | |
|
238 |
parser.add_option( |
|
|
282 | parser.add_option( | |
|
283 | "-v", | |
|
284 | "--verbose", | |
|
239 | 285 |
|
|
240 |
|
|
|
286 | help="display all timing data (not just best total time)", | |
|
287 | ) | |
|
241 | 288 | |
|
242 |
parser.add_option( |
|
|
289 | parser.add_option( | |
|
290 | "", | |
|
291 | "--variants", | |
|
243 | 292 |
|
|
244 | 293 |
|
|
245 |
|
|
|
246 | parser.add_option('', '--contexts', | |
|
294 | "(eg: plain,min,sorted) (plain = no modification)", | |
|
295 | ) | |
|
296 | parser.add_option( | |
|
297 | '', | |
|
298 | '--contexts', | |
|
247 | 299 |
|
|
248 |
|
|
|
300 | help='obtain changectx from results instead of integer revs', | |
|
301 | ) | |
|
249 | 302 | |
|
250 | 303 | (options, args) = parser.parse_args() |
|
251 | 304 | |
@@ -294,17 +347,20 b' for r in revs:' | |||
|
294 | 347 | data = perf(varrset, target=options.repo, contexts=options.contexts) |
|
295 | 348 | varres[var] = data |
|
296 | 349 | res.append(varres) |
|
297 | printresult(variants, idx, varres, len(revsets), | |
|
298 |
|
|
|
350 | printresult( | |
|
351 | variants, idx, varres, len(revsets), verbose=options.verbose | |
|
352 | ) | |
|
299 | 353 | sys.stdout.flush() |
|
300 | 354 | print("----------------------------") |
|
301 | 355 | |
|
302 | 356 | |
|
303 |
print( |
|
|
357 | print( | |
|
358 | """ | |
|
304 | 359 |
|
|
305 | 360 |
|
|
306 | 361 |
|
|
307 |
|
|
|
362 | """ | |
|
363 | ) | |
|
308 | 364 | |
|
309 | 365 | print('Revision:') |
|
310 | 366 | for idx, rev in enumerate(revs): |
@@ -321,7 +377,13 b' for ridx, rset in enumerate(revsets):' | |||
|
321 | 377 | printheader(variants, len(results), verbose=options.verbose, relative=True) |
|
322 | 378 | ref = None |
|
323 | 379 | for idx, data in enumerate(results): |
|
324 | printresult(variants, idx, data[ridx], len(results), | |
|
325 | verbose=options.verbose, reference=ref) | |
|
380 | printresult( | |
|
381 | variants, | |
|
382 | idx, | |
|
383 | data[ridx], | |
|
384 | len(results), | |
|
385 | verbose=options.verbose, | |
|
386 | reference=ref, | |
|
387 | ) | |
|
326 | 388 | ref = data[ridx] |
|
327 | 389 | print() |
@@ -9,16 +9,19 b' import signal' | |||
|
9 | 9 | import sys |
|
10 | 10 | import traceback |
|
11 | 11 | |
|
12 | ||
|
12 | 13 | def sigshow(*args): |
|
13 | 14 | sys.stderr.write("\n") |
|
14 | 15 | traceback.print_stack(args[1], limit=10, file=sys.stderr) |
|
15 | 16 | sys.stderr.write("----\n") |
|
16 | 17 | |
|
18 | ||
|
17 | 19 | def sigexit(*args): |
|
18 | 20 | sigshow(*args) |
|
19 | 21 | print('alarm!') |
|
20 | 22 | sys.exit(1) |
|
21 | 23 | |
|
24 | ||
|
22 | 25 | def extsetup(ui): |
|
23 | 26 | signal.signal(signal.SIGQUIT, sigshow) |
|
24 | 27 | signal.signal(signal.SIGALRM, sigexit) |
@@ -62,9 +62,7 b' from mercurial import (' | |||
|
62 | 62 | registrar, |
|
63 | 63 | scmutil, |
|
64 | 64 | ) |
|
65 |
from mercurial.utils import |
|
|
66 | dateutil, | |
|
67 | ) | |
|
65 | from mercurial.utils import dateutil | |
|
68 | 66 | |
|
69 | 67 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
|
70 | 68 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
@@ -77,14 +75,17 b' command = registrar.command(cmdtable)' | |||
|
77 | 75 | |
|
78 | 76 | newfile = {'new fi', 'rename', 'copy f', 'copy t'} |
|
79 | 77 | |
|
78 | ||
|
80 | 79 | def zerodict(): |
|
81 | 80 | return collections.defaultdict(lambda: 0) |
|
82 | 81 | |
|
82 | ||
|
83 | 83 | def roundto(x, k): |
|
84 | 84 | if x > k * 2: |
|
85 | 85 | return int(round(x / float(k)) * k) |
|
86 | 86 | return int(round(x)) |
|
87 | 87 | |
|
88 | ||
|
88 | 89 | def parsegitdiff(lines): |
|
89 | 90 | filename, mar, lineadd, lineremove = None, None, zerodict(), 0 |
|
90 | 91 | binary = False |
@@ -110,10 +111,16 b' def parsegitdiff(lines):' | |||
|
110 | 111 | if filename: |
|
111 | 112 | yield filename, mar, lineadd, lineremove, binary |
|
112 | 113 | |
|
113 | @command('analyze', | |
|
114 | [('o', 'output', '', _('write output to given file'), _('FILE')), | |
|
115 | ('r', 'rev', [], _('analyze specified revisions'), _('REV'))], | |
|
116 | _('hg analyze'), optionalrepo=True) | |
|
114 | ||
|
115 | @command( | |
|
116 | 'analyze', | |
|
117 | [ | |
|
118 | ('o', 'output', '', _('write output to given file'), _('FILE')), | |
|
119 | ('r', 'rev', [], _('analyze specified revisions'), _('REV')), | |
|
120 | ], | |
|
121 | _('hg analyze'), | |
|
122 | optionalrepo=True, | |
|
123 | ) | |
|
117 | 124 | def analyze(ui, repo, *revs, **opts): |
|
118 | 125 | '''create a simple model of a repository to use for later synthesis |
|
119 | 126 | |
@@ -176,8 +183,9 b' def analyze(ui, repo, *revs, **opts):' | |||
|
176 | 183 | revs = scmutil.revrange(repo, revs) |
|
177 | 184 | revs.sort() |
|
178 | 185 | |
|
179 |
progress = ui.makeprogress( |
|
|
180 | total=len(revs)) | |
|
186 | progress = ui.makeprogress( | |
|
187 | _('analyzing'), unit=_('changesets'), total=len(revs) | |
|
188 | ) | |
|
181 | 189 | for i, rev in enumerate(revs): |
|
182 | 190 | progress.update(i) |
|
183 | 191 | ctx = repo[rev] |
@@ -198,8 +206,9 b' def analyze(ui, repo, *revs, **opts):' | |||
|
198 | 206 | timedelta = ctx.date()[0] - lastctx.date()[0] |
|
199 | 207 | interarrival[roundto(timedelta, 300)] += 1 |
|
200 | 208 | diffopts = diffutil.diffallopts(ui, {'git': True}) |
|
201 |
diff = sum( |
|
|
202 |
|
|
|
209 | diff = sum( | |
|
210 | (d.splitlines() for d in ctx.diff(pctx, opts=diffopts)), [] | |
|
211 | ) | |
|
203 | 212 | fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0 |
|
204 | 213 | for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff): |
|
205 | 214 | if isbin: |
@@ -207,8 +216,9 b' def analyze(ui, repo, *revs, **opts):' | |||
|
207 | 216 | added = sum(lineadd.itervalues(), 0) |
|
208 | 217 | if mar == 'm': |
|
209 | 218 | if added and lineremove: |
|
210 |
lineschanged[ |
|
|
211 |
|
|
|
219 | lineschanged[ | |
|
220 | roundto(added, 5), roundto(lineremove, 5) | |
|
221 | ] += 1 | |
|
212 | 222 | filechanges += 1 |
|
213 | 223 | elif mar == 'a': |
|
214 | 224 | fileadds += 1 |
@@ -238,7 +248,9 b' def analyze(ui, repo, *revs, **opts):' | |||
|
238 | 248 | def pronk(d): |
|
239 | 249 | return sorted(d.iteritems(), key=lambda x: x[1], reverse=True) |
|
240 | 250 | |
|
241 | json.dump({'revs': len(revs), | |
|
251 | json.dump( | |
|
252 | { | |
|
253 | 'revs': len(revs), | |
|
242 | 254 |
|
|
243 | 255 |
|
|
244 | 256 |
|
@@ -254,14 +266,20 b' def analyze(ui, repo, *revs, **opts):' | |||
|
254 | 266 |
|
|
255 | 267 |
|
|
256 | 268 |
|
|
257 |
|
|
|
269 | fp, | |
|
270 | ) | |
|
258 | 271 | fp.close() |
|
259 | 272 | |
|
260 | @command('synthesize', | |
|
261 | [('c', 'count', 0, _('create given number of commits'), _('COUNT')), | |
|
273 | ||
|
274 | @command( | |
|
275 | 'synthesize', | |
|
276 | [ | |
|
277 | ('c', 'count', 0, _('create given number of commits'), _('COUNT')), | |
|
262 | 278 |
|
|
263 |
|
|
|
264 | _('hg synthesize [OPTION].. DESCFILE')) | |
|
279 | ('', 'initfiles', 0, _('initial file count to create'), _('COUNT')), | |
|
280 | ], | |
|
281 | _('hg synthesize [OPTION].. DESCFILE'), | |
|
282 | ) | |
|
265 | 283 | def synthesize(ui, repo, descpath, **opts): |
|
266 | 284 | '''synthesize commits based on a model of an existing repository |
|
267 | 285 | |
@@ -384,16 +402,23 b' def synthesize(ui, repo, descpath, **opt' | |||
|
384 | 402 | |
|
385 | 403 | progress.complete() |
|
386 | 404 | message = 'synthesized wide repo with %d files' % (len(files),) |
|
387 |
mc = context.memctx( |
|
|
388 | files, filectxfn, ui.username(), | |
|
389 | '%d %d' % dateutil.makedate()) | |
|
405 | mc = context.memctx( | |
|
406 | repo, | |
|
407 | [pctx.node(), nullid], | |
|
408 | message, | |
|
409 | files, | |
|
410 | filectxfn, | |
|
411 | ui.username(), | |
|
412 | '%d %d' % dateutil.makedate(), | |
|
413 | ) | |
|
390 | 414 | initnode = mc.commit() |
|
391 | 415 | if ui.debugflag: |
|
392 | 416 | hexfn = hex |
|
393 | 417 | else: |
|
394 | 418 | hexfn = short |
|
395 | ui.status(_('added commit %s with %d files\n') | |
|
396 |
% (hexfn(initnode), len(files)) |
|
|
419 | ui.status( | |
|
420 | _('added commit %s with %d files\n') % (hexfn(initnode), len(files)) | |
|
421 | ) | |
|
397 | 422 | |
|
398 | 423 | # Synthesize incremental revisions to the repository, adding repo depth. |
|
399 | 424 | count = int(opts['count']) |
@@ -437,8 +462,11 b' def synthesize(ui, repo, descpath, **opt' | |||
|
437 | 462 | for __ in pycompat.xrange(10): |
|
438 | 463 | fctx = pctx.filectx(random.choice(mfk)) |
|
439 | 464 | path = fctx.path() |
|
440 | if not (path in nevertouch or fctx.isbinary() or | |
|
441 |
|
|
|
465 | if not ( | |
|
466 | path in nevertouch | |
|
467 | or fctx.isbinary() | |
|
468 | or 'l' in fctx.flags() | |
|
469 | ): | |
|
442 | 470 | break |
|
443 | 471 | lines = fctx.data().splitlines() |
|
444 | 472 | add, remove = pick(lineschanged) |
@@ -466,14 +494,20 b' def synthesize(ui, repo, descpath, **opt' | |||
|
466 | 494 | path.append(random.choice(words)) |
|
467 | 495 | path.append(random.choice(words)) |
|
468 | 496 | pathstr = '/'.join(filter(None, path)) |
|
469 |
data = |
|
|
497 | data = ( | |
|
498 | '\n'.join( | |
|
470 | 499 | makeline() |
|
471 |
for __ in pycompat.xrange(pick(linesinfilesadded)) |
|
|
500 | for __ in pycompat.xrange(pick(linesinfilesadded)) | |
|
501 | ) | |
|
502 | + '\n' | |
|
503 | ) | |
|
472 | 504 | changes[pathstr] = data |
|
505 | ||
|
473 | 506 | def filectxfn(repo, memctx, path): |
|
474 | 507 | if path not in changes: |
|
475 | 508 | return None |
|
476 | 509 | return context.memfilectx(repo, memctx, path, changes[path]) |
|
510 | ||
|
477 | 511 | if not changes: |
|
478 | 512 | continue |
|
479 | 513 | if revs: |
@@ -481,11 +515,17 b' def synthesize(ui, repo, descpath, **opt' | |||
|
481 | 515 | else: |
|
482 | 516 | date = time.time() - (86400 * count) |
|
483 | 517 | # dates in mercurial must be positive, fit in 32-bit signed integers. |
|
484 |
date = min(0x7 |
|
|
518 | date = min(0x7FFFFFFF, max(0, date)) | |
|
485 | 519 | user = random.choice(words) + '@' + random.choice(words) |
|
486 |
mc = context.memctx( |
|
|
520 | mc = context.memctx( | |
|
521 | repo, | |
|
522 | pl, | |
|
523 | makeline(minimum=2), | |
|
487 | 524 |
|
|
488 | filectxfn, user, '%d %d' % (date, pick(tzoffset))) | |
|
525 | filectxfn, | |
|
526 | user, | |
|
527 | '%d %d' % (date, pick(tzoffset)), | |
|
528 | ) | |
|
489 | 529 | newnode = mc.commit() |
|
490 | 530 | heads.add(repo.changelog.rev(newnode)) |
|
491 | 531 | heads.discard(r1) |
@@ -495,10 +535,12 b' def synthesize(ui, repo, descpath, **opt' | |||
|
495 | 535 | lock.release() |
|
496 | 536 | wlock.release() |
|
497 | 537 | |
|
538 | ||
|
498 | 539 | def renamedirs(dirs, words): |
|
499 | 540 | '''Randomly rename the directory names in the per-dir file count dict.''' |
|
500 | 541 | wordgen = itertools.cycle(words) |
|
501 | 542 | replacements = {'': ''} |
|
543 | ||
|
502 | 544 | def rename(dirpath): |
|
503 | 545 | '''Recursively rename the directory and all path prefixes. |
|
504 | 546 | |
@@ -516,6 +558,7 b' def renamedirs(dirs, words):' | |||
|
516 | 558 | renamed = os.path.join(head, next(wordgen)) |
|
517 | 559 | replacements[dirpath] = renamed |
|
518 | 560 | return renamed |
|
561 | ||
|
519 | 562 | result = [] |
|
520 | 563 | for dirpath, count in dirs.iteritems(): |
|
521 | 564 | result.append([rename(dirpath.lstrip(os.sep)), count]) |
@@ -14,11 +14,13 b' import sys' | |||
|
14 | 14 | #################### |
|
15 | 15 | # for Python3 compatibility (almost comes from mercurial/pycompat.py) |
|
16 | 16 | |
|
17 |
ispy3 = |
|
|
17 | ispy3 = sys.version_info[0] >= 3 | |
|
18 | ||
|
18 | 19 | |
|
19 | 20 | def identity(a): |
|
20 | 21 | return a |
|
21 | 22 | |
|
23 | ||
|
22 | 24 | def _rapply(f, xs): |
|
23 | 25 | if xs is None: |
|
24 | 26 | # assume None means non-value of optional data |
@@ -29,12 +31,14 b' def _rapply(f, xs):' | |||
|
29 | 31 | return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items()) |
|
30 | 32 | return f(xs) |
|
31 | 33 | |
|
34 | ||
|
32 | 35 | def rapply(f, xs): |
|
33 | 36 | if f is identity: |
|
34 | 37 | # fast path mainly for py2 |
|
35 | 38 | return xs |
|
36 | 39 | return _rapply(f, xs) |
|
37 | 40 | |
|
41 | ||
|
38 | 42 | if ispy3: |
|
39 | 43 | import builtins |
|
40 | 44 | |
@@ -49,29 +53,37 b' if ispy3:' | |||
|
49 | 53 | |
|
50 | 54 | def opentext(f): |
|
51 | 55 | return open(f, 'r') |
|
56 | ||
|
57 | ||
|
52 | 58 | else: |
|
53 | 59 | bytestr = str |
|
54 | 60 | sysstr = identity |
|
55 | 61 | |
|
56 | 62 | opentext = open |
|
57 | 63 | |
|
64 | ||
|
58 | 65 | def b2s(x): |
|
59 | 66 | # convert BYTES elements in "x" to SYSSTR recursively |
|
60 | 67 | return rapply(sysstr, x) |
|
61 | 68 | |
|
69 | ||
|
62 | 70 | def writeout(data): |
|
63 | 71 | # write "data" in BYTES into stdout |
|
64 | 72 | sys.stdout.write(data) |
|
65 | 73 | |
|
74 | ||
|
66 | 75 | def writeerr(data): |
|
67 | 76 | # write "data" in BYTES into stderr |
|
68 | 77 | sys.stderr.write(data) |
|
69 | 78 | |
|
79 | ||
|
70 | 80 | #################### |
|
71 | 81 | |
|
82 | ||
|
72 | 83 | class embeddedmatcher(object): |
|
73 | 84 | """Base class to detect embedded code fragments in *.t test script |
|
74 | 85 | """ |
|
86 | ||
|
75 | 87 | __metaclass__ = abc.ABCMeta |
|
76 | 88 | |
|
77 | 89 | def __init__(self, desc): |
@@ -126,6 +138,7 b' class embeddedmatcher(object):' | |||
|
126 | 138 | def codeinside(self, ctx, line): |
|
127 | 139 | """Return actual code at line inside embedded code""" |
|
128 | 140 | |
|
141 | ||
|
129 | 142 | def embedded(basefile, lines, errors, matchers): |
|
130 | 143 | """pick embedded code fragments up from given lines |
|
131 | 144 | |
@@ -185,8 +198,10 b' def embedded(basefile, lines, errors, ma' | |||
|
185 | 198 | elif not matcher.isinside(ctx, line): |
|
186 | 199 | # this is an error of basefile |
|
187 | 200 | # (if matchers are implemented correctly) |
|
188 |
errors.append( |
|
|
189 | % (basefile, lineno, matcher.desc)) | |
|
201 | errors.append( | |
|
202 | '%s:%d: unexpected line for "%s"' | |
|
203 | % (basefile, lineno, matcher.desc) | |
|
204 | ) | |
|
190 | 205 | # stop extracting embedded code by current 'matcher', |
|
191 | 206 | # because appearance of unexpected line might mean |
|
192 | 207 | # that expected end-of-embedded-code line might never |
@@ -208,10 +223,14 b' def embedded(basefile, lines, errors, ma' | |||
|
208 | 223 | if matched: |
|
209 | 224 | if len(matched) > 1: |
|
210 | 225 | # this is an error of matchers, maybe |
|
211 |
errors.append( |
|
|
212 | (basefile, lineno, | |
|
213 | ', '.join(['"%s"' % m.desc | |
|
214 | for m, c in matched]))) | |
|
226 | errors.append( | |
|
227 | '%s:%d: ambiguous line for %s' | |
|
228 | % ( | |
|
229 | basefile, | |
|
230 | lineno, | |
|
231 | ', '.join(['"%s"' % m.desc for m, c in matched]), | |
|
232 | ) | |
|
233 | ) | |
|
215 | 234 | # omit extracting embedded code, because choosing |
|
216 | 235 | # arbitrary matcher from matched ones might fail to |
|
217 | 236 | # detect the end of embedded code as expected. |
@@ -238,8 +257,11 b' def embedded(basefile, lines, errors, ma' | |||
|
238 | 257 | else: |
|
239 | 258 | # this is an error of basefile |
|
240 | 259 | # (if matchers are implemented correctly) |
|
241 | errors.append('%s:%d: unexpected end of file for "%s"' | |
|
242 | % (basefile, lineno, matcher.desc)) | |
|
260 | errors.append( | |
|
261 | '%s:%d: unexpected end of file for "%s"' | |
|
262 | % (basefile, lineno, matcher.desc) | |
|
263 | ) | |
|
264 | ||
|
243 | 265 | |
|
244 | 266 | # heredoc limit mark to ignore embedded code at check-code.py or so |
|
245 | 267 | heredocignorelimit = 'NO_CHECK_EOF' |
@@ -252,6 +274,7 b" heredocignorelimit = 'NO_CHECK_EOF'" | |||
|
252 | 274 | # - << 'LIMITMARK' |
|
253 | 275 | heredoclimitpat = r'\s*<<\s*(?P<lquote>["\']?)(?P<limit>\w+)(?P=lquote)' |
|
254 | 276 | |
|
277 | ||
|
255 | 278 | class fileheredocmatcher(embeddedmatcher): |
|
256 | 279 | """Detect "cat > FILE << LIMIT" style embedded code |
|
257 | 280 | |
@@ -290,6 +313,7 b' class fileheredocmatcher(embeddedmatcher' | |||
|
290 | 313 | >>> matcher.ignores(ctx) |
|
291 | 314 | True |
|
292 | 315 | """ |
|
316 | ||
|
293 | 317 | _prefix = ' > ' |
|
294 | 318 | |
|
295 | 319 | def __init__(self, desc, namepat): |
@@ -302,8 +326,9 b' class fileheredocmatcher(embeddedmatcher' | |||
|
302 | 326 | # - > NAMEPAT |
|
303 | 327 | # - > "NAMEPAT" |
|
304 | 328 | # - > 'NAMEPAT' |
|
305 | namepat = (r'\s*>>?\s*(?P<nquote>["\']?)(?P<name>%s)(?P=nquote)' | |
|
306 | % namepat) | |
|
329 | namepat = ( | |
|
330 | r'\s*>>?\s*(?P<nquote>["\']?)(?P<name>%s)(?P=nquote)' % namepat | |
|
331 | ) | |
|
307 | 332 | self._fileres = [ |
|
308 | 333 | # "cat > NAME << LIMIT" case |
|
309 | 334 | re.compile(r' \$ \s*cat' + namepat + heredoclimitpat), |
@@ -316,8 +341,10 b' class fileheredocmatcher(embeddedmatcher' | |||
|
316 | 341 | for filere in self._fileres: |
|
317 | 342 | matched = filere.match(line) |
|
318 | 343 | if matched: |
|
319 |
return ( |
|
|
320 |
|
|
|
344 | return ( | |
|
345 | matched.group('name'), | |
|
346 | ' > %s\n' % matched.group('limit'), | |
|
347 | ) | |
|
321 | 348 | |
|
322 | 349 | def endsat(self, ctx, line): |
|
323 | 350 | return ctx[1] == line |
@@ -340,9 +367,11 b' class fileheredocmatcher(embeddedmatcher' | |||
|
340 | 367 | def codeinside(self, ctx, line): |
|
341 | 368 | return line[len(self._prefix):] # strip prefix |
|
342 | 369 | |
|
370 | ||
|
343 | 371 | #### |
|
344 | 372 | # for embedded python script |
|
345 | 373 | |
|
374 | ||
|
346 | 375 | class pydoctestmatcher(embeddedmatcher): |
|
347 | 376 | """Detect ">>> code" style embedded python code |
|
348 | 377 | |
@@ -395,6 +424,7 b' class pydoctestmatcher(embeddedmatcher):' | |||
|
395 | 424 | True |
|
396 | 425 | >>> matcher.codeatend(ctx, end) |
|
397 | 426 | """ |
|
427 | ||
|
398 | 428 | _prefix = ' >>> ' |
|
399 | 429 | _prefixre = re.compile(r' (>>>|\.\.\.) ') |
|
400 | 430 | |
@@ -438,6 +468,7 b' class pydoctestmatcher(embeddedmatcher):' | |||
|
438 | 468 | return line[len(self._prefix):] # strip prefix ' >>> '/' ... ' |
|
439 | 469 | return '\n' # an expected output line is treated as an empty line |
|
440 | 470 | |
|
471 | ||
|
441 | 472 | class pyheredocmatcher(embeddedmatcher): |
|
442 | 473 | """Detect "python << LIMIT" style embedded python code |
|
443 | 474 | |
@@ -474,10 +505,12 b' class pyheredocmatcher(embeddedmatcher):' | |||
|
474 | 505 | >>> matcher.ignores(ctx) |
|
475 | 506 | True |
|
476 | 507 | """ |
|
508 | ||
|
477 | 509 | _prefix = ' > ' |
|
478 | 510 | |
|
479 | _startre = re.compile(r' \$ (\$PYTHON|"\$PYTHON"|python).*' + | |
|
480 | heredoclimitpat) | |
|
511 | _startre = re.compile( | |
|
512 | r' \$ (\$PYTHON|"\$PYTHON"|python).*' + heredoclimitpat | |
|
513 | ) | |
|
481 | 514 | |
|
482 | 515 | def __init__(self): |
|
483 | 516 | super(pyheredocmatcher, self).__init__("heredoc python invocation") |
@@ -509,6 +542,7 b' class pyheredocmatcher(embeddedmatcher):' | |||
|
509 | 542 | def codeinside(self, ctx, line): |
|
510 | 543 | return line[len(self._prefix):] # strip prefix |
|
511 | 544 | |
|
545 | ||
|
512 | 546 | _pymatchers = [ |
|
513 | 547 | pydoctestmatcher(), |
|
514 | 548 | pyheredocmatcher(), |
@@ -517,9 +551,11 b' class pyheredocmatcher(embeddedmatcher):' | |||
|
517 | 551 | fileheredocmatcher('heredoc .py file', r'[^<]+\.py'), |
|
518 | 552 | ] |
|
519 | 553 | |
|
554 | ||
|
520 | 555 | def pyembedded(basefile, lines, errors): |
|
521 | 556 | return embedded(basefile, lines, errors, _pymatchers) |
|
522 | 557 | |
|
558 | ||
|
523 | 559 | #### |
|
524 | 560 | # for embedded shell script |
|
525 | 561 | |
@@ -529,22 +565,27 b' def pyembedded(basefile, lines, errors):' | |||
|
529 | 565 | fileheredocmatcher('heredoc .sh file', r'[^<]+\.sh'), |
|
530 | 566 | ] |
|
531 | 567 | |
|
568 | ||
|
532 | 569 | def shembedded(basefile, lines, errors): |
|
533 | 570 | return embedded(basefile, lines, errors, _shmatchers) |
|
534 | 571 | |
|
572 | ||
|
535 | 573 | #### |
|
536 | 574 | # for embedded hgrc configuration |
|
537 | 575 | |
|
538 | 576 | _hgrcmatchers = [ |
|
539 | 577 | # use '[^<]+' instead of '\S+', in order to match against |
|
540 | 578 | # paths including whitespaces |
|
541 |
fileheredocmatcher( |
|
|
542 |
|
|
|
579 | fileheredocmatcher( | |
|
580 | 'heredoc hgrc file', r'(([^/<]+/)+hgrc|\$HGRCPATH|\${HGRCPATH})' | |
|
581 | ), | |
|
543 | 582 | ] |
|
544 | 583 | |
|
584 | ||
|
545 | 585 | def hgrcembedded(basefile, lines, errors): |
|
546 | 586 | return embedded(basefile, lines, errors, _hgrcmatchers) |
|
547 | 587 | |
|
588 | ||
|
548 | 589 | #### |
|
549 | 590 | |
|
550 | 591 | if __name__ == "__main__": |
@@ -558,8 +599,7 b' if __name__ == "__main__":' | |||
|
558 | 599 | name = '<anonymous>' |
|
559 | 600 | writeout("%s:%d: %s starts\n" % (basefile, starts, name)) |
|
560 | 601 | if opts.verbose and code: |
|
561 | writeout(" |%s\n" % | |
|
562 | "\n |".join(l for l in code.splitlines())) | |
|
602 | writeout(" |%s\n" % "\n |".join(l for l in code.splitlines())) | |
|
563 | 603 | writeout("%s:%d: %s ends\n" % (basefile, ends, name)) |
|
564 | 604 | for e in errors: |
|
565 | 605 | writeerr("%s\n" % e) |
@@ -579,9 +619,11 b' if __name__ == "__main__":' | |||
|
579 | 619 | return ret |
|
580 | 620 | |
|
581 | 621 | commands = {} |
|
622 | ||
|
582 | 623 | def command(name, desc): |
|
583 | 624 | def wrap(func): |
|
584 | 625 | commands[name] = (desc, func) |
|
626 | ||
|
585 | 627 | return wrap |
|
586 | 628 | |
|
587 | 629 | @command("pyembedded", "detect embedded python script") |
@@ -596,21 +638,29 b' if __name__ == "__main__":' | |||
|
596 | 638 | def hgrcembeddedcmd(args, opts): |
|
597 | 639 | return applyembedded(args, hgrcembedded, opts) |
|
598 | 640 | |
|
599 |
availablecommands = "\n".join( |
|
|
600 |
|
|
|
641 | availablecommands = "\n".join( | |
|
642 | [" - %s: %s" % (key, value[0]) for key, value in commands.items()] | |
|
643 | ) | |
|
601 | 644 | |
|
602 |
parser = optparse.OptionParser( |
|
|
645 | parser = optparse.OptionParser( | |
|
646 | """%prog COMMAND [file ...] | |
|
603 | 647 |
|
|
604 | 648 |
|
|
605 | 649 |
|
|
606 | 650 | ("FILENAME:LINENO:"). |
|
607 | 651 |
|
|
608 | 652 |
|
|
609 | """ + availablecommands + """ | |
|
610 | """) | |
|
611 | parser.add_option("-v", "--verbose", | |
|
653 | """ | |
|
654 | + availablecommands | |
|
655 | + """ | |
|
656 | """ | |
|
657 | ) | |
|
658 | parser.add_option( | |
|
659 | "-v", | |
|
660 | "--verbose", | |
|
612 | 661 |
|
|
613 |
|
|
|
662 | action="store_true", | |
|
663 | ) | |
|
614 | 664 | (opts, args) = parser.parse_args() |
|
615 | 665 | |
|
616 | 666 | if not args or args[0] not in commands: |
@@ -96,6 +96,7 b' import sys' | |||
|
96 | 96 | # Enable tracing. Run 'python -m win32traceutil' to debug |
|
97 | 97 | if getattr(sys, 'isapidllhandle', None) is not None: |
|
98 | 98 | import win32traceutil |
|
99 | ||
|
99 | 100 | win32traceutil.SetupForPrint # silence unused import warning |
|
100 | 101 | |
|
101 | 102 | import isapi_wsgi |
@@ -106,11 +107,13 b' from mercurial.hgweb.hgwebdir_mod import' | |||
|
106 | 107 | class WsgiHandler(isapi_wsgi.IsapiWsgiHandler): |
|
107 | 108 | error_status = '500 Internal Server Error' # less silly error message |
|
108 | 109 | |
|
110 | ||
|
109 | 111 | isapi_wsgi.IsapiWsgiHandler = WsgiHandler |
|
110 | 112 | |
|
111 | 113 | # Only create the hgwebdir instance once |
|
112 | 114 | application = hgwebdir(hgweb_config) |
|
113 | 115 | |
|
116 | ||
|
114 | 117 | def handler(environ, start_response): |
|
115 | 118 | |
|
116 | 119 | # Translate IIS's weird URLs |
@@ -125,10 +128,13 b' def handler(environ, start_response):' | |||
|
125 | 128 | |
|
126 | 129 | return application(environ, start_response) |
|
127 | 130 | |
|
131 | ||
|
128 | 132 | def __ExtensionFactory__(): |
|
129 | 133 | return isapi_wsgi.ISAPISimpleHandler(handler) |
|
130 | 134 | |
|
135 | ||
|
131 | 136 | if __name__=='__main__': |
|
132 | 137 | from isapi.install import ISAPIParameters, HandleCommandLine |
|
138 | ||
|
133 | 139 | params = ISAPIParameters() |
|
134 | 140 | HandleCommandLine(params) |
@@ -11,7 +11,9 b' import sys' | |||
|
11 | 11 | # import from the live mercurial repo |
|
12 | 12 | os.environ['HGMODULEPOLICY'] = 'py' |
|
13 | 13 | sys.path.insert(0, "..") |
|
14 |
from mercurial import demandimport |
|
|
14 | from mercurial import demandimport | |
|
15 | ||
|
16 | demandimport.enable() | |
|
15 | 17 | from mercurial import ( |
|
16 | 18 | commands, |
|
17 | 19 | extensions, |
@@ -36,13 +38,16 b' initlevel_cmd = 1' | |||
|
36 | 38 | initlevel_ext = 1 |
|
37 | 39 | initlevel_ext_cmd = 3 |
|
38 | 40 | |
|
41 | ||
|
39 | 42 | def showavailables(ui, initlevel): |
|
40 |
avail = |
|
|
41 |
', '.join(['%r' % (m * 4) for m in level2mark[initlevel + 1:]]) |
|
|
43 | avail = ' available marks and order of them in this help: %s\n' % ( | |
|
44 | ', '.join(['%r' % (m * 4) for m in level2mark[initlevel + 1 :]]) | |
|
45 | ) | |
|
42 | 46 | ui.warn(avail.encode('utf-8')) |
|
43 | 47 | |
|
48 | ||
|
44 | 49 | def checkseclevel(ui, doc, name, initlevel): |
|
45 |
ui.note |
|
|
50 | ui.note('checking "%s"\n' % name) | |
|
46 | 51 | if not isinstance(doc, bytes): |
|
47 | 52 | doc = doc.encode('utf-8') |
|
48 | 53 | blocks, pruned = minirst.parse(doc, 0, ['verbose']) |
@@ -54,66 +59,77 b' def checkseclevel(ui, doc, name, initlev' | |||
|
54 | 59 | mark = block[b'underline'] |
|
55 | 60 | title = block[b'lines'][0] |
|
56 | 61 | if (mark not in mark2level) or (mark2level[mark] <= initlevel): |
|
57 | ui.warn((('invalid section mark %r for "%s" of %s\n') % | |
|
58 | (mark * 4, title, name)).encode('utf-8')) | |
|
62 | ui.warn( | |
|
63 | ( | |
|
64 | 'invalid section mark %r for "%s" of %s\n' | |
|
65 | % (mark * 4, title, name) | |
|
66 | ).encode('utf-8') | |
|
67 | ) | |
|
59 | 68 | showavailables(ui, initlevel) |
|
60 | 69 | errorcnt += 1 |
|
61 | 70 | continue |
|
62 | 71 | nextlevel = mark2level[mark] |
|
63 | 72 | if curlevel < nextlevel and curlevel + 1 != nextlevel: |
|
64 |
ui.warn |
|
|
65 | (title, name)) | |
|
73 | ui.warn('gap of section level at "%s" of %s\n' % (title, name)) | |
|
66 | 74 | showavailables(ui, initlevel) |
|
67 | 75 | errorcnt += 1 |
|
68 | 76 | continue |
|
69 | ui.note(('appropriate section level for "%s %s"\n') % | |
|
70 | (mark * (nextlevel * 2), title)) | |
|
77 | ui.note( | |
|
78 | 'appropriate section level for "%s %s"\n' | |
|
79 | % (mark * (nextlevel * 2), title) | |
|
80 | ) | |
|
71 | 81 | curlevel = nextlevel |
|
72 | 82 | |
|
73 | 83 | return errorcnt |
|
74 | 84 | |
|
85 | ||
|
75 | 86 | def checkcmdtable(ui, cmdtable, namefmt, initlevel): |
|
76 | 87 | errorcnt = 0 |
|
77 | 88 | for k, entry in cmdtable.items(): |
|
78 | 89 | name = k.split(b"|")[0].lstrip(b"^") |
|
79 | 90 | if not entry[0].__doc__: |
|
80 |
ui.note |
|
|
81 | (namefmt % name)) | |
|
91 | ui.note('skip checking %s: no help document\n' % (namefmt % name)) | |
|
82 | 92 | continue |
|
83 |
errorcnt += checkseclevel( |
|
|
84 | namefmt % name, | |
|
85 | initlevel) | |
|
93 | errorcnt += checkseclevel( | |
|
94 | ui, entry[0].__doc__, namefmt % name, initlevel | |
|
95 | ) | |
|
86 | 96 | return errorcnt |
|
87 | 97 | |
|
98 | ||
|
88 | 99 | def checkhghelps(ui): |
|
89 | 100 | errorcnt = 0 |
|
90 | 101 | for h in helptable: |
|
91 | 102 | names, sec, doc = h[0:3] |
|
92 | 103 | if callable(doc): |
|
93 | 104 | doc = doc(ui) |
|
94 |
errorcnt += checkseclevel( |
|
|
95 |
|
|
|
96 | initlevel_topic) | |
|
105 | errorcnt += checkseclevel( | |
|
106 | ui, doc, '%s help topic' % names[0], initlevel_topic | |
|
107 | ) | |
|
97 | 108 | |
|
98 | 109 | errorcnt += checkcmdtable(ui, table, '%s command', initlevel_cmd) |
|
99 | 110 | |
|
100 |
for name in sorted( |
|
|
101 |
|
|
|
111 | for name in sorted( | |
|
112 | list(extensions.enabled()) + list(extensions.disabled()) | |
|
113 | ): | |
|
102 | 114 | mod = extensions.load(ui, name, None) |
|
103 | 115 | if not mod.__doc__: |
|
104 |
ui.note |
|
|
116 | ui.note('skip checking %s extension: no help document\n' % name) | |
|
105 | 117 | continue |
|
106 |
errorcnt += checkseclevel( |
|
|
107 | '%s extension' % name, | |
|
108 | initlevel_ext) | |
|
118 | errorcnt += checkseclevel( | |
|
119 | ui, mod.__doc__, '%s extension' % name, initlevel_ext | |
|
120 | ) | |
|
109 | 121 | |
|
110 | 122 | cmdtable = getattr(mod, 'cmdtable', None) |
|
111 | 123 | if cmdtable: |
|
112 |
errorcnt += checkcmdtable( |
|
|
124 | errorcnt += checkcmdtable( | |
|
125 | ui, | |
|
126 | cmdtable, | |
|
113 | 127 |
|
|
114 |
|
|
|
128 | initlevel_ext_cmd, | |
|
129 | ) | |
|
115 | 130 | return errorcnt |
|
116 | 131 | |
|
132 | ||
|
117 | 133 | def checkfile(ui, filename, initlevel): |
|
118 | 134 | if filename == '-': |
|
119 | 135 | filename = 'stdin' |
@@ -122,43 +138,76 b' def checkfile(ui, filename, initlevel):' | |||
|
122 | 138 | with open(filename) as fp: |
|
123 | 139 | doc = fp.read() |
|
124 | 140 | |
|
125 | ui.note(('checking input from %s with initlevel %d\n') % | |
|
126 |
(filename, initlevel) |
|
|
141 | ui.note( | |
|
142 | 'checking input from %s with initlevel %d\n' % (filename, initlevel) | |
|
143 | ) | |
|
127 | 144 | return checkseclevel(ui, doc, 'input from %s' % filename, initlevel) |
|
128 | 145 | |
|
146 | ||
|
129 | 147 | def main(): |
|
130 |
optparser = optparse.OptionParser( |
|
|
148 | optparser = optparse.OptionParser( | |
|
149 | """%prog [options] | |
|
131 | 150 |
|
|
132 | 151 |
|
|
133 | 152 |
|
|
134 | 153 |
|
|
135 |
|
|
|
136 | optparser.add_option("-v", "--verbose", | |
|
137 | help="enable additional output", | |
|
138 | action="store_true") | |
|
139 | optparser.add_option("-d", "--debug", | |
|
140 | help="debug mode", | |
|
141 | action="store_true") | |
|
142 | optparser.add_option("-f", "--file", | |
|
154 | """ | |
|
155 | ) | |
|
156 | optparser.add_option( | |
|
157 | "-v", "--verbose", help="enable additional output", action="store_true" | |
|
158 | ) | |
|
159 | optparser.add_option( | |
|
160 | "-d", "--debug", help="debug mode", action="store_true" | |
|
161 | ) | |
|
162 | optparser.add_option( | |
|
163 | "-f", | |
|
164 | "--file", | |
|
143 | 165 |
|
|
144 |
|
|
|
166 | action="store", | |
|
167 | default="", | |
|
168 | ) | |
|
145 | 169 | |
|
146 |
optparser.add_option( |
|
|
170 | optparser.add_option( | |
|
171 | "-t", | |
|
172 | "--topic", | |
|
147 | 173 |
|
|
148 | action="store_const", dest="initlevel", const=0) | |
|
149 | optparser.add_option("-c", "--command", | |
|
174 | action="store_const", | |
|
175 | dest="initlevel", | |
|
176 | const=0, | |
|
177 | ) | |
|
178 | optparser.add_option( | |
|
179 | "-c", | |
|
180 | "--command", | |
|
150 | 181 |
|
|
151 | action="store_const", dest="initlevel", const=1) | |
|
152 | optparser.add_option("-e", "--extension", | |
|
182 | action="store_const", | |
|
183 | dest="initlevel", | |
|
184 | const=1, | |
|
185 | ) | |
|
186 | optparser.add_option( | |
|
187 | "-e", | |
|
188 | "--extension", | |
|
153 | 189 |
|
|
154 | action="store_const", dest="initlevel", const=1) | |
|
155 | optparser.add_option("-C", "--extension-command", | |
|
190 | action="store_const", | |
|
191 | dest="initlevel", | |
|
192 | const=1, | |
|
193 | ) | |
|
194 | optparser.add_option( | |
|
195 | "-C", | |
|
196 | "--extension-command", | |
|
156 | 197 |
|
|
157 | action="store_const", dest="initlevel", const=3) | |
|
198 | action="store_const", | |
|
199 | dest="initlevel", | |
|
200 | const=3, | |
|
201 | ) | |
|
158 | 202 | |
|
159 |
optparser.add_option( |
|
|
203 | optparser.add_option( | |
|
204 | "-l", | |
|
205 | "--initlevel", | |
|
160 | 206 |
|
|
161 | action="store", type="int", default=0) | |
|
207 | action="store", | |
|
208 | type="int", | |
|
209 | default=0, | |
|
210 | ) | |
|
162 | 211 | |
|
163 | 212 | (options, args) = optparser.parse_args() |
|
164 | 213 | |
@@ -173,5 +222,6 b' option.' | |||
|
173 | 222 | if checkhghelps(ui): |
|
174 | 223 | sys.exit(1) |
|
175 | 224 | |
|
225 | ||
|
176 | 226 | if __name__ == "__main__": |
|
177 | 227 | main() |
@@ -12,6 +12,7 b' import textwrap' | |||
|
12 | 12 | |
|
13 | 13 | try: |
|
14 | 14 | import msvcrt |
|
15 | ||
|
15 | 16 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) |
|
16 | 17 | msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) |
|
17 | 18 | except ImportError: |
@@ -22,10 +23,13 b' except ImportError:' | |||
|
22 | 23 | os.environ[r'HGMODULEPOLICY'] = r'allow' |
|
23 | 24 | # import from the live mercurial repo |
|
24 | 25 | sys.path.insert(0, r"..") |
|
25 |
from mercurial import demandimport |
|
|
26 | from mercurial import demandimport | |
|
27 | ||
|
28 | demandimport.enable() | |
|
26 | 29 | # Load util so that the locale path is set by i18n.setdatapath() before |
|
27 | 30 | # calling _(). |
|
28 | 31 | from mercurial import util |
|
32 | ||
|
29 | 33 | util.datapath |
|
30 | 34 | from mercurial import ( |
|
31 | 35 | commands, |
@@ -46,6 +50,7 b' globalopts = commands.globalopts' | |||
|
46 | 50 | helptable = help.helptable |
|
47 | 51 | loaddoc = help.loaddoc |
|
48 | 52 | |
|
53 | ||
|
49 | 54 | def get_desc(docstr): |
|
50 | 55 | if not docstr: |
|
51 | 56 | return b"", b"" |
@@ -64,6 +69,7 b' def get_desc(docstr):' | |||
|
64 | 69 | |
|
65 | 70 | return (shortdesc, desc) |
|
66 | 71 | |
|
72 | ||
|
67 | 73 | def get_opts(opts): |
|
68 | 74 | for opt in opts: |
|
69 | 75 | if len(opt) == 5: |
@@ -86,6 +92,7 b' def get_opts(opts):' | |||
|
86 | 92 | desc += default and _(b" (default: %s)") % bytes(default) or b"" |
|
87 | 93 | yield (b", ".join(allopts), desc) |
|
88 | 94 | |
|
95 | ||
|
89 | 96 | def get_cmd(cmd, cmdtable): |
|
90 | 97 | d = {} |
|
91 | 98 | attr = cmdtable[cmd] |
@@ -106,6 +113,7 b' def get_cmd(cmd, cmdtable):' | |||
|
106 | 113 | |
|
107 | 114 | return d |
|
108 | 115 | |
|
116 | ||
|
109 | 117 | def showdoc(ui): |
|
110 | 118 | # print options |
|
111 | 119 | ui.write(minirst.section(_(b"Options"))) |
@@ -127,14 +135,22 b' def showdoc(ui):' | |||
|
127 | 135 | helpprinter(ui, helptable, minirst.section, exclude=[b'config']) |
|
128 | 136 | |
|
129 | 137 | ui.write(minirst.section(_(b"Extensions"))) |
|
130 | ui.write(_(b"This section contains help for extensions that are " | |
|
138 | ui.write( | |
|
139 | _( | |
|
140 | b"This section contains help for extensions that are " | |
|
131 | 141 |
|
|
132 |
|
|
|
133 | ui.write((b"\n\n" | |
|
142 | b"extensions is available in the help system." | |
|
143 | ) | |
|
144 | ) | |
|
145 | ui.write( | |
|
146 | ( | |
|
147 | b"\n\n" | |
|
134 | 148 |
|
|
135 | 149 |
|
|
136 | 150 |
|
|
137 |
|
|
|
151 | b" :depth: 1\n\n" | |
|
152 | ) | |
|
153 | ) | |
|
138 | 154 | |
|
139 | 155 | for extensionname in sorted(allextensionnames()): |
|
140 | 156 | mod = extensions.load(ui, extensionname, None) |
@@ -143,24 +159,42 b' def showdoc(ui):' | |||
|
143 | 159 | cmdtable = getattr(mod, 'cmdtable', None) |
|
144 | 160 | if cmdtable: |
|
145 | 161 | ui.write(minirst.subsubsection(_(b'Commands'))) |
|
146 | commandprinter(ui, cmdtable, minirst.subsubsubsection, | |
|
147 | minirst.subsubsubsubsection) | |
|
162 | commandprinter( | |
|
163 | ui, | |
|
164 | cmdtable, | |
|
165 | minirst.subsubsubsection, | |
|
166 | minirst.subsubsubsubsection, | |
|
167 | ) | |
|
168 | ||
|
148 | 169 | |
|
149 | 170 | def showtopic(ui, topic): |
|
150 | 171 | extrahelptable = [ |
|
151 | 172 | ([b"common"], b'', loaddoc(b'common'), help.TOPIC_CATEGORY_MISC), |
|
152 | 173 | ([b"hg.1"], b'', loaddoc(b'hg.1'), help.TOPIC_CATEGORY_CONFIG), |
|
153 | 174 | ([b"hg-ssh.8"], b'', loaddoc(b'hg-ssh.8'), help.TOPIC_CATEGORY_CONFIG), |
|
154 | ([b"hgignore.5"], b'', loaddoc(b'hgignore.5'), | |
|
155 | help.TOPIC_CATEGORY_CONFIG), | |
|
175 | ( | |
|
176 | [b"hgignore.5"], | |
|
177 | b'', | |
|
178 | loaddoc(b'hgignore.5'), | |
|
179 | help.TOPIC_CATEGORY_CONFIG, | |
|
180 | ), | |
|
156 | 181 | ([b"hgrc.5"], b'', loaddoc(b'hgrc.5'), help.TOPIC_CATEGORY_CONFIG), |
|
157 | ([b"hgignore.5.gendoc"], b'', loaddoc(b'hgignore'), | |
|
158 | help.TOPIC_CATEGORY_CONFIG), | |
|
159 | ([b"hgrc.5.gendoc"], b'', loaddoc(b'config'), | |
|
160 | help.TOPIC_CATEGORY_CONFIG), | |
|
182 | ( | |
|
183 | [b"hgignore.5.gendoc"], | |
|
184 | b'', | |
|
185 | loaddoc(b'hgignore'), | |
|
186 | help.TOPIC_CATEGORY_CONFIG, | |
|
187 | ), | |
|
188 | ( | |
|
189 | [b"hgrc.5.gendoc"], | |
|
190 | b'', | |
|
191 | loaddoc(b'config'), | |
|
192 | help.TOPIC_CATEGORY_CONFIG, | |
|
193 | ), | |
|
161 | 194 | ] |
|
162 | 195 | helpprinter(ui, helptable + extrahelptable, None, include=[topic]) |
|
163 | 196 | |
|
197 | ||
|
164 | 198 | def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]): |
|
165 | 199 | for h in helptable: |
|
166 | 200 | names, sec, doc = h[0:3] |
@@ -178,6 +212,7 b' def helpprinter(ui, helptable, sectionfu' | |||
|
178 | 212 | ui.write(doc) |
|
179 | 213 | ui.write(b"\n") |
|
180 | 214 | |
|
215 | ||
|
181 | 216 | def commandprinter(ui, cmdtable, sectionfunc, subsectionfunc): |
|
182 | 217 | """Render restructuredtext describing a list of commands and their |
|
183 | 218 | documentations, grouped by command category. |
@@ -222,7 +257,8 b' def commandprinter(ui, cmdtable, section' | |||
|
222 | 257 | if helpcategory(cmd) not in cmdsbycategory: |
|
223 | 258 | raise AssertionError( |
|
224 | 259 | "The following command did not register its (category) in " |
|
225 |
"help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd)) |
|
|
260 | "help.CATEGORY_ORDER: %s (%s)" % (cmd, helpcategory(cmd)) | |
|
261 | ) | |
|
226 | 262 | cmdsbycategory[helpcategory(cmd)].append(cmd) |
|
227 | 263 | |
|
228 | 264 | # Print the help for each command. We present the commands grouped by |
@@ -270,16 +306,22 b' def commandprinter(ui, cmdtable, section' | |||
|
270 | 306 | if optstr.endswith(b"[+]>"): |
|
271 | 307 | multioccur = True |
|
272 | 308 | if multioccur: |
|
273 | ui.write(_(b"\n[+] marked option can be specified" | |
|
274 |
|
|
|
309 | ui.write( | |
|
310 | _( | |
|
311 | b"\n[+] marked option can be specified" | |
|
312 | b" multiple times\n" | |
|
313 | ) | |
|
314 | ) | |
|
275 | 315 | ui.write(b"\n") |
|
276 | 316 | # aliases |
|
277 | 317 | if d[b'aliases']: |
|
278 | 318 | ui.write(_(b" aliases: %s\n\n") % b" ".join(d[b'aliases'])) |
|
279 | 319 | |
|
320 | ||
|
280 | 321 | def allextensionnames(): |
|
281 | 322 | return set(extensions.enabled().keys()) | set(extensions.disabled().keys()) |
|
282 | 323 | |
|
324 | ||
|
283 | 325 | if __name__ == "__main__": |
|
284 | 326 | doc = b'hg.1.gendoc' |
|
285 | 327 | if len(sys.argv) > 1: |
@@ -53,6 +53,7 b' from docutils import (' | |||
|
53 | 53 | nodes, |
|
54 | 54 | writers, |
|
55 | 55 | ) |
|
56 | ||
|
56 | 57 | try: |
|
57 | 58 | import roman |
|
58 | 59 | except ImportError: |
@@ -65,7 +66,7 b' BLOCKQOUTE_INDENT = 3.5' | |||
|
65 | 66 | |
|
66 | 67 | # Define two macros so man/roff can calculate the |
|
67 | 68 | # indent/unindent margins by itself |
|
68 |
MACRO_DEF = |
|
|
69 | MACRO_DEF = r""". | |
|
69 | 70 | .nr rst2man-indent-level 0 |
|
70 | 71 | . |
|
71 | 72 | .de1 rstReportMargin |
@@ -92,11 +93,12 b' level margin: \\\\n[rst2man-indent\\\\n[rst2' | |||
|
92 | 93 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] |
|
93 | 94 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u |
|
94 | 95 | .. |
|
95 |
""" |
|
|
96 | """ | |
|
97 | ||
|
96 | 98 | |
|
97 | 99 | class Writer(writers.Writer): |
|
98 | 100 | |
|
99 |
supported = |
|
|
101 | supported = 'manpage' | |
|
100 | 102 | """Formats this writer supports.""" |
|
101 | 103 | |
|
102 | 104 | output = None |
@@ -118,11 +120,14 b' class Table(object):' | |||
|
118 | 120 | self._options = ['center'] |
|
119 | 121 | self._tab_char = '\t' |
|
120 | 122 | self._coldefs = [] |
|
123 | ||
|
121 | 124 | def new_row(self): |
|
122 | 125 | self._rows.append([]) |
|
126 | ||
|
123 | 127 | def append_separator(self, separator): |
|
124 | 128 | """Append the separator for table head.""" |
|
125 | 129 | self._rows.append([separator]) |
|
130 | ||
|
126 | 131 | def append_cell(self, cell_lines): |
|
127 | 132 | """cell_lines is an array of lines""" |
|
128 | 133 | start = 0 |
@@ -131,12 +136,14 b' class Table(object):' | |||
|
131 | 136 | self._rows[-1].append(cell_lines[start:]) |
|
132 | 137 | if len(self._coldefs) < len(self._rows[-1]): |
|
133 | 138 | self._coldefs.append('l') |
|
139 | ||
|
134 | 140 | def _minimize_cell(self, cell_lines): |
|
135 | 141 | """Remove leading and trailing blank and ``.sp`` lines""" |
|
136 |
while |
|
|
142 | while cell_lines and cell_lines[0] in ('\n', '.sp\n'): | |
|
137 | 143 | del cell_lines[0] |
|
138 |
while |
|
|
144 | while cell_lines and cell_lines[-1] in ('\n', '.sp\n'): | |
|
139 | 145 | del cell_lines[-1] |
|
146 | ||
|
140 | 147 | def as_list(self): |
|
141 | 148 | text = ['.TS\n'] |
|
142 | 149 | text.append(' '.join(self._options) + ';\n') |
@@ -159,6 +166,7 b' class Table(object):' | |||
|
159 | 166 | text.append('.TE\n') |
|
160 | 167 | return text |
|
161 | 168 | |
|
169 | ||
|
162 | 170 | class Translator(nodes.NodeVisitor): |
|
163 | 171 | """""" |
|
164 | 172 | |
@@ -171,8 +179,9 b' class Translator(nodes.NodeVisitor):' | |||
|
171 | 179 | lcode = settings.language_code |
|
172 | 180 | arglen = len(inspect.getargspec(languages.get_language)[0]) |
|
173 | 181 | if arglen == 2: |
|
174 |
self.language = languages.get_language( |
|
|
175 |
|
|
|
182 | self.language = languages.get_language( | |
|
183 | lcode, self.document.reporter | |
|
184 | ) | |
|
176 | 185 | else: |
|
177 | 186 | self.language = languages.get_language(lcode) |
|
178 | 187 | self.head = [] |
@@ -189,9 +198,11 b' class Translator(nodes.NodeVisitor):' | |||
|
189 | 198 | # writing the header .TH and .SH NAME is postboned after |
|
190 | 199 | # docinfo. |
|
191 | 200 | self._docinfo = { |
|
192 |
|
|
|
201 | "title": "", | |
|
202 | "title_upper": "", | |
|
193 | 203 |
|
|
194 |
|
|
|
204 | "manual_section": "", | |
|
205 | "manual_group": "", | |
|
195 | 206 |
|
|
196 | 207 |
|
|
197 | 208 |
|
@@ -222,18 +233,14 b' class Translator(nodes.NodeVisitor):' | |||
|
222 | 233 |
|
|
223 | 234 |
|
|
224 | 235 |
|
|
225 | ||
|
226 | 236 |
|
|
227 | ||
|
228 | 237 |
|
|
229 | 238 |
|
|
230 | 239 |
|
|
231 | 240 |
|
|
232 | 241 |
|
|
233 | ||
|
234 | 242 |
|
|
235 | 243 |
|
|
236 | ||
|
237 | 244 |
|
|
238 | 245 |
|
|
239 | 246 | # NOTE don't specify the newline before a dot-command, but ensure |
@@ -244,8 +251,8 b' class Translator(nodes.NodeVisitor):' | |||
|
244 | 251 | line/comment.""" |
|
245 | 252 | prefix = '.\\" ' |
|
246 | 253 | out_text = ''.join( |
|
247 | [(prefix + in_line + '\n') | |
|
248 | for in_line in text.split('\n')]) | |
|
254 | [(prefix + in_line + '\n') for in_line in text.split('\n')] | |
|
255 | ) | |
|
249 | 256 | return out_text |
|
250 | 257 | |
|
251 | 258 | def comment(self, text): |
@@ -268,13 +275,18 b' class Translator(nodes.NodeVisitor):' | |||
|
268 | 275 | if self.body[i] == '.sp\n': |
|
269 | 276 | if self.body[i - 1][:4] in ('.BI ','.IP '): |
|
270 | 277 | self.body[i] = '.\n' |
|
271 | elif (self.body[i - 1][:3] == '.B ' and | |
|
272 |
self.body[i - |
|
|
278 | elif ( | |
|
279 | self.body[i - 1][:3] == '.B ' | |
|
280 | and self.body[i - 2][:4] == '.TP\n' | |
|
281 | ): | |
|
273 | 282 | self.body[i] = '.\n' |
|
274 |
elif ( |
|
|
275 |
self.body[i - |
|
|
276 |
|
|
|
277 | or self.body[i - 3][:4] == '\n.B ') | |
|
283 | elif ( | |
|
284 | self.body[i - 1] == '\n' | |
|
285 | and self.body[i - 2][0] != '.' | |
|
286 | and ( | |
|
287 | self.body[i - 3][:7] == '.TP\n.B ' | |
|
288 | or self.body[i - 3][:4] == '\n.B ' | |
|
289 | ) | |
|
278 | 290 |
|
|
279 | 291 | self.body[i] = '.\n' |
|
280 | 292 | return ''.join(self.head + self.body + self.foot) |
@@ -358,6 +370,7 b' class Translator(nodes.NodeVisitor):' | |||
|
358 | 370 | |
|
359 | 371 | def get_width(self): |
|
360 | 372 | return self._indent |
|
373 | ||
|
361 | 374 | def __repr__(self): |
|
362 | 375 | return 'enum_style-%s' % list(self._style) |
|
363 | 376 | |
@@ -376,10 +389,12 b' class Translator(nodes.NodeVisitor):' | |||
|
376 | 389 | self._list_char.pop() |
|
377 | 390 | |
|
378 | 391 | def header(self): |
|
379 | tmpl = (".TH %(title_upper)s %(manual_section)s" | |
|
392 | tmpl = ( | |
|
393 | ".TH %(title_upper)s %(manual_section)s" | |
|
380 | 394 |
|
|
381 | 395 |
|
|
382 |
|
|
|
396 | "%(title)s \\- %(subtitle)s\n" | |
|
397 | ) | |
|
383 | 398 | return tmpl % self._docinfo |
|
384 | 399 | |
|
385 | 400 | def append_header(self): |
@@ -400,8 +415,7 b' class Translator(nodes.NodeVisitor):' | |||
|
400 | 415 | |
|
401 | 416 | def visit_admonition(self, node, name=None): |
|
402 | 417 | if name: |
|
403 | self.body.append('.IP %s\n' % | |
|
404 | self.language.labels.get(name, name)) | |
|
418 | self.body.append('.IP %s\n' % self.language.labels.get(name, name)) | |
|
405 | 419 | |
|
406 | 420 | def depart_admonition(self, node): |
|
407 | 421 | self.body.append('.RE\n') |
@@ -488,8 +502,7 b' class Translator(nodes.NodeVisitor):' | |||
|
488 | 502 | def write_colspecs(self): |
|
489 | 503 | self.body.append("%s.\n" % ('L '*len(self.colspecs))) |
|
490 | 504 | |
|
491 | def visit_comment(self, node, | |
|
492 | sub=re.compile('-(?=-)').sub): | |
|
505 | def visit_comment(self, node, sub=re.compile('-(?=-)').sub): | |
|
493 | 506 | self.body.append(self.comment(node.astext())) |
|
494 | 507 | raise nodes.SkipNode() |
|
495 | 508 | |
@@ -575,21 +588,33 b' class Translator(nodes.NodeVisitor):' | |||
|
575 | 588 | |
|
576 | 589 | def depart_document(self, node): |
|
577 | 590 | if self._docinfo['author']: |
|
578 |
self.body.append( |
|
|
579 |
|
|
|
580 | skip = ('author', 'copyright', 'date', | |
|
581 | 'manual_group', 'manual_section', | |
|
591 | self.body.append( | |
|
592 | '.SH AUTHOR\n%s\n' % ', '.join(self._docinfo['author']) | |
|
593 | ) | |
|
594 | skip = ( | |
|
595 | 'author', | |
|
596 | 'copyright', | |
|
597 | 'date', | |
|
598 | 'manual_group', | |
|
599 | 'manual_section', | |
|
582 | 600 |
|
|
583 | 'title', 'title_upper', 'version') | |
|
601 | 'title', | |
|
602 | 'title_upper', | |
|
603 | 'version', | |
|
604 | ) | |
|
584 | 605 | for name in self._docinfo_keys: |
|
585 | 606 | if name == 'address': |
|
586 |
self.body.append( |
|
|
607 | self.body.append( | |
|
608 | "\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" | |
|
609 | % ( | |
|
587 | 610 |
|
|
588 | 611 |
|
|
589 | 612 |
|
|
590 | 613 |
|
|
591 | 614 |
|
|
592 |
|
|
|
615 | self.defs['indent'][1], | |
|
616 | ) | |
|
617 | ) | |
|
593 | 618 | elif name not in skip: |
|
594 | 619 | if name in self._docinfo_names: |
|
595 | 620 | label = self._docinfo_names[name] |
@@ -597,10 +622,10 b' class Translator(nodes.NodeVisitor):' | |||
|
597 | 622 | label = self.language.labels.get(name, name) |
|
598 | 623 | self.body.append("\n%s: %s\n" % (label, self._docinfo[name])) |
|
599 | 624 | if self._docinfo['copyright']: |
|
600 | self.body.append('.SH COPYRIGHT\n%s\n' | |
|
601 | % self._docinfo['copyright']) | |
|
602 | self.body.append(self.comment( | |
|
603 | 'Generated by docutils manpage writer.\n')) | |
|
625 | self.body.append('.SH COPYRIGHT\n%s\n' % self._docinfo['copyright']) | |
|
626 | self.body.append( | |
|
627 | self.comment('Generated by docutils manpage writer.\n') | |
|
628 | ) | |
|
604 | 629 | |
|
605 | 630 | def visit_emphasis(self, node): |
|
606 | 631 | self.body.append(self.defs['emphasis'][0]) |
@@ -611,11 +636,13 b' class Translator(nodes.NodeVisitor):' | |||
|
611 | 636 | def visit_entry(self, node): |
|
612 | 637 | # a cell in a table row |
|
613 | 638 | if 'morerows' in node: |
|
614 |
self.document.reporter.warning( |
|
|
615 |
base_node=node |
|
|
639 | self.document.reporter.warning( | |
|
640 | '"table row spanning" not supported', base_node=node | |
|
641 | ) | |
|
616 | 642 | if 'morecols' in node: |
|
617 | 643 | self.document.reporter.warning( |
|
618 |
|
|
|
644 | '"table cell spanning" not supported', base_node=node | |
|
645 | ) | |
|
619 | 646 | self.context.append(len(self.body)) |
|
620 | 647 | |
|
621 | 648 | def depart_entry(self, node): |
@@ -675,8 +702,7 b' class Translator(nodes.NodeVisitor):' | |||
|
675 | 702 | self.dedent() |
|
676 | 703 | |
|
677 | 704 | def visit_footer(self, node): |
|
678 | self.document.reporter.warning('"footer" not supported', | |
|
679 | base_node=node) | |
|
705 | self.document.reporter.warning('"footer" not supported', base_node=node) | |
|
680 | 706 | |
|
681 | 707 | def depart_footer(self, node): |
|
682 | 708 | pass |
@@ -690,8 +716,9 b' class Translator(nodes.NodeVisitor):' | |||
|
690 | 716 | pass |
|
691 | 717 | |
|
692 | 718 | def footnote_backrefs(self, node): |
|
693 |
self.document.reporter.warning( |
|
|
694 |
|
|
|
719 | self.document.reporter.warning( | |
|
720 | '"footnote_backrefs" not supported', base_node=node | |
|
721 | ) | |
|
695 | 722 | |
|
696 | 723 | def visit_footnote_reference(self, node): |
|
697 | 724 | self.body.append('['+self.deunicode(node.astext())+']') |
@@ -736,8 +763,7 b' class Translator(nodes.NodeVisitor):' | |||
|
736 | 763 | self.body.append('\n') |
|
737 | 764 | |
|
738 | 765 | def visit_image(self, node): |
|
739 | self.document.reporter.warning('"image" not supported', | |
|
740 | base_node=node) | |
|
766 | self.document.reporter.warning('"image" not supported', base_node=node) | |
|
741 | 767 | text = [] |
|
742 | 768 | if 'alt' in node.attributes: |
|
743 | 769 | text.append(node.attributes['alt']) |
@@ -753,11 +779,11 b' class Translator(nodes.NodeVisitor):' | |||
|
753 | 779 | |
|
754 | 780 | def visit_label(self, node): |
|
755 | 781 | # footnote and citation |
|
756 |
if |
|
|
757 |
|
|
|
782 | if isinstance(node.parent, nodes.footnote) or isinstance( | |
|
783 | node.parent, nodes.citation | |
|
784 | ): | |
|
758 | 785 | raise nodes.SkipNode() |
|
759 | self.document.reporter.warning('"unsupported "label"', | |
|
760 | base_node=node) | |
|
786 | self.document.reporter.warning('"unsupported "label"', base_node=node) | |
|
761 | 787 | self.body.append('[') |
|
762 | 788 | |
|
763 | 789 | def depart_label(self, node): |
@@ -794,9 +820,10 b' class Translator(nodes.NodeVisitor):' | |||
|
794 | 820 | |
|
795 | 821 | def visit_list_item(self, node): |
|
796 | 822 | # man 7 man argues to use ".IP" instead of ".TP" |
|
797 |
self.body.append( |
|
|
798 | next(self._list_char[-1]), | |
|
799 |
|
|
|
823 | self.body.append( | |
|
824 | '.IP %s %d\n' | |
|
825 | % (next(self._list_char[-1]), self._list_char[-1].get_width(),) | |
|
826 | ) | |
|
800 | 827 | |
|
801 | 828 | def depart_list_item(self, node): |
|
802 | 829 | pass |
@@ -968,8 +995,9 b' class Translator(nodes.NodeVisitor):' | |||
|
968 | 995 | raise nodes.SkipNode() |
|
969 | 996 | |
|
970 | 997 | def visit_substitution_reference(self, node): |
|
971 |
self.document.reporter.warning( |
|
|
972 | base_node=node) | |
|
998 | self.document.reporter.warning( | |
|
999 | '"substitution_reference" not supported', base_node=node | |
|
1000 | ) | |
|
973 | 1001 | |
|
974 | 1002 | def visit_subtitle(self, node): |
|
975 | 1003 | if isinstance(node.parent, nodes.sidebar): |
@@ -995,8 +1023,10 b' class Translator(nodes.NodeVisitor):' | |||
|
995 | 1023 | line = ', line %s' % node['line'] |
|
996 | 1024 | else: |
|
997 | 1025 | line = '' |
|
998 | self.body.append('.IP "System Message: %s/%s (%s:%s)"\n' | |
|
999 | % (node['type'], node['level'], node['source'], line)) | |
|
1026 | self.body.append( | |
|
1027 | '.IP "System Message: %s/%s (%s:%s)"\n' | |
|
1028 | % (node['type'], node['level'], node['source'], line) | |
|
1029 | ) | |
|
1000 | 1030 | |
|
1001 | 1031 | def depart_system_message(self, node): |
|
1002 | 1032 | pass |
@@ -1111,7 +1141,9 b' class Translator(nodes.NodeVisitor):' | |||
|
1111 | 1141 | depart_warning = depart_admonition |
|
1112 | 1142 | |
|
1113 | 1143 | def unimplemented_visit(self, node): |
|
1114 |
raise NotImplementedError( |
|
|
1115 |
|
|
|
1144 | raise NotImplementedError( | |
|
1145 | 'visiting unimplemented node type: %s' % node.__class__.__name__ | |
|
1146 | ) | |
|
1147 | ||
|
1116 | 1148 | |
|
1117 | 1149 | # vim: set fileencoding=utf-8 et ts=4 ai : |
@@ -71,8 +71,11 b' isenabled = demandimport.isenabled' | |||
|
71 | 71 | disable = demandimport.disable |
|
72 | 72 | deactivated = demandimport.deactivated |
|
73 | 73 | |
|
74 | ||
|
74 | 75 | def enable(): |
|
75 | 76 | # chg pre-imports modules so do not enable demandimport for it |
|
76 | if ('CHGINTERNALMARK' not in os.environ | |
|
77 | and os.environ.get('HGDEMANDIMPORT') != 'disable'): | |
|
77 | if ( | |
|
78 | 'CHGINTERNALMARK' not in os.environ | |
|
79 | and os.environ.get('HGDEMANDIMPORT') != 'disable' | |
|
80 | ): | |
|
78 | 81 | demandimport.enable() |
@@ -38,6 +38,7 b' contextmanager = contextlib.contextmanag' | |||
|
38 | 38 | |
|
39 | 39 | nothing = object() |
|
40 | 40 | |
|
41 | ||
|
41 | 42 | def _hgextimport(importfunc, name, globals, *args, **kwargs): |
|
42 | 43 | try: |
|
43 | 44 | return importfunc(name, globals, *args, **kwargs) |
@@ -53,6 +54,7 b' def _hgextimport(importfunc, name, globa' | |||
|
53 | 54 | # retry to import with "hgext_" prefix |
|
54 | 55 | return importfunc(hgextname, globals, *args, **kwargs) |
|
55 | 56 | |
|
57 | ||
|
56 | 58 | class _demandmod(object): |
|
57 | 59 | """module demand-loader and proxy |
|
58 | 60 | |
@@ -67,8 +69,9 b' class _demandmod(object):' | |||
|
67 | 69 | else: |
|
68 | 70 | head = name |
|
69 | 71 | after = [] |
|
70 |
object.__setattr__( |
|
|
71 |
|
|
|
72 | object.__setattr__( | |
|
73 | self, r"_data", (head, globals, locals, after, level, set()) | |
|
74 | ) | |
|
72 | 75 | object.__setattr__(self, r"_module", None) |
|
73 | 76 | |
|
74 | 77 | def _extend(self, name): |
@@ -91,7 +94,8 b' class _demandmod(object):' | |||
|
91 | 94 | with tracing.log('demandimport %s', self._data[0]): |
|
92 | 95 | head, globals, locals, after, level, modrefs = self._data |
|
93 | 96 | mod = _hgextimport( |
|
94 |
_origimport, head, globals, locals, None, level |
|
|
97 | _origimport, head, globals, locals, None, level | |
|
98 | ) | |
|
95 | 99 | if mod is self: |
|
96 | 100 | # In this case, _hgextimport() above should imply |
|
97 | 101 | # _demandimport(). Otherwise, _hgextimport() never |
@@ -115,8 +119,11 b' class _demandmod(object):' | |||
|
115 | 119 | if '.' in p: |
|
116 | 120 | h, t = p.split('.', 1) |
|
117 | 121 | if getattr(mod, h, nothing) is nothing: |
|
118 |
setattr( |
|
|
119 |
|
|
|
122 | setattr( | |
|
123 | mod, | |
|
124 | h, | |
|
125 | _demandmod(p, mod.__dict__, mod.__dict__, level=1), | |
|
126 | ) | |
|
120 | 127 | elif t: |
|
121 | 128 | subload(getattr(mod, h), t) |
|
122 | 129 | |
@@ -164,8 +171,10 b' class _demandmod(object):' | |||
|
164 | 171 | self._load() |
|
165 | 172 | return self._module.__doc__ |
|
166 | 173 | |
|
174 | ||
|
167 | 175 | _pypy = '__pypy__' in sys.builtin_module_names |
|
168 | 176 | |
|
177 | ||
|
169 | 178 | def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1): |
|
170 | 179 | if locals is None or name in ignores or fromlist == ('*',): |
|
171 | 180 | # these cases we can't really delay |
@@ -244,8 +253,9 b' def _demandimport(name, globals=None, lo' | |||
|
244 | 253 | if level >= 0: |
|
245 | 254 | if name: |
|
246 | 255 | # "from a import b" or "from .a import b" style |
|
247 |
rootmod = _hgextimport( |
|
|
248 | level=level) | |
|
256 | rootmod = _hgextimport( | |
|
257 | _origimport, name, globals, locals, level=level | |
|
258 | ) | |
|
249 | 259 | mod = chainmodules(rootmod, name) |
|
250 | 260 | elif _pypy: |
|
251 | 261 | # PyPy's __import__ throws an exception if invoked |
@@ -260,8 +270,9 b' def _demandimport(name, globals=None, lo' | |||
|
260 | 270 | mn = mn.rsplit('.', level - 1)[0] |
|
261 | 271 | mod = sys.modules[mn] |
|
262 | 272 | else: |
|
263 |
mod = _hgextimport( |
|
|
264 |
|
|
|
273 | mod = _hgextimport( | |
|
274 | _origimport, name, globals, locals, level=level | |
|
275 | ) | |
|
265 | 276 | |
|
266 | 277 | for x in fromlist: |
|
267 | 278 | processfromitem(mod, x) |
@@ -278,23 +289,29 b' def _demandimport(name, globals=None, lo' | |||
|
278 | 289 | |
|
279 | 290 | return mod |
|
280 | 291 | |
|
292 | ||
|
281 | 293 | ignores = set() |
|
282 | 294 | |
|
295 | ||
|
283 | 296 | def init(ignoreset): |
|
284 | 297 | global ignores |
|
285 | 298 | ignores = ignoreset |
|
286 | 299 | |
|
300 | ||
|
287 | 301 | def isenabled(): |
|
288 | 302 | return builtins.__import__ == _demandimport |
|
289 | 303 | |
|
304 | ||
|
290 | 305 | def enable(): |
|
291 | 306 | "enable global demand-loading of modules" |
|
292 | 307 | builtins.__import__ = _demandimport |
|
293 | 308 | |
|
309 | ||
|
294 | 310 | def disable(): |
|
295 | 311 | "disable global demand-loading of modules" |
|
296 | 312 | builtins.__import__ = _origimport |
|
297 | 313 | |
|
314 | ||
|
298 | 315 | @contextmanager |
|
299 | 316 | def deactivated(): |
|
300 | 317 | "context manager for disabling demandimport in 'with' blocks" |
@@ -36,10 +36,12 b' from . import tracing' | |||
|
36 | 36 | |
|
37 | 37 | _deactivated = False |
|
38 | 38 | |
|
39 | ||
|
39 | 40 | class _lazyloaderex(importlib.util.LazyLoader): |
|
40 | 41 | """This is a LazyLoader except it also follows the _deactivated global and |
|
41 | 42 | the ignore list. |
|
42 | 43 | """ |
|
44 | ||
|
43 | 45 | def exec_module(self, module): |
|
44 | 46 | """Make the module load lazily.""" |
|
45 | 47 | with tracing.log('demandimport %s', module): |
@@ -48,14 +50,18 b' class _lazyloaderex(importlib.util.LazyL' | |||
|
48 | 50 | else: |
|
49 | 51 | super().exec_module(module) |
|
50 | 52 | |
|
53 | ||
|
51 | 54 | # This is 3.6+ because with Python 3.5 it isn't possible to lazily load |
|
52 | 55 | # extensions. See the discussion in https://bugs.python.org/issue26186 for more. |
|
53 | 56 | _extensions_loader = _lazyloaderex.factory( |
|
54 |
importlib.machinery.ExtensionFileLoader |
|
|
57 | importlib.machinery.ExtensionFileLoader | |
|
58 | ) | |
|
55 | 59 | _bytecode_loader = _lazyloaderex.factory( |
|
56 |
importlib.machinery.SourcelessFileLoader |
|
|
60 | importlib.machinery.SourcelessFileLoader | |
|
61 | ) | |
|
57 | 62 | _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader) |
|
58 | 63 | |
|
64 | ||
|
59 | 65 | def _makefinder(path): |
|
60 | 66 | return importlib.machinery.FileFinder( |
|
61 | 67 | path, |
@@ -65,15 +71,19 b' def _makefinder(path):' | |||
|
65 | 71 | (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES), |
|
66 | 72 | ) |
|
67 | 73 | |
|
74 | ||
|
68 | 75 | ignores = set() |
|
69 | 76 | |
|
77 | ||
|
70 | 78 | def init(ignoreset): |
|
71 | 79 | global ignores |
|
72 | 80 | ignores = ignoreset |
|
73 | 81 | |
|
82 | ||
|
74 | 83 | def isenabled(): |
|
75 | 84 | return _makefinder in sys.path_hooks and not _deactivated |
|
76 | 85 | |
|
86 | ||
|
77 | 87 | def disable(): |
|
78 | 88 | try: |
|
79 | 89 | while True: |
@@ -81,9 +91,11 b' def disable():' | |||
|
81 | 91 | except ValueError: |
|
82 | 92 | pass |
|
83 | 93 | |
|
94 | ||
|
84 | 95 | def enable(): |
|
85 | 96 | sys.path_hooks.insert(0, _makefinder) |
|
86 | 97 | |
|
98 | ||
|
87 | 99 | @contextlib.contextmanager |
|
88 | 100 | def deactivated(): |
|
89 | 101 | # This implementation is a bit different from Python 2's. Python 3 |
@@ -14,6 +14,7 b' import os' | |||
|
14 | 14 | _checked = False |
|
15 | 15 | _session = 'none' |
|
16 | 16 | |
|
17 | ||
|
17 | 18 | def _isactive(): |
|
18 | 19 | global _pipe, _session, _checked |
|
19 | 20 | if _pipe is None: |
@@ -26,6 +27,7 b' def _isactive():' | |||
|
26 | 27 | _session = os.environ.get('HGCATAPULTSESSION', 'none') |
|
27 | 28 | return True |
|
28 | 29 | |
|
30 | ||
|
29 | 31 | @contextlib.contextmanager |
|
30 | 32 | def log(whencefmt, *whenceargs): |
|
31 | 33 | if not _isactive(): |
@@ -48,6 +50,7 b' def log(whencefmt, *whenceargs):' | |||
|
48 | 50 | except IOError: |
|
49 | 51 | pass |
|
50 | 52 | |
|
53 | ||
|
51 | 54 | def counter(label, amount, *labelargs): |
|
52 | 55 | if not _isactive(): |
|
53 | 56 | return |
@@ -1,3 +1,4 b'' | |||
|
1 | 1 | from __future__ import absolute_import |
|
2 | 2 | import pkgutil |
|
3 | ||
|
3 | 4 | __path__ = pkgutil.extend_path(__path__, __name__) |
@@ -53,9 +53,7 b' from mercurial import (' | |||
|
53 | 53 | scmutil, |
|
54 | 54 | util, |
|
55 | 55 | ) |
|
56 |
from mercurial.utils import |
|
|
57 | stringutil, | |
|
58 | ) | |
|
56 | from mercurial.utils import stringutil | |
|
59 | 57 | |
|
60 | 58 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
|
61 | 59 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
@@ -81,8 +79,10 b' colortable = {' | |||
|
81 | 79 | |
|
82 | 80 | defaultdict = collections.defaultdict |
|
83 | 81 | |
|
82 | ||
|
84 | 83 | class nullui(object): |
|
85 | 84 | """blank ui object doing nothing""" |
|
85 | ||
|
86 | 86 | debugflag = False |
|
87 | 87 | verbose = False |
|
88 | 88 | quiet = True |
@@ -90,16 +90,20 b' class nullui(object):' | |||
|
90 | 90 | def __getitem__(name): |
|
91 | 91 | def nullfunc(*args, **kwds): |
|
92 | 92 | return |
|
93 | ||
|
93 | 94 | return nullfunc |
|
94 | 95 | |
|
96 | ||
|
95 | 97 | class emptyfilecontext(object): |
|
96 | 98 | """minimal filecontext representing an empty file""" |
|
99 | ||
|
97 | 100 | def data(self): |
|
98 | 101 | return '' |
|
99 | 102 | |
|
100 | 103 | def node(self): |
|
101 | 104 | return node.nullid |
|
102 | 105 | |
|
106 | ||
|
103 | 107 | def uniq(lst): |
|
104 | 108 | """list -> list. remove duplicated items without changing the order""" |
|
105 | 109 | seen = set() |
@@ -110,6 +114,7 b' def uniq(lst):' | |||
|
110 | 114 | result.append(x) |
|
111 | 115 | return result |
|
112 | 116 | |
|
117 | ||
|
113 | 118 | def getdraftstack(headctx, limit=None): |
|
114 | 119 | """(ctx, int?) -> [ctx]. get a linear stack of non-public changesets. |
|
115 | 120 | |
@@ -132,6 +137,7 b' def getdraftstack(headctx, limit=None):' | |||
|
132 | 137 | result.reverse() |
|
133 | 138 | return result |
|
134 | 139 | |
|
140 | ||
|
135 | 141 | def getfilestack(stack, path, seenfctxs=None): |
|
136 | 142 | """([ctx], str, set) -> [fctx], {ctx: fctx} |
|
137 | 143 | |
@@ -213,10 +219,12 b' def getfilestack(stack, path, seenfctxs=' | |||
|
213 | 219 | # remove uniq and find a different way to identify fctxs. |
|
214 | 220 | return uniq(fctxs), fctxmap |
|
215 | 221 | |
|
222 | ||
|
216 | 223 | class overlaystore(patch.filestore): |
|
217 | 224 | """read-only, hybrid store based on a dict and ctx. |
|
218 | 225 | memworkingcopy: {path: content}, overrides file contents. |
|
219 | 226 | """ |
|
227 | ||
|
220 | 228 | def __init__(self, basectx, memworkingcopy): |
|
221 | 229 | self.basectx = basectx |
|
222 | 230 | self.memworkingcopy = memworkingcopy |
@@ -234,6 +242,7 b' class overlaystore(patch.filestore):' | |||
|
234 | 242 | copy = fctx.copysource() |
|
235 | 243 | return content, mode, copy |
|
236 | 244 | |
|
245 | ||
|
237 | 246 | def overlaycontext(memworkingcopy, ctx, parents=None, extra=None): |
|
238 | 247 | """({path: content}, ctx, (p1node, p2node)?, {}?) -> memctx |
|
239 | 248 | memworkingcopy overrides file contents. |
@@ -249,9 +258,17 b' def overlaycontext(memworkingcopy, ctx, ' | |||
|
249 | 258 | files = set(ctx.files()).union(memworkingcopy) |
|
250 | 259 | store = overlaystore(ctx, memworkingcopy) |
|
251 | 260 | return context.memctx( |
|
252 |
repo=ctx.repo(), |
|
|
253 | files=files, filectxfn=store, user=user, date=date, | |
|
254 | branch=None, extra=extra) | |
|
261 | repo=ctx.repo(), | |
|
262 | parents=parents, | |
|
263 | text=desc, | |
|
264 | files=files, | |
|
265 | filectxfn=store, | |
|
266 | user=user, | |
|
267 | date=date, | |
|
268 | branch=None, | |
|
269 | extra=extra, | |
|
270 | ) | |
|
271 | ||
|
255 | 272 | |
|
256 | 273 | class filefixupstate(object): |
|
257 | 274 | """state needed to apply fixups to a single file |
@@ -346,9 +363,10 b' class filefixupstate(object):' | |||
|
346 | 363 | blines = self.targetlines[b1:b2] |
|
347 | 364 | if self.ui.debugflag: |
|
348 | 365 | idx = (max(rev - 1, 0)) // 2 |
|
349 |
self.ui.write( |
|
|
350 | % (node.short(self.fctxs[idx].node()), | |
|
351 |
|
|
|
366 | self.ui.write( | |
|
367 | _('%s: chunk %d:%d -> %d lines\n') | |
|
368 | % (node.short(self.fctxs[idx].node()), a1, a2, len(blines)) | |
|
369 | ) | |
|
352 | 370 | self.linelog.replacelines(rev, a1, a2, b1, b2) |
|
353 | 371 | if self.opts.get('edit_lines', False): |
|
354 | 372 | self.finalcontents = self._checkoutlinelogwithedits() |
@@ -386,8 +404,9 b' class filefixupstate(object):' | |||
|
386 | 404 | # pure insertion, check nearby lines. ignore lines belong |
|
387 | 405 | # to the public (first) changeset (i.e. annotated[i][0] == 1) |
|
388 | 406 | nearbylinenums = {a2, max(0, a1 - 1)} |
|
389 |
involved = [ |
|
|
390 |
|
|
|
407 | involved = [ | |
|
408 | annotated[i] for i in nearbylinenums if annotated[i][0] != 1 | |
|
409 | ] | |
|
391 | 410 | involvedrevs = list(set(r for r, l in involved)) |
|
392 | 411 | newfixups = [] |
|
393 | 412 | if len(involvedrevs) == 1 and self._iscontinuous(a1, a2 - 1, True): |
@@ -448,18 +467,26 b' class filefixupstate(object):' | |||
|
448 | 467 | """() -> [str]. prompt all lines for edit""" |
|
449 | 468 | alllines = self.linelog.getalllines() |
|
450 | 469 | # header |
|
451 | editortext = (_('HG: editing %s\nHG: "y" means the line to the right ' | |
|
452 | 'exists in the changeset to the top\nHG:\n') | |
|
453 | % self.fctxs[-1].path()) | |
|
470 | editortext = ( | |
|
471 | _( | |
|
472 | 'HG: editing %s\nHG: "y" means the line to the right ' | |
|
473 | 'exists in the changeset to the top\nHG:\n' | |
|
474 | ) | |
|
475 | % self.fctxs[-1].path() | |
|
476 | ) | |
|
454 | 477 | # [(idx, fctx)]. hide the dummy emptyfilecontext |
|
455 |
visiblefctxs = [ |
|
|
478 | visiblefctxs = [ | |
|
479 | (i, f) | |
|
456 | 480 |
|
|
457 |
|
|
|
481 | if not isinstance(f, emptyfilecontext) | |
|
482 | ] | |
|
458 | 483 | for i, (j, f) in enumerate(visiblefctxs): |
|
459 |
editortext += |
|
|
460 | ('|' * i, '-' * (len(visiblefctxs) - i + 1), | |
|
484 | editortext += _('HG: %s/%s %s %s\n') % ( | |
|
485 | '|' * i, | |
|
486 | '-' * (len(visiblefctxs) - i + 1), | |
|
461 | 487 |
|
|
462 |
|
|
|
488 | f.description().split('\n', 1)[0], | |
|
489 | ) | |
|
463 | 490 | editortext += _('HG: %s\n') % ('|' * len(visiblefctxs)) |
|
464 | 491 | # figure out the lifetime of a line, this is relatively inefficient, |
|
465 | 492 | # but probably fine |
@@ -470,10 +497,15 b' class filefixupstate(object):' | |||
|
470 | 497 | lineset[l].add(i) |
|
471 | 498 | # append lines |
|
472 | 499 | for l in alllines: |
|
473 |
editortext += |
|
|
474 | (''.join([('y' if i in lineset[l] else ' ') | |
|
475 | for i, _f in visiblefctxs]), | |
|
476 |
|
|
|
500 | editortext += ' %s : %s' % ( | |
|
501 | ''.join( | |
|
502 | [ | |
|
503 | ('y' if i in lineset[l] else ' ') | |
|
504 | for i, _f in visiblefctxs | |
|
505 | ] | |
|
506 | ), | |
|
507 | self._getline(l), | |
|
508 | ) | |
|
477 | 509 | # run editor |
|
478 | 510 | editedtext = self.ui.edit(editortext, '', action='absorb') |
|
479 | 511 | if not editedtext: |
@@ -489,7 +521,8 b' class filefixupstate(object):' | |||
|
489 | 521 | raise error.Abort(_('malformed line: %s') % l) |
|
490 | 522 | linecontent = l[colonpos + 2:] |
|
491 | 523 | for i, ch in enumerate( |
|
492 |
|
|
|
524 | pycompat.bytestr(l[leftpadpos : colonpos - 1]) | |
|
525 | ): | |
|
493 | 526 | if ch == 'y': |
|
494 | 527 | contents[visiblefctxs[i][0]] += linecontent |
|
495 | 528 | # chunkstats is hard to calculate if anything changes, therefore |
@@ -539,8 +572,12 b' class filefixupstate(object):' | |||
|
539 | 572 | lastrev = pcurrentchunk[0][0] |
|
540 | 573 | lasta2 = pcurrentchunk[0][2] |
|
541 | 574 | lastb2 = pcurrentchunk[0][4] |
|
542 | if (a1 == lasta2 and b1 == lastb2 and rev == lastrev and | |
|
543 | self._iscontinuous(max(a1 - 1, 0), a1)): | |
|
575 | if ( | |
|
576 | a1 == lasta2 | |
|
577 | and b1 == lastb2 | |
|
578 | and rev == lastrev | |
|
579 | and self._iscontinuous(max(a1 - 1, 0), a1) | |
|
580 | ): | |
|
544 | 581 | # merge into currentchunk |
|
545 | 582 | pcurrentchunk[0][2] = a2 |
|
546 | 583 | pcurrentchunk[0][4] = b2 |
@@ -551,7 +588,6 b' class filefixupstate(object):' | |||
|
551 | 588 | return result |
|
552 | 589 | |
|
553 | 590 | def _showchanges(self, fm, alines, blines, chunk, fixups): |
|
554 | ||
|
555 | 591 | def trim(line): |
|
556 | 592 | if line.endswith('\n'): |
|
557 | 593 | line = line[:-1] |
@@ -568,9 +604,12 b' class filefixupstate(object):' | |||
|
568 | 604 | bidxs[i - b1] = (max(idx, 1) - 1) // 2 |
|
569 | 605 | |
|
570 | 606 | fm.startitem() |
|
571 | fm.write('hunk', ' %s\n', | |
|
572 | '@@ -%d,%d +%d,%d @@' | |
|
573 | % (a1, a2 - a1, b1, b2 - b1), label='diff.hunk') | |
|
607 | fm.write( | |
|
608 | 'hunk', | |
|
609 | ' %s\n', | |
|
610 | '@@ -%d,%d +%d,%d @@' % (a1, a2 - a1, b1, b2 - b1), | |
|
611 | label='diff.hunk', | |
|
612 | ) | |
|
574 | 613 | fm.data(path=self.path, linetype='hunk') |
|
575 | 614 | |
|
576 | 615 | def writeline(idx, diffchar, line, linetype, linelabel): |
@@ -582,16 +621,24 b' class filefixupstate(object):' | |||
|
582 | 621 | node = ctx.hex() |
|
583 | 622 | self.ctxaffected.add(ctx.changectx()) |
|
584 | 623 | fm.write('node', '%-7.7s ', node, label='absorb.node') |
|
585 | fm.write('diffchar ' + linetype, '%s%s\n', diffchar, line, | |
|
586 |
|
|
|
624 | fm.write( | |
|
625 | 'diffchar ' + linetype, | |
|
626 | '%s%s\n', | |
|
627 | diffchar, | |
|
628 | line, | |
|
629 | label=linelabel, | |
|
630 | ) | |
|
587 | 631 | fm.data(path=self.path, linetype=linetype) |
|
588 | 632 | |
|
589 | 633 | for i in pycompat.xrange(a1, a2): |
|
590 | writeline(aidxs[i - a1], '-', trim(alines[i]), 'deleted', | |
|
591 |
'diff.deleted' |
|
|
634 | writeline( | |
|
635 | aidxs[i - a1], '-', trim(alines[i]), 'deleted', 'diff.deleted' | |
|
636 | ) | |
|
592 | 637 | for i in pycompat.xrange(b1, b2): |
|
593 | writeline(bidxs[i - b1], '+', trim(blines[i]), 'inserted', | |
|
594 |
'diff.inserted' |
|
|
638 | writeline( | |
|
639 | bidxs[i - b1], '+', trim(blines[i]), 'inserted', 'diff.inserted' | |
|
640 | ) | |
|
641 | ||
|
595 | 642 | |
|
596 | 643 | class fixupstate(object): |
|
597 | 644 | """state needed to run absorb |
@@ -648,9 +695,11 b' class fixupstate(object):' | |||
|
648 | 695 | targetfctx = targetctx[path] |
|
649 | 696 | fctxs, ctx2fctx = getfilestack(self.stack, path, seenfctxs) |
|
650 | 697 | # ignore symbolic links or binary, or unchanged files |
|
651 | if any(f.islink() or stringutil.binary(f.data()) | |
|
698 | if any( | |
|
699 | f.islink() or stringutil.binary(f.data()) | |
|
652 | 700 |
|
|
653 |
|
|
|
701 | if not isinstance(f, emptyfilecontext) | |
|
702 | ): | |
|
654 | 703 | continue |
|
655 | 704 | if targetfctx.data() == fctxs[-1].data() and not editopt: |
|
656 | 705 | continue |
@@ -677,8 +726,10 b' class fixupstate(object):' | |||
|
677 | 726 | @property |
|
678 | 727 | def chunkstats(self): |
|
679 | 728 | """-> {path: chunkstats}. collect chunkstats from filefixupstates""" |
|
680 |
return dict( |
|
|
681 | for path, state in self.fixupmap.iteritems()) | |
|
729 | return dict( | |
|
730 | (path, state.chunkstats) | |
|
731 | for path, state in self.fixupmap.iteritems() | |
|
732 | ) | |
|
682 | 733 | |
|
683 | 734 | def commit(self): |
|
684 | 735 | """commit changes. update self.finalnode, self.replacemap""" |
@@ -698,8 +749,10 b' class fixupstate(object):' | |||
|
698 | 749 | # chunkstats for each file |
|
699 | 750 | for path, stat in chunkstats.iteritems(): |
|
700 | 751 | if stat[0]: |
|
701 |
ui.write( |
|
|
702 | % (path, stat[0], stat[1])) | |
|
752 | ui.write( | |
|
753 | _('%s: %d of %d chunk(s) applied\n') | |
|
754 | % (path, stat[0], stat[1]) | |
|
755 | ) | |
|
703 | 756 | elif not ui.quiet: |
|
704 | 757 | # a summary for all files |
|
705 | 758 | stats = chunkstats.values() |
@@ -733,7 +786,9 b' class fixupstate(object):' | |||
|
733 | 786 | self.replacemap[ctx.node()] = lastcommitted.node() |
|
734 | 787 | if memworkingcopy: |
|
735 | 788 | msg = _('%d file(s) changed, became %s') % ( |
|
736 |
len(memworkingcopy), |
|
|
789 | len(memworkingcopy), | |
|
790 | self._ctx2str(lastcommitted), | |
|
791 | ) | |
|
737 | 792 | else: |
|
738 | 793 | msg = _('became %s') % self._ctx2str(lastcommitted) |
|
739 | 794 | if self.ui.verbose and msg: |
@@ -766,16 +821,19 b' class fixupstate(object):' | |||
|
766 | 821 | |
|
767 | 822 | def _movebookmarks(self, tr): |
|
768 | 823 | repo = self.repo |
|
769 |
needupdate = [ |
|
|
824 | needupdate = [ | |
|
825 | (name, self.replacemap[hsh]) | |
|
770 | 826 |
|
|
771 |
|
|
|
827 | if hsh in self.replacemap | |
|
828 | ] | |
|
772 | 829 | changes = [] |
|
773 | 830 | for name, hsh in needupdate: |
|
774 | 831 | if hsh: |
|
775 | 832 | changes.append((name, hsh)) |
|
776 | 833 | if self.ui.verbose: |
|
777 |
self.ui.write( |
|
|
778 |
|
|
|
834 | self.ui.write( | |
|
835 | _('moving bookmark %s to %s\n') % (name, node.hex(hsh)) | |
|
836 | ) | |
|
779 | 837 | else: |
|
780 | 838 | changes.append((name, None)) |
|
781 | 839 | if self.ui.verbose: |
@@ -798,8 +856,10 b' class fixupstate(object):' | |||
|
798 | 856 | restore = noop |
|
799 | 857 | if util.safehasattr(dirstate, '_fsmonitorstate'): |
|
800 | 858 | bak = dirstate._fsmonitorstate.invalidate |
|
859 | ||
|
801 | 860 | def restore(): |
|
802 | 861 | dirstate._fsmonitorstate.invalidate = bak |
|
862 | ||
|
803 | 863 | dirstate._fsmonitorstate.invalidate = noop |
|
804 | 864 | try: |
|
805 | 865 | with dirstate.parentchange(): |
@@ -852,11 +912,15 b' class fixupstate(object):' | |||
|
852 | 912 | return obsolete.isenabled(self.repo, obsolete.createmarkersopt) |
|
853 | 913 | |
|
854 | 914 | def _cleanupoldcommits(self): |
|
855 | replacements = {k: ([v] if v is not None else []) | |
|
856 | for k, v in self.replacemap.iteritems()} | |
|
915 | replacements = { | |
|
916 | k: ([v] if v is not None else []) | |
|
917 | for k, v in self.replacemap.iteritems() | |
|
918 | } | |
|
857 | 919 | if replacements: |
|
858 |
scmutil.cleanupnodes( |
|
|
859 | fixphase=True) | |
|
920 | scmutil.cleanupnodes( | |
|
921 | self.repo, replacements, operation='absorb', fixphase=True | |
|
922 | ) | |
|
923 | ||
|
860 | 924 | |
|
861 | 925 | def _parsechunk(hunk): |
|
862 | 926 | """(crecord.uihunk or patch.recordhunk) -> (path, (a1, a2, [bline]))""" |
@@ -874,6 +938,7 b' def _parsechunk(hunk):' | |||
|
874 | 938 | blines = [l[1:] for l in patchlines[1:] if not l.startswith('-')] |
|
875 | 939 | return path, (a1, a2, blines) |
|
876 | 940 | |
|
941 | ||
|
877 | 942 | def overlaydiffcontext(ctx, chunks): |
|
878 | 943 | """(ctx, [crecord.uihunk]) -> memctx |
|
879 | 944 | |
@@ -905,6 +970,7 b' def overlaydiffcontext(ctx, chunks):' | |||
|
905 | 970 | memworkingcopy[path] = ''.join(lines) |
|
906 | 971 | return overlaycontext(memworkingcopy, ctx) |
|
907 | 972 | |
|
973 | ||
|
908 | 974 | def absorb(ui, repo, stack=None, targetctx=None, pats=None, opts=None): |
|
909 | 975 | """pick fixup chunks from targetctx, apply them to stack. |
|
910 | 976 | |
@@ -919,9 +985,10 b' def absorb(ui, repo, stack=None, targetc' | |||
|
919 | 985 | raise error.Abort(_('cannot absorb into a merge')) |
|
920 | 986 | stack = getdraftstack(headctx, limit) |
|
921 | 987 | if limit and len(stack) >= limit: |
|
922 | ui.warn(_('absorb: only the recent %d changesets will ' | |
|
923 | 'be analysed\n') | |
|
924 |
|
|
|
988 | ui.warn( | |
|
989 | _('absorb: only the recent %d changesets will ' 'be analysed\n') | |
|
990 | % limit | |
|
991 | ) | |
|
925 | 992 | if not stack: |
|
926 | 993 | raise error.Abort(_('no mutable changeset to change')) |
|
927 | 994 | if targetctx is None: # default to working copy |
@@ -953,13 +1020,19 b' def absorb(ui, repo, stack=None, targetc' | |||
|
953 | 1020 | fm.data(linetype='changeset') |
|
954 | 1021 | fm.write('node', '%-7.7s ', ctx.hex(), label='absorb.node') |
|
955 | 1022 | descfirstline = ctx.description().splitlines()[0] |
|
956 | fm.write('descfirstline', '%s\n', descfirstline, | |
|
957 |
|
|
|
1023 | fm.write( | |
|
1024 | 'descfirstline', | |
|
1025 | '%s\n', | |
|
1026 | descfirstline, | |
|
1027 | label='absorb.description', | |
|
1028 | ) | |
|
958 | 1029 | fm.end() |
|
959 | 1030 | if not opts.get('dry_run'): |
|
960 | if (not opts.get('apply_changes') and | |
|
961 | state.ctxaffected and | |
|
962 | ui.promptchoice("apply changes (yn)? $$ &Yes $$ &No", default=1)): | |
|
1031 | if ( | |
|
1032 | not opts.get('apply_changes') | |
|
1033 | and state.ctxaffected | |
|
1034 | and ui.promptchoice("apply changes (yn)? $$ &Yes $$ &No", default=1) | |
|
1035 | ): | |
|
963 | 1036 | raise error.Abort(_('absorb cancelled\n')) |
|
964 | 1037 | |
|
965 | 1038 | state.apply() |
@@ -969,20 +1042,45 b' def absorb(ui, repo, stack=None, targetc' | |||
|
969 | 1042 | ui.write(_('nothing applied\n')) |
|
970 | 1043 | return state |
|
971 | 1044 | |
|
972 | @command('absorb', | |
|
973 | [('a', 'apply-changes', None, | |
|
974 | _('apply changes without prompting for confirmation')), | |
|
975 | ('p', 'print-changes', None, | |
|
976 | _('always print which changesets are modified by which changes')), | |
|
977 | ('i', 'interactive', None, | |
|
978 | _('interactively select which chunks to apply (EXPERIMENTAL)')), | |
|
979 |
|
|
|
980 | _('edit what lines belong to which changesets before commit ' | |
|
981 | '(EXPERIMENTAL)')), | |
|
982 | ] + commands.dryrunopts + commands.templateopts + commands.walkopts, | |
|
1045 | ||
|
1046 | @command( | |
|
1047 | 'absorb', | |
|
1048 | [ | |
|
1049 | ( | |
|
1050 | 'a', | |
|
1051 | 'apply-changes', | |
|
1052 | None, | |
|
1053 | _('apply changes without prompting for confirmation'), | |
|
1054 | ), | |
|
1055 | ( | |
|
1056 | 'p', | |
|
1057 | 'print-changes', | |
|
1058 | None, | |
|
1059 | _('always print which changesets are modified by which changes'), | |
|
1060 | ), | |
|
1061 | ( | |
|
1062 | 'i', | |
|
1063 | 'interactive', | |
|
1064 | None, | |
|
1065 | _('interactively select which chunks to apply (EXPERIMENTAL)'), | |
|
1066 | ), | |
|
1067 | ( | |
|
1068 | 'e', | |
|
1069 | 'edit-lines', | |
|
1070 | None, | |
|
1071 | _( | |
|
1072 | 'edit what lines belong to which changesets before commit ' | |
|
1073 | '(EXPERIMENTAL)' | |
|
1074 | ), | |
|
1075 | ), | |
|
1076 | ] | |
|
1077 | + commands.dryrunopts | |
|
1078 | + commands.templateopts | |
|
1079 | + commands.walkopts, | |
|
983 | 1080 |
|
|
984 | 1081 |
|
|
985 |
|
|
|
1082 | helpbasic=True, | |
|
1083 | ) | |
|
986 | 1084 | def absorbcmd(ui, repo, *pats, **opts): |
|
987 | 1085 | """incorporate corrections into the stack of draft changesets |
|
988 | 1086 |
@@ -224,9 +224,7 b' from mercurial import (' | |||
|
224 | 224 | registrar, |
|
225 | 225 | util, |
|
226 | 226 | ) |
|
227 |
from mercurial.utils import |
|
|
228 | procutil, | |
|
229 | ) | |
|
227 | from mercurial.utils import procutil | |
|
230 | 228 | |
|
231 | 229 | urlreq = util.urlreq |
|
232 | 230 | |
@@ -240,33 +238,29 b' configtable = {}' | |||
|
240 | 238 | configitem = registrar.configitem(configtable) |
|
241 | 239 | |
|
242 | 240 | # deprecated config: acl.config |
|
243 | configitem('acl', 'config', | |
|
244 | default=None, | |
|
241 | configitem( | |
|
242 | 'acl', 'config', default=None, | |
|
245 | 243 | ) |
|
246 | configitem('acl.groups', '.*', | |
|
247 | default=None, | |
|
248 | generic=True, | |
|
244 | configitem( | |
|
245 | 'acl.groups', '.*', default=None, generic=True, | |
|
249 | 246 | ) |
|
250 | configitem('acl.deny.branches', '.*', | |
|
251 | default=None, | |
|
252 | generic=True, | |
|
247 | configitem( | |
|
248 | 'acl.deny.branches', '.*', default=None, generic=True, | |
|
253 | 249 | ) |
|
254 | configitem('acl.allow.branches', '.*', | |
|
255 | default=None, | |
|
256 | generic=True, | |
|
250 | configitem( | |
|
251 | 'acl.allow.branches', '.*', default=None, generic=True, | |
|
257 | 252 | ) |
|
258 | configitem('acl.deny', '.*', | |
|
259 | default=None, | |
|
260 | generic=True, | |
|
253 | configitem( | |
|
254 | 'acl.deny', '.*', default=None, generic=True, | |
|
261 | 255 | ) |
|
262 | configitem('acl.allow', '.*', | |
|
263 | default=None, | |
|
264 | generic=True, | |
|
256 | configitem( | |
|
257 | 'acl.allow', '.*', default=None, generic=True, | |
|
265 | 258 | ) |
|
266 | configitem('acl', 'sources', | |
|
267 | default=lambda: ['serve'], | |
|
259 | configitem( | |
|
260 | 'acl', 'sources', default=lambda: ['serve'], | |
|
268 | 261 | ) |
|
269 | 262 | |
|
263 | ||
|
270 | 264 | def _getusers(ui, group): |
|
271 | 265 | |
|
272 | 266 | # First, try to use group definition from section [acl.groups] |
@@ -281,6 +275,7 b' def _getusers(ui, group):' | |||
|
281 | 275 | except KeyError: |
|
282 | 276 | raise error.Abort(_("group '%s' is undefined") % group) |
|
283 | 277 | |
|
278 | ||
|
284 | 279 | def _usermatch(ui, user, usersorgroups): |
|
285 | 280 | |
|
286 | 281 | if usersorgroups == '*': |
@@ -293,29 +288,35 b' def _usermatch(ui, user, usersorgroups):' | |||
|
293 | 288 | # if ug is a user name: !username |
|
294 | 289 | # if ug is a group name: !@groupname |
|
295 | 290 | ug = ug[1:] |
|
296 | if (not ug.startswith('@') and user != ug | |
|
297 |
|
|
|
291 | if ( | |
|
292 | not ug.startswith('@') | |
|
293 | and user != ug | |
|
294 | or ug.startswith('@') | |
|
295 | and user not in _getusers(ui, ug[1:]) | |
|
296 | ): | |
|
298 | 297 | return True |
|
299 | 298 | |
|
300 | 299 | # Test for user or group. Format: |
|
301 | 300 | # if ug is a user name: username |
|
302 | 301 | # if ug is a group name: @groupname |
|
303 | elif (user == ug | |
|
304 | or ug.startswith('@') and user in _getusers(ui, ug[1:])): | |
|
302 | elif user == ug or ug.startswith('@') and user in _getusers(ui, ug[1:]): | |
|
305 | 303 | return True |
|
306 | 304 | |
|
307 | 305 | return False |
|
308 | 306 | |
|
307 | ||
|
309 | 308 | def buildmatch(ui, repo, user, key): |
|
310 | 309 | '''return tuple of (match function, list enabled).''' |
|
311 | 310 | if not ui.has_section(key): |
|
312 | 311 | ui.debug('acl: %s not enabled\n' % key) |
|
313 | 312 | return None |
|
314 | 313 | |
|
315 | pats = [pat for pat, users in ui.configitems(key) | |
|
316 |
if _usermatch(ui, user, users) |
|
|
317 | ui.debug('acl: %s enabled, %d entries for user %s\n' % | |
|
318 | (key, len(pats), user)) | |
|
314 | pats = [ | |
|
315 | pat for pat, users in ui.configitems(key) if _usermatch(ui, user, users) | |
|
316 | ] | |
|
317 | ui.debug( | |
|
318 | 'acl: %s enabled, %d entries for user %s\n' % (key, len(pats), user) | |
|
319 | ) | |
|
319 | 320 | |
|
320 | 321 | # Branch-based ACL |
|
321 | 322 | if not repo: |
@@ -332,6 +333,7 b' def buildmatch(ui, repo, user, key):' | |||
|
332 | 333 | return match.match(repo.root, '', pats) |
|
333 | 334 | return util.never |
|
334 | 335 | |
|
336 | ||
|
335 | 337 | def ensureenabled(ui): |
|
336 | 338 | """make sure the extension is enabled when used as hook |
|
337 | 339 | |
@@ -345,16 +347,22 b' def ensureenabled(ui):' | |||
|
345 | 347 | ui.setconfig('extensions', 'acl', '', source='internal') |
|
346 | 348 | extensions.loadall(ui, ['acl']) |
|
347 | 349 | |
|
350 | ||
|
348 | 351 | def hook(ui, repo, hooktype, node=None, source=None, **kwargs): |
|
349 | 352 | |
|
350 | 353 | ensureenabled(ui) |
|
351 | 354 | |
|
352 | 355 | if hooktype not in ['pretxnchangegroup', 'pretxncommit', 'prepushkey']: |
|
353 | 356 | raise error.Abort( |
|
354 | _('config error - hook type "%s" cannot stop ' | |
|
355 | 'incoming changesets, commits, nor bookmarks') % hooktype) | |
|
356 | if (hooktype == 'pretxnchangegroup' and | |
|
357 | source not in ui.configlist('acl', 'sources')): | |
|
357 | _( | |
|
358 | 'config error - hook type "%s" cannot stop ' | |
|
359 | 'incoming changesets, commits, nor bookmarks' | |
|
360 | ) | |
|
361 | % hooktype | |
|
362 | ) | |
|
363 | if hooktype == 'pretxnchangegroup' and source not in ui.configlist( | |
|
364 | 'acl', 'sources' | |
|
365 | ): | |
|
358 | 366 | ui.debug('acl: changes have source "%s" - skipping\n' % source) |
|
359 | 367 | return |
|
360 | 368 | |
@@ -374,6 +382,7 b' def hook(ui, repo, hooktype, node=None, ' | |||
|
374 | 382 | else: |
|
375 | 383 | _txnhook(ui, repo, hooktype, node, source, user, **kwargs) |
|
376 | 384 | |
|
385 | ||
|
377 | 386 | def _pkhook(ui, repo, hooktype, node, source, user, **kwargs): |
|
378 | 387 | if kwargs[r'namespace'] == 'bookmarks': |
|
379 | 388 | bookmark = kwargs[r'key'] |
@@ -382,22 +391,38 b' def _pkhook(ui, repo, hooktype, node, so' | |||
|
382 | 391 | denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks') |
|
383 | 392 | |
|
384 | 393 | if denybookmarks and denybookmarks(bookmark): |
|
385 | raise error.Abort(_('acl: user "%s" denied on bookmark "%s"' | |
|
386 |
|
|
|
387 |
|
|
|
394 | raise error.Abort( | |
|
395 | _('acl: user "%s" denied on bookmark "%s"' ' (changeset "%s")') | |
|
396 | % (user, bookmark, ctx) | |
|
397 | ) | |
|
388 | 398 | if allowbookmarks and not allowbookmarks(bookmark): |
|
389 | raise error.Abort(_('acl: user "%s" not allowed on bookmark "%s"' | |
|
390 | ' (changeset "%s")') | |
|
391 | % (user, bookmark, ctx)) | |
|
392 | ui.debug('acl: bookmark access granted: "%s" on bookmark "%s"\n' | |
|
393 |
|
|
|
399 | raise error.Abort( | |
|
400 | _( | |
|
401 | 'acl: user "%s" not allowed on bookmark "%s"' | |
|
402 | ' (changeset "%s")' | |
|
403 | ) | |
|
404 | % (user, bookmark, ctx) | |
|
405 | ) | |
|
406 | ui.debug( | |
|
407 | 'acl: bookmark access granted: "%s" on bookmark "%s"\n' | |
|
408 | % (ctx, bookmark) | |
|
409 | ) | |
|
410 | ||
|
394 | 411 | |
|
395 | 412 | def _txnhook(ui, repo, hooktype, node, source, user, **kwargs): |
|
396 | 413 | # deprecated config: acl.config |
|
397 | 414 | cfg = ui.config('acl', 'config') |
|
398 | 415 | if cfg: |
|
399 | ui.readconfig(cfg, sections=['acl.groups', 'acl.allow.branches', | |
|
400 | 'acl.deny.branches', 'acl.allow', 'acl.deny']) | |
|
416 | ui.readconfig( | |
|
417 | cfg, | |
|
418 | sections=[ | |
|
419 | 'acl.groups', | |
|
420 | 'acl.allow.branches', | |
|
421 | 'acl.deny.branches', | |
|
422 | 'acl.allow', | |
|
423 | 'acl.deny', | |
|
424 | ], | |
|
425 | ) | |
|
401 | 426 | |
|
402 | 427 | allowbranches = buildmatch(ui, None, user, 'acl.allow.branches') |
|
403 | 428 | denybranches = buildmatch(ui, None, user, 'acl.deny.branches') |
@@ -408,21 +433,31 b' def _txnhook(ui, repo, hooktype, node, s' | |||
|
408 | 433 | ctx = repo[rev] |
|
409 | 434 | branch = ctx.branch() |
|
410 | 435 | if denybranches and denybranches(branch): |
|
411 |
raise error.Abort( |
|
|
412 |
|
|
|
413 |
|
|
|
436 | raise error.Abort( | |
|
437 | _('acl: user "%s" denied on branch "%s"' ' (changeset "%s")') | |
|
438 | % (user, branch, ctx) | |
|
439 | ) | |
|
414 | 440 | if allowbranches and not allowbranches(branch): |
|
415 | raise error.Abort(_('acl: user "%s" not allowed on branch "%s"' | |
|
416 | ' (changeset "%s")') | |
|
417 | % (user, branch, ctx)) | |
|
418 | ui.debug('acl: branch access granted: "%s" on branch "%s"\n' | |
|
419 | % (ctx, branch)) | |
|
441 | raise error.Abort( | |
|
442 | _( | |
|
443 | 'acl: user "%s" not allowed on branch "%s"' | |
|
444 | ' (changeset "%s")' | |
|
445 | ) | |
|
446 | % (user, branch, ctx) | |
|
447 | ) | |
|
448 | ui.debug( | |
|
449 | 'acl: branch access granted: "%s" on branch "%s"\n' % (ctx, branch) | |
|
450 | ) | |
|
420 | 451 | |
|
421 | 452 | for f in ctx.files(): |
|
422 | 453 | if deny and deny(f): |
|
423 |
raise error.Abort( |
|
|
424 | ' (changeset "%s")') % (user, f, ctx)) | |
|
454 | raise error.Abort( | |
|
455 | _('acl: user "%s" denied on "%s"' ' (changeset "%s")') | |
|
456 | % (user, f, ctx) | |
|
457 | ) | |
|
425 | 458 | if allow and not allow(f): |
|
426 |
raise error.Abort( |
|
|
427 | ' (changeset "%s")') % (user, f, ctx)) | |
|
459 | raise error.Abort( | |
|
460 | _('acl: user "%s" not allowed on "%s"' ' (changeset "%s")') | |
|
461 | % (user, f, ctx) | |
|
462 | ) | |
|
428 | 463 | ui.debug('acl: path access granted: "%s"\n' % ctx) |
@@ -29,20 +29,35 b" testedwith = 'ships-with-hg-core'" | |||
|
29 | 29 | cmdtable = {} |
|
30 | 30 | command = registrar.command(cmdtable) |
|
31 | 31 | |
|
32 | @command('amend', | |
|
33 | [('A', 'addremove', None, | |
|
34 | _('mark new/missing files as added/removed before committing')), | |
|
32 | ||
|
33 | @command( | |
|
34 | 'amend', | |
|
35 | [ | |
|
36 | ( | |
|
37 | 'A', | |
|
38 | 'addremove', | |
|
39 | None, | |
|
40 | _('mark new/missing files as added/removed before committing'), | |
|
41 | ), | |
|
35 | 42 | ('e', 'edit', None, _('invoke editor on commit messages')), |
|
36 | 43 | ('i', 'interactive', None, _('use interactive mode')), |
|
37 | (b'', b'close-branch', None, | |
|
38 | _(b'mark a branch as closed, hiding it from the branch list')), | |
|
44 | ( | |
|
45 | b'', | |
|
46 | b'close-branch', | |
|
47 | None, | |
|
48 | _(b'mark a branch as closed, hiding it from the branch list'), | |
|
49 | ), | |
|
39 | 50 | (b's', b'secret', None, _(b'use the secret phase for committing')), |
|
40 | 51 | ('n', 'note', '', _('store a note on the amend')), |
|
41 | ] + cmdutil.walkopts + cmdutil.commitopts + cmdutil.commitopts2 | |
|
52 | ] | |
|
53 | + cmdutil.walkopts | |
|
54 | + cmdutil.commitopts | |
|
55 | + cmdutil.commitopts2 | |
|
42 | 56 |
|
|
43 | 57 | _('[OPTION]... [FILE]...'), |
|
44 | 58 | helpcategory=command.CATEGORY_COMMITTING, |
|
45 |
inferrepo=True |
|
|
59 | inferrepo=True, | |
|
60 | ) | |
|
46 | 61 | def amend(ui, repo, *pats, **opts): |
|
47 | 62 | """amend the working copy parent with all or specified outstanding changes |
|
48 | 63 |
@@ -35,22 +35,23 b' from mercurial import (' | |||
|
35 | 35 | pycompat, |
|
36 | 36 | registrar, |
|
37 | 37 | scmutil, |
|
38 | similar | |
|
38 | similar, | |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | configtable = {} |
|
42 | 42 | configitem = registrar.configitem(configtable) |
|
43 | 43 | |
|
44 | configitem('automv', 'similarity', | |
|
45 | default=95, | |
|
44 | configitem( | |
|
45 | 'automv', 'similarity', default=95, | |
|
46 | 46 | ) |
|
47 | 47 | |
|
48 | ||
|
48 | 49 | def extsetup(ui): |
|
49 | entry = extensions.wrapcommand( | |
|
50 | commands.table, 'commit', mvcheck) | |
|
50 | entry = extensions.wrapcommand(commands.table, 'commit', mvcheck) | |
|
51 | 51 | entry[1].append( |
|
52 | ('', 'no-automv', None, | |
|
53 | _('disable automatic file move detection'))) | |
|
52 | ('', 'no-automv', None, _('disable automatic file move detection')) | |
|
53 | ) | |
|
54 | ||
|
54 | 55 | |
|
55 | 56 | def mvcheck(orig, ui, repo, *pats, **opts): |
|
56 | 57 | """Hook to check for moves at commit time""" |
@@ -65,14 +66,16 b' def mvcheck(orig, ui, repo, *pats, **opt' | |||
|
65 | 66 | match = scmutil.match(repo[None], pats, opts) |
|
66 | 67 | added, removed = _interestingfiles(repo, match) |
|
67 | 68 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) |
|
68 |
renames = _findrenames( |
|
|
69 |
|
|
|
69 | renames = _findrenames( | |
|
70 | repo, uipathfn, added, removed, threshold / 100.0 | |
|
71 | ) | |
|
70 | 72 | |
|
71 | 73 | with repo.wlock(): |
|
72 | 74 | if renames is not None: |
|
73 | 75 | scmutil._markchanges(repo, (), (), renames) |
|
74 | 76 | return orig(ui, repo, *pats, **pycompat.strkwargs(opts)) |
|
75 | 77 | |
|
78 | ||
|
76 | 79 | def _interestingfiles(repo, matcher): |
|
77 | 80 | """Find what files were added or removed in this commit. |
|
78 | 81 | |
@@ -90,6 +93,7 b' def _interestingfiles(repo, matcher):' | |||
|
90 | 93 | |
|
91 | 94 | return added, removed |
|
92 | 95 | |
|
96 | ||
|
93 | 97 | def _findrenames(repo, uipathfn, added, removed, similarity): |
|
94 | 98 | """Find what files in added are really moved files. |
|
95 | 99 | |
@@ -100,11 +104,13 b' def _findrenames(repo, uipathfn, added, ' | |||
|
100 | 104 | renames = {} |
|
101 | 105 | if similarity > 0: |
|
102 | 106 | for src, dst, score in similar.findrenames( |
|
103 |
|
|
|
107 | repo, added, removed, similarity | |
|
108 | ): | |
|
104 | 109 | if repo.ui.verbose: |
|
105 | 110 | repo.ui.status( |
|
106 |
_('detected move of %s as %s (%d%% similar)\n') |
|
|
107 |
|
|
|
111 | _('detected move of %s as %s (%d%% similar)\n') | |
|
112 | % (uipathfn(src), uipathfn(dst), score * 100) | |
|
113 | ) | |
|
108 | 114 | renames[dst] = src |
|
109 | 115 | if renames: |
|
110 | 116 | repo.ui.status(_('detected move of %d files\n') % len(renames)) |
@@ -28,6 +28,7 b' from mercurial import (' | |||
|
28 | 28 | # leave the attribute unspecified. |
|
29 | 29 | testedwith = 'ships-with-hg-core' |
|
30 | 30 | |
|
31 | ||
|
31 | 32 | def prettyedge(before, edge, after): |
|
32 | 33 | if edge == '~': |
|
33 | 34 | return '\xE2\x95\xA7' # U+2567 ╧ |
@@ -49,15 +50,21 b' def prettyedge(before, edge, after):' | |||
|
49 | 50 | return '\xE2\x94\xBC' # U+253C ┼ |
|
50 | 51 | return edge |
|
51 | 52 | |
|
53 | ||
|
52 | 54 | def convertedges(line): |
|
53 | 55 | line = ' %s ' % line |
|
54 | 56 | pretty = [] |
|
55 | 57 | for idx in pycompat.xrange(len(line) - 2): |
|
56 |
pretty.append( |
|
|
58 | pretty.append( | |
|
59 | prettyedge( | |
|
60 | line[idx : idx + 1], | |
|
57 | 61 |
|
|
58 |
|
|
|
62 | line[idx + 2 : idx + 3], | |
|
63 | ) | |
|
64 | ) | |
|
59 | 65 | return ''.join(pretty) |
|
60 | 66 | |
|
67 | ||
|
61 | 68 | def getprettygraphnode(orig, *args, **kwargs): |
|
62 | 69 | node = orig(*args, **kwargs) |
|
63 | 70 | if node == 'o': |
@@ -72,11 +79,13 b' def getprettygraphnode(orig, *args, **kw' | |||
|
72 | 79 | return '\xE2\x95\xA4' # U+2564 ╤ |
|
73 | 80 | return node |
|
74 | 81 | |
|
82 | ||
|
75 | 83 | def outputprettygraph(orig, ui, graph, *args, **kwargs): |
|
76 | 84 | (edges, text) = zip(*graph) |
|
77 | 85 | graph = zip([convertedges(e) for e in edges], text) |
|
78 | 86 | return orig(ui, graph, *args, **kwargs) |
|
79 | 87 | |
|
88 | ||
|
80 | 89 | def extsetup(ui): |
|
81 | 90 | if ui.plain('graph'): |
|
82 | 91 | return |
@@ -86,8 +95,12 b' def extsetup(ui):' | |||
|
86 | 95 | return |
|
87 | 96 | |
|
88 | 97 | if r'A' in encoding._wide: |
|
89 | ui.warn(_('beautifygraph: unsupported terminal settings, ' | |
|
90 | 'monospace narrow text required\n')) | |
|
98 | ui.warn( | |
|
99 | _( | |
|
100 | 'beautifygraph: unsupported terminal settings, ' | |
|
101 | 'monospace narrow text required\n' | |
|
102 | ) | |
|
103 | ) | |
|
91 | 104 | return |
|
92 | 105 | |
|
93 | 106 | extensions.wrapfunction(graphmod, 'outputgraph', outputprettygraph) |
@@ -71,30 +71,33 b' command = registrar.command(cmdtable)' | |||
|
71 | 71 | configtable = {} |
|
72 | 72 | configitem = registrar.configitem(configtable) |
|
73 | 73 | |
|
74 | configitem('blackbox', 'dirty', | |
|
75 | default=False, | |
|
74 | configitem( | |
|
75 | 'blackbox', 'dirty', default=False, | |
|
76 | 76 | ) |
|
77 | configitem('blackbox', 'maxsize', | |
|
78 | default='1 MB', | |
|
77 | configitem( | |
|
78 | 'blackbox', 'maxsize', default='1 MB', | |
|
79 | ) | |
|
80 | configitem( | |
|
81 | 'blackbox', 'logsource', default=False, | |
|
79 | 82 | ) |
|
80 | configitem('blackbox', 'logsource', | |
|
81 | default=False, | |
|
83 | configitem( | |
|
84 | 'blackbox', 'maxfiles', default=7, | |
|
82 | 85 | ) |
|
83 | configitem('blackbox', 'maxfiles', | |
|
84 | default=7, | |
|
86 | configitem( | |
|
87 | 'blackbox', 'track', default=lambda: ['*'], | |
|
85 | 88 | ) |
|
86 | configitem('blackbox', 'track', | |
|
87 | default=lambda: ['*'], | |
|
88 | ) | |
|
89 | configitem('blackbox', 'ignore', | |
|
89 | configitem( | |
|
90 | 'blackbox', | |
|
91 | 'ignore', | |
|
90 | 92 | default=lambda: ['chgserver', 'cmdserver', 'extension'], |
|
91 | 93 | ) |
|
92 | configitem('blackbox', 'date-format', | |
|
93 | default='%Y/%m/%d %H:%M:%S', | |
|
94 | configitem( | |
|
95 | 'blackbox', 'date-format', default='%Y/%m/%d %H:%M:%S', | |
|
94 | 96 | ) |
|
95 | 97 | |
|
96 | 98 | _lastlogger = loggingutil.proxylogger() |
|
97 | 99 | |
|
100 | ||
|
98 | 101 | class blackboxlogger(object): |
|
99 | 102 | def __init__(self, ui, repo): |
|
100 | 103 | self._repo = repo |
@@ -105,9 +108,9 b' class blackboxlogger(object):' | |||
|
105 | 108 | self._inlog = False |
|
106 | 109 | |
|
107 | 110 | def tracked(self, event): |
|
108 | return ((b'*' in self._trackedevents | |
|
109 |
|
|
|
110 |
|
|
|
111 | return ( | |
|
112 | b'*' in self._trackedevents and event not in self._ignoredevents | |
|
113 | ) or event in self._trackedevents | |
|
111 | 114 | |
|
112 | 115 | def log(self, ui, event, msg, opts): |
|
113 | 116 | # self._log() -> ctx.dirty() may create new subrepo instance, which |
@@ -129,9 +132,10 b' class blackboxlogger(object):' | |||
|
129 | 132 | changed = '' |
|
130 | 133 | ctx = self._repo[None] |
|
131 | 134 | parents = ctx.parents() |
|
132 |
rev = |
|
|
133 |
if |
|
|
134 |
|
|
|
135 | rev = '+'.join([hex(p.node()) for p in parents]) | |
|
136 | if ui.configbool('blackbox', 'dirty') and ctx.dirty( | |
|
137 | missing=True, merge=False, branch=False | |
|
138 | ): | |
|
135 | 139 | changed = '+' |
|
136 | 140 | if ui.configbool('blackbox', 'logsource'): |
|
137 | 141 | src = ' [%s]' % event |
@@ -141,20 +145,28 b' class blackboxlogger(object):' | |||
|
141 | 145 | fmt = '%s %s @%s%s (%s)%s> %s' |
|
142 | 146 | args = (date, user, rev, changed, pid, src, msg) |
|
143 | 147 | with loggingutil.openlogfile( |
|
144 | ui, self._repo.vfs, name='blackbox.log', | |
|
145 | maxfiles=self._maxfiles, maxsize=self._maxsize) as fp: | |
|
148 | ui, | |
|
149 | self._repo.vfs, | |
|
150 | name='blackbox.log', | |
|
151 | maxfiles=self._maxfiles, | |
|
152 | maxsize=self._maxsize, | |
|
153 | ) as fp: | |
|
146 | 154 | fp.write(fmt % args) |
|
147 | 155 | except (IOError, OSError) as err: |
|
148 | 156 | # deactivate this to avoid failed logging again |
|
149 | 157 | self._trackedevents.clear() |
|
150 | ui.debug('warning: cannot write to blackbox.log: %s\n' % | |
|
151 | encoding.strtolocal(err.strerror)) | |
|
158 | ui.debug( | |
|
159 | 'warning: cannot write to blackbox.log: %s\n' | |
|
160 | % encoding.strtolocal(err.strerror) | |
|
161 | ) | |
|
152 | 162 | return |
|
153 | 163 | _lastlogger.logger = self |
|
154 | 164 | |
|
165 | ||
|
155 | 166 | def uipopulate(ui): |
|
156 | 167 | ui.setlogger(b'blackbox', _lastlogger) |
|
157 | 168 | |
|
169 | ||
|
158 | 170 | def reposetup(ui, repo): |
|
159 | 171 | # During 'hg pull' a httppeer repo is created to represent the remote repo. |
|
160 | 172 | # It doesn't have a .hg directory to put a blackbox in, so we don't do |
@@ -174,12 +186,14 b' def reposetup(ui, repo):' | |||
|
174 | 186 | |
|
175 | 187 | repo._wlockfreeprefix.add('blackbox.log') |
|
176 | 188 | |
|
177 | @command('blackbox', | |
|
178 | [('l', 'limit', 10, _('the number of events to show')), | |
|
179 | ], | |
|
189 | ||
|
190 | @command( | |
|
191 | 'blackbox', | |
|
192 | [('l', 'limit', 10, _('the number of events to show')),], | |
|
180 | 193 | _('hg blackbox [OPTION]...'), |
|
181 | 194 | helpcategory=command.CATEGORY_MAINTENANCE, |
|
182 |
helpbasic=True |
|
|
195 | helpbasic=True, | |
|
196 | ) | |
|
183 | 197 | def blackbox(ui, repo, *revs, **opts): |
|
184 | 198 | '''view the recent repository events |
|
185 | 199 | ''' |
@@ -36,20 +36,26 b" configitem(MY_NAME, 'enable-branches', F" | |||
|
36 | 36 | cmdtable = {} |
|
37 | 37 | command = registrar.command(cmdtable) |
|
38 | 38 | |
|
39 | ||
|
39 | 40 | def commit_hook(ui, repo, **kwargs): |
|
40 | 41 | active = repo._bookmarks.active |
|
41 | 42 | if active: |
|
42 | 43 | if active in ui.configlist(MY_NAME, 'protect'): |
|
43 | 44 | raise error.Abort( |
|
44 |
_('cannot commit, bookmark %s is protected') % active |
|
|
45 | _('cannot commit, bookmark %s is protected') % active | |
|
46 | ) | |
|
45 | 47 | if not cwd_at_bookmark(repo, active): |
|
46 | 48 | raise error.Abort( |
|
47 | _('cannot commit, working directory out of sync with active bookmark'), | |
|
48 | hint=_("run 'hg up %s'") % active) | |
|
49 | _( | |
|
50 | 'cannot commit, working directory out of sync with active bookmark' | |
|
51 | ), | |
|
52 | hint=_("run 'hg up %s'") % active, | |
|
53 | ) | |
|
49 | 54 | elif ui.configbool(MY_NAME, 'require-bookmark', True): |
|
50 | 55 | raise error.Abort(_('cannot commit without an active bookmark')) |
|
51 | 56 | return 0 |
|
52 | 57 | |
|
58 | ||
|
53 | 59 | def bookmarks_update(orig, repo, parents, node): |
|
54 | 60 | if len(parents) == 2: |
|
55 | 61 | # called during commit |
@@ -58,43 +64,59 b' def bookmarks_update(orig, repo, parents' | |||
|
58 | 64 | # called during update |
|
59 | 65 | return False |
|
60 | 66 | |
|
67 | ||
|
61 | 68 | def bookmarks_addbookmarks( |
|
62 |
|
|
|
69 | orig, repo, tr, names, rev=None, force=False, inactive=False | |
|
70 | ): | |
|
63 | 71 | if not rev: |
|
64 | 72 | marks = repo._bookmarks |
|
65 | 73 | for name in names: |
|
66 | 74 | if name in marks: |
|
67 |
raise error.Abort( |
|
|
75 | raise error.Abort( | |
|
76 | _( | |
|
68 | 77 | "bookmark %s already exists, to move use the --rev option" |
|
69 |
) |
|
|
78 | ) | |
|
79 | % name | |
|
80 | ) | |
|
70 | 81 | return orig(repo, tr, names, rev, force, inactive) |
|
71 | 82 | |
|
83 | ||
|
72 | 84 | def commands_commit(orig, ui, repo, *args, **opts): |
|
73 | 85 | commit_hook(ui, repo) |
|
74 | 86 | return orig(ui, repo, *args, **opts) |
|
75 | 87 | |
|
88 | ||
|
76 | 89 | def commands_pull(orig, ui, repo, *args, **opts): |
|
77 | 90 | rc = orig(ui, repo, *args, **opts) |
|
78 | 91 | active = repo._bookmarks.active |
|
79 | 92 | if active and not cwd_at_bookmark(repo, active): |
|
80 |
ui.warn( |
|
|
93 | ui.warn( | |
|
94 | _( | |
|
81 | 95 | "working directory out of sync with active bookmark, run " |
|
82 | 96 | "'hg up %s'" |
|
83 | ) % active) | |
|
97 | ) | |
|
98 | % active | |
|
99 | ) | |
|
84 | 100 | return rc |
|
85 | 101 | |
|
102 | ||
|
86 | 103 | def commands_branch(orig, ui, repo, label=None, **opts): |
|
87 | 104 | if label and not opts.get(r'clean') and not opts.get(r'rev'): |
|
88 | 105 | raise error.Abort( |
|
89 | _("creating named branches is disabled and you should use bookmarks"), | |
|
90 | hint="see 'hg help bookflow'") | |
|
106 | _( | |
|
107 | "creating named branches is disabled and you should use bookmarks" | |
|
108 | ), | |
|
109 | hint="see 'hg help bookflow'", | |
|
110 | ) | |
|
91 | 111 | return orig(ui, repo, label, **opts) |
|
92 | 112 | |
|
113 | ||
|
93 | 114 | def cwd_at_bookmark(repo, mark): |
|
94 | 115 | mark_id = repo._bookmarks[mark] |
|
95 | 116 | cur_id = repo.lookup('.') |
|
96 | 117 | return cur_id == mark_id |
|
97 | 118 | |
|
119 | ||
|
98 | 120 | def uisetup(ui): |
|
99 | 121 | extensions.wrapfunction(bookmarks, 'update', bookmarks_update) |
|
100 | 122 | extensions.wrapfunction(bookmarks, 'addbookmarks', bookmarks_addbookmarks) |
@@ -324,71 +324,80 b" testedwith = 'ships-with-hg-core'" | |||
|
324 | 324 | configtable = {} |
|
325 | 325 | configitem = registrar.configitem(configtable) |
|
326 | 326 | |
|
327 | configitem('bugzilla', 'apikey', | |
|
328 | default='', | |
|
327 | configitem( | |
|
328 | 'bugzilla', 'apikey', default='', | |
|
329 | ) | |
|
330 | configitem( | |
|
331 | 'bugzilla', 'bzdir', default='/var/www/html/bugzilla', | |
|
329 | 332 | ) |
|
330 | configitem('bugzilla', 'bzdir', | |
|
331 | default='/var/www/html/bugzilla', | |
|
333 | configitem( | |
|
334 | 'bugzilla', 'bzemail', default=None, | |
|
332 | 335 | ) |
|
333 | configitem('bugzilla', 'bzemail', | |
|
334 | default=None, | |
|
336 | configitem( | |
|
337 | 'bugzilla', 'bzurl', default='http://localhost/bugzilla/', | |
|
335 | 338 | ) |
|
336 | configitem('bugzilla', 'bzurl', | |
|
337 | default='http://localhost/bugzilla/', | |
|
339 | configitem( | |
|
340 | 'bugzilla', 'bzuser', default=None, | |
|
338 | 341 | ) |
|
339 | configitem('bugzilla', 'bzuser', | |
|
340 | default=None, | |
|
342 | configitem( | |
|
343 | 'bugzilla', 'db', default='bugs', | |
|
341 | 344 | ) |
|
342 | configitem('bugzilla', 'db', | |
|
343 | default='bugs', | |
|
344 | ) | |
|
345 | configitem('bugzilla', 'fixregexp', | |
|
346 |
|
|
|
345 | configitem( | |
|
346 | 'bugzilla', | |
|
347 | 'fixregexp', | |
|
348 | default=( | |
|
349 | br'fix(?:es)?\s*(?:bugs?\s*)?,?\s*' | |
|
347 | 350 |
|
|
348 | 351 |
|
|
349 |
|
|
|
350 | ) | |
|
351 | configitem('bugzilla', 'fixresolution', | |
|
352 | default='FIXED', | |
|
352 | br'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?' | |
|
353 | ), | |
|
353 | 354 | ) |
|
354 | configitem('bugzilla', 'fixstatus', | |
|
355 | default='RESOLVED', | |
|
355 | configitem( | |
|
356 | 'bugzilla', 'fixresolution', default='FIXED', | |
|
356 | 357 | ) |
|
357 | configitem('bugzilla', 'host', | |
|
358 | default='localhost', | |
|
358 | configitem( | |
|
359 | 'bugzilla', 'fixstatus', default='RESOLVED', | |
|
359 | 360 | ) |
|
360 | configitem('bugzilla', 'notify', | |
|
361 | default=configitem.dynamicdefault, | |
|
361 | configitem( | |
|
362 | 'bugzilla', 'host', default='localhost', | |
|
362 | 363 | ) |
|
363 | configitem('bugzilla', 'password', | |
|
364 | default=None, | |
|
364 | configitem( | |
|
365 | 'bugzilla', 'notify', default=configitem.dynamicdefault, | |
|
365 | 366 | ) |
|
366 | configitem('bugzilla', 'regexp', | |
|
367 | default=(br'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' | |
|
368 | br'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)' | |
|
369 | br'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?') | |
|
367 | configitem( | |
|
368 | 'bugzilla', 'password', default=None, | |
|
370 | 369 | ) |
|
371 | configitem('bugzilla', 'strip', | |
|
372 | default=0, | |
|
370 | configitem( | |
|
371 | 'bugzilla', | |
|
372 | 'regexp', | |
|
373 | default=( | |
|
374 | br'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' | |
|
375 | br'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)' | |
|
376 | br'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?' | |
|
377 | ), | |
|
373 | 378 | ) |
|
374 | configitem('bugzilla', 'style', | |
|
375 | default=None, | |
|
379 | configitem( | |
|
380 | 'bugzilla', 'strip', default=0, | |
|
376 | 381 | ) |
|
377 | configitem('bugzilla', 'template', | |
|
378 | default=None, | |
|
382 | configitem( | |
|
383 | 'bugzilla', 'style', default=None, | |
|
379 | 384 | ) |
|
380 | configitem('bugzilla', 'timeout', | |
|
381 | default=5, | |
|
385 | configitem( | |
|
386 | 'bugzilla', 'template', default=None, | |
|
382 | 387 | ) |
|
383 | configitem('bugzilla', 'user', | |
|
384 | default='bugs', | |
|
388 | configitem( | |
|
389 | 'bugzilla', 'timeout', default=5, | |
|
385 | 390 | ) |
|
386 | configitem('bugzilla', 'usermap', | |
|
387 | default=None, | |
|
391 | configitem( | |
|
392 | 'bugzilla', 'user', default='bugs', | |
|
388 | 393 | ) |
|
389 | configitem('bugzilla', 'version', | |
|
390 | default=None, | |
|
394 | configitem( | |
|
395 | 'bugzilla', 'usermap', default=None, | |
|
391 | 396 | ) |
|
397 | configitem( | |
|
398 | 'bugzilla', 'version', default=None, | |
|
399 | ) | |
|
400 | ||
|
392 | 401 | |
|
393 | 402 | class bzaccess(object): |
|
394 | 403 | '''Base class for access to Bugzilla.''' |
@@ -434,6 +443,7 b' class bzaccess(object):' | |||
|
434 | 443 | emails automatically. |
|
435 | 444 | ''' |
|
436 | 445 | |
|
446 | ||
|
437 | 447 | # Bugzilla via direct access to MySQL database. |
|
438 | 448 | class bzmysql(bzaccess): |
|
439 | 449 | '''Support for direct MySQL access to Bugzilla. |
@@ -454,6 +464,7 b' class bzmysql(bzaccess):' | |||
|
454 | 464 | def __init__(self, ui): |
|
455 | 465 | try: |
|
456 | 466 | import MySQLdb as mysql |
|
467 | ||
|
457 | 468 | bzmysql._MySQLdb = mysql |
|
458 | 469 | except ImportError as err: |
|
459 | 470 | raise error.Abort(_('python mysql support not available: %s') % err) |
@@ -465,12 +476,13 b' class bzmysql(bzaccess):' | |||
|
465 | 476 | passwd = self.ui.config('bugzilla', 'password') |
|
466 | 477 | db = self.ui.config('bugzilla', 'db') |
|
467 | 478 | timeout = int(self.ui.config('bugzilla', 'timeout')) |
|
468 | self.ui.note(_('connecting to %s:%s as %s, password %s\n') % | |
|
469 | (host, db, user, '*' * len(passwd))) | |
|
470 | self.conn = bzmysql._MySQLdb.connect(host=host, | |
|
471 | user=user, passwd=passwd, | |
|
472 | db=db, | |
|
473 | connect_timeout=timeout) | |
|
479 | self.ui.note( | |
|
480 | _('connecting to %s:%s as %s, password %s\n') | |
|
481 | % (host, db, user, '*' * len(passwd)) | |
|
482 | ) | |
|
483 | self.conn = bzmysql._MySQLdb.connect( | |
|
484 | host=host, user=user, passwd=passwd, db=db, connect_timeout=timeout | |
|
485 | ) | |
|
474 | 486 | self.cursor = self.conn.cursor() |
|
475 | 487 | self.longdesc_id = self.get_longdesc_id() |
|
476 | 488 | self.user_ids = {} |
@@ -495,8 +507,10 b' class bzmysql(bzaccess):' | |||
|
495 | 507 | |
|
496 | 508 | def filter_real_bug_ids(self, bugs): |
|
497 | 509 | '''filter not-existing bugs from set.''' |
|
498 | self.run('select bug_id from bugs where bug_id in %s' % | |
|
499 | bzmysql.sql_buglist(bugs.keys())) | |
|
510 | self.run( | |
|
511 | 'select bug_id from bugs where bug_id in %s' | |
|
512 | % bzmysql.sql_buglist(bugs.keys()) | |
|
513 | ) | |
|
500 | 514 | existing = [id for (id,) in self.cursor.fetchall()] |
|
501 | 515 | for id in bugs.keys(): |
|
502 | 516 | if id not in existing: |
@@ -505,12 +519,16 b' class bzmysql(bzaccess):' | |||
|
505 | 519 | |
|
506 | 520 | def filter_cset_known_bug_ids(self, node, bugs): |
|
507 | 521 | '''filter bug ids that already refer to this changeset from set.''' |
|
508 | self.run('''select bug_id from longdescs where | |
|
509 | bug_id in %s and thetext like "%%%s%%"''' % | |
|
510 | (bzmysql.sql_buglist(bugs.keys()), short(node))) | |
|
522 | self.run( | |
|
523 | '''select bug_id from longdescs where | |
|
524 | bug_id in %s and thetext like "%%%s%%"''' | |
|
525 | % (bzmysql.sql_buglist(bugs.keys()), short(node)) | |
|
526 | ) | |
|
511 | 527 | for (id,) in self.cursor.fetchall(): |
|
512 | self.ui.status(_('bug %d already knows about changeset %s\n') % | |
|
513 | (id, short(node))) | |
|
528 | self.ui.status( | |
|
529 | _('bug %d already knows about changeset %s\n') | |
|
530 | % (id, short(node)) | |
|
531 | ) | |
|
514 | 532 | del bugs[id] |
|
515 | 533 | |
|
516 | 534 | def notify(self, bugs, committer): |
@@ -534,8 +552,9 b' class bzmysql(bzaccess):' | |||
|
534 | 552 | ret = fp.close() |
|
535 | 553 | if ret: |
|
536 | 554 | self.ui.warn(out) |
|
537 |
raise error.Abort( |
|
|
538 |
|
|
|
555 | raise error.Abort( | |
|
556 | _('bugzilla notify command %s') % procutil.explainexit(ret) | |
|
557 | ) | |
|
539 | 558 | self.ui.status(_('done\n')) |
|
540 | 559 | |
|
541 | 560 | def get_user_id(self, user): |
@@ -547,8 +566,11 b' class bzmysql(bzaccess):' | |||
|
547 | 566 | userid = int(user) |
|
548 | 567 | except ValueError: |
|
549 | 568 | self.ui.note(_('looking up user %s\n') % user) |
|
550 |
self.run( |
|
|
551 | where login_name like %s''', user) | |
|
569 | self.run( | |
|
570 | '''select userid from profiles | |
|
571 | where login_name like %s''', | |
|
572 | user, | |
|
573 | ) | |
|
552 | 574 | all = self.cursor.fetchall() |
|
553 | 575 | if len(all) != 1: |
|
554 | 576 | raise KeyError(user) |
@@ -567,13 +589,16 b' class bzmysql(bzaccess):' | |||
|
567 | 589 | try: |
|
568 | 590 | defaultuser = self.ui.config('bugzilla', 'bzuser') |
|
569 | 591 | if not defaultuser: |
|
570 |
raise error.Abort( |
|
|
571 | user) | |
|
592 | raise error.Abort( | |
|
593 | _('cannot find bugzilla user id for %s') % user | |
|
594 | ) | |
|
572 | 595 | userid = self.get_user_id(defaultuser) |
|
573 | 596 | user = defaultuser |
|
574 | 597 | except KeyError: |
|
575 | raise error.Abort(_('cannot find bugzilla user id for %s or %s') | |
|
576 | % (user, defaultuser)) | |
|
598 | raise error.Abort( | |
|
599 | _('cannot find bugzilla user id for %s or %s') | |
|
600 | % (user, defaultuser) | |
|
601 | ) | |
|
577 | 602 | return (user, userid) |
|
578 | 603 | |
|
579 | 604 | def updatebug(self, bugid, newstate, text, committer): |
@@ -586,22 +611,29 b' class bzmysql(bzaccess):' | |||
|
586 | 611 | |
|
587 | 612 | (user, userid) = self.get_bugzilla_user(committer) |
|
588 | 613 | now = time.strftime(r'%Y-%m-%d %H:%M:%S') |
|
589 | self.run('''insert into longdescs | |
|
614 | self.run( | |
|
615 | '''insert into longdescs | |
|
590 | 616 |
|
|
591 | 617 | values (%s, %s, %s, %s)''', |
|
592 |
|
|
|
593 | self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid) | |
|
618 | (bugid, userid, now, text), | |
|
619 | ) | |
|
620 | self.run( | |
|
621 | '''insert into bugs_activity (bug_id, who, bug_when, fieldid) | |
|
594 | 622 | values (%s, %s, %s, %s)''', |
|
595 |
|
|
|
623 | (bugid, userid, now, self.longdesc_id), | |
|
624 | ) | |
|
596 | 625 | self.conn.commit() |
|
597 | 626 | |
|
627 | ||
|
598 | 628 | class bzmysql_2_18(bzmysql): |
|
599 | 629 | '''support for bugzilla 2.18 series.''' |
|
600 | 630 | |
|
601 | 631 | def __init__(self, ui): |
|
602 | 632 | bzmysql.__init__(self, ui) |
|
603 | 633 | self.default_notify = ( |
|
604 |
"cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s" |
|
|
634 | "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s" | |
|
635 | ) | |
|
636 | ||
|
605 | 637 | |
|
606 | 638 | class bzmysql_3_0(bzmysql_2_18): |
|
607 | 639 | '''support for bugzilla 3.0 series.''' |
@@ -617,8 +649,10 b' class bzmysql_3_0(bzmysql_2_18):' | |||
|
617 | 649 | raise error.Abort(_('unknown database schema')) |
|
618 | 650 | return ids[0][0] |
|
619 | 651 | |
|
652 | ||
|
620 | 653 | # Bugzilla via XMLRPC interface. |
|
621 | 654 | |
|
655 | ||
|
622 | 656 | class cookietransportrequest(object): |
|
623 | 657 | """A Transport request method that retains cookies over its lifetime. |
|
624 | 658 | |
@@ -636,6 +670,7 b' class cookietransportrequest(object):' | |||
|
636 | 670 | # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/ |
|
637 | 671 | |
|
638 | 672 | cookies = [] |
|
673 | ||
|
639 | 674 | def send_cookies(self, connection): |
|
640 | 675 | if self.cookies: |
|
641 | 676 | for cookie in self.cookies: |
@@ -673,8 +708,12 b' class cookietransportrequest(object):' | |||
|
673 | 708 | self.cookies.append(cookie) |
|
674 | 709 | |
|
675 | 710 | if response.status != 200: |
|
676 |
raise xmlrpclib.ProtocolError( |
|
|
677 | response.reason, response.msg.headers) | |
|
711 | raise xmlrpclib.ProtocolError( | |
|
712 | host + handler, | |
|
713 | response.status, | |
|
714 | response.reason, | |
|
715 | response.msg.headers, | |
|
716 | ) | |
|
678 | 717 | |
|
679 | 718 | payload = response.read() |
|
680 | 719 | parser, unmarshaller = self.getparser() |
@@ -683,6 +722,7 b' class cookietransportrequest(object):' | |||
|
683 | 722 | |
|
684 | 723 | return unmarshaller.close() |
|
685 | 724 | |
|
725 | ||
|
686 | 726 | # The explicit calls to the underlying xmlrpclib __init__() methods are |
|
687 | 727 | # necessary. The xmlrpclib.Transport classes are old-style classes, and |
|
688 | 728 | # it turns out their __init__() doesn't get called when doing multiple |
@@ -692,11 +732,13 b' class cookietransport(cookietransportreq' | |||
|
692 | 732 | if util.safehasattr(xmlrpclib.Transport, "__init__"): |
|
693 | 733 | xmlrpclib.Transport.__init__(self, use_datetime) |
|
694 | 734 | |
|
735 | ||
|
695 | 736 | class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport): |
|
696 | 737 | def __init__(self, use_datetime=0): |
|
697 | 738 | if util.safehasattr(xmlrpclib.Transport, "__init__"): |
|
698 | 739 | xmlrpclib.SafeTransport.__init__(self, use_datetime) |
|
699 | 740 | |
|
741 | ||
|
700 | 742 | class bzxmlrpc(bzaccess): |
|
701 | 743 | """Support for access to Bugzilla via the Bugzilla XMLRPC API. |
|
702 | 744 | |
@@ -719,8 +761,9 b' class bzxmlrpc(bzaccess):' | |||
|
719 | 761 | ver = self.bzproxy.Bugzilla.version()['version'].split('.') |
|
720 | 762 | self.bzvermajor = int(ver[0]) |
|
721 | 763 | self.bzverminor = int(ver[1]) |
|
722 |
login = self.bzproxy.User.login( |
|
|
723 | 'restrict_login': True}) | |
|
764 | login = self.bzproxy.User.login( | |
|
765 | {'login': user, 'password': passwd, 'restrict_login': True} | |
|
766 | ) | |
|
724 | 767 | self.bztoken = login.get('token', '') |
|
725 | 768 | |
|
726 | 769 | def transport(self, uri): |
@@ -731,17 +774,20 b' class bzxmlrpc(bzaccess):' | |||
|
731 | 774 | |
|
732 | 775 | def get_bug_comments(self, id): |
|
733 | 776 | """Return a string with all comment text for a bug.""" |
|
734 |
c = self.bzproxy.Bug.comments( |
|
|
735 | 'include_fields': ['text'], | |
|
736 | 'token': self.bztoken}) | |
|
777 | c = self.bzproxy.Bug.comments( | |
|
778 | {'ids': [id], 'include_fields': ['text'], 'token': self.bztoken} | |
|
779 | ) | |
|
737 | 780 | return ''.join([t['text'] for t in c['bugs']['%d' % id]['comments']]) |
|
738 | 781 | |
|
739 | 782 | def filter_real_bug_ids(self, bugs): |
|
740 |
probe = self.bzproxy.Bug.get( |
|
|
783 | probe = self.bzproxy.Bug.get( | |
|
784 | { | |
|
785 | 'ids': sorted(bugs.keys()), | |
|
741 | 786 |
|
|
742 | 787 |
|
|
743 | 788 |
|
|
744 | }) | |
|
789 | } | |
|
790 | ) | |
|
745 | 791 | for badbug in probe['faults']: |
|
746 | 792 | id = badbug['id'] |
|
747 | 793 | self.ui.status(_('bug %d does not exist\n') % id) |
@@ -750,8 +796,10 b' class bzxmlrpc(bzaccess):' | |||
|
750 | 796 | def filter_cset_known_bug_ids(self, node, bugs): |
|
751 | 797 | for id in sorted(bugs.keys()): |
|
752 | 798 | if self.get_bug_comments(id).find(short(node)) != -1: |
|
753 | self.ui.status(_('bug %d already knows about changeset %s\n') % | |
|
754 | (id, short(node))) | |
|
799 | self.ui.status( | |
|
800 | _('bug %d already knows about changeset %s\n') | |
|
801 | % (id, short(node)) | |
|
802 | ) | |
|
755 | 803 | del bugs[id] |
|
756 | 804 | |
|
757 | 805 | def updatebug(self, bugid, newstate, text, committer): |
@@ -769,12 +817,17 b' class bzxmlrpc(bzaccess):' | |||
|
769 | 817 | self.bzproxy.Bug.update(args) |
|
770 | 818 | else: |
|
771 | 819 | if 'fix' in newstate: |
|
772 | self.ui.warn(_("Bugzilla/XMLRPC needs Bugzilla 4.0 or later " | |
|
773 | "to mark bugs fixed\n")) | |
|
820 | self.ui.warn( | |
|
821 | _( | |
|
822 | "Bugzilla/XMLRPC needs Bugzilla 4.0 or later " | |
|
823 | "to mark bugs fixed\n" | |
|
824 | ) | |
|
825 | ) | |
|
774 | 826 | args['id'] = bugid |
|
775 | 827 | args['comment'] = text |
|
776 | 828 | self.bzproxy.Bug.add_comment(args) |
|
777 | 829 | |
|
830 | ||
|
778 | 831 | class bzxmlrpcemail(bzxmlrpc): |
|
779 | 832 | """Read data from Bugzilla via XMLRPC, send updates via email. |
|
780 | 833 | |
@@ -823,15 +876,18 b' class bzxmlrpcemail(bzxmlrpc):' | |||
|
823 | 876 | than the subject line, and leave a blank line after it. |
|
824 | 877 | ''' |
|
825 | 878 | user = self.map_committer(committer) |
|
826 |
matches = self.bzproxy.User.get( |
|
|
827 |
|
|
|
879 | matches = self.bzproxy.User.get( | |
|
880 | {'match': [user], 'token': self.bztoken} | |
|
881 | ) | |
|
828 | 882 | if not matches['users']: |
|
829 | 883 | user = self.ui.config('bugzilla', 'user') |
|
830 |
matches = self.bzproxy.User.get( |
|
|
831 |
|
|
|
884 | matches = self.bzproxy.User.get( | |
|
885 | {'match': [user], 'token': self.bztoken} | |
|
886 | ) | |
|
832 | 887 | if not matches['users']: |
|
833 |
raise error.Abort( |
|
|
834 | % user) | |
|
888 | raise error.Abort( | |
|
889 | _("default bugzilla user %s email not found") % user | |
|
890 | ) | |
|
835 | 891 | user = matches['users'][0]['email'] |
|
836 | 892 | commands.append(self.makecommandline("id", bugid)) |
|
837 | 893 | |
@@ -856,13 +912,16 b' class bzxmlrpcemail(bzxmlrpc):' | |||
|
856 | 912 | cmds.append(self.makecommandline("resolution", self.fixresolution)) |
|
857 | 913 | self.send_bug_modify_email(bugid, cmds, text, committer) |
|
858 | 914 | |
|
915 | ||
|
859 | 916 | class NotFound(LookupError): |
|
860 | 917 | pass |
|
861 | 918 | |
|
919 | ||
|
862 | 920 | class bzrestapi(bzaccess): |
|
863 | 921 | """Read and write bugzilla data using the REST API available since |
|
864 | 922 | Bugzilla 5.0. |
|
865 | 923 | """ |
|
924 | ||
|
866 | 925 | def __init__(self, ui): |
|
867 | 926 | bzaccess.__init__(self, ui) |
|
868 | 927 | bz = self.ui.config('bugzilla', 'bzurl') |
@@ -902,14 +961,15 b' class bzrestapi(bzaccess):' | |||
|
902 | 961 | def _submit(self, burl, data, method='POST'): |
|
903 | 962 | data = json.dumps(data) |
|
904 | 963 | if method == 'PUT': |
|
964 | ||
|
905 | 965 | class putrequest(util.urlreq.request): |
|
906 | 966 | def get_method(self): |
|
907 | 967 | return 'PUT' |
|
968 | ||
|
908 | 969 | request_type = putrequest |
|
909 | 970 | else: |
|
910 | 971 | request_type = util.urlreq.request |
|
911 | req = request_type(burl, data, | |
|
912 | {'Content-Type': 'application/json'}) | |
|
972 | req = request_type(burl, data, {'Content-Type': 'application/json'}) | |
|
913 | 973 | try: |
|
914 | 974 | resp = url.opener(self.ui).open(req) |
|
915 | 975 | return json.loads(resp.read()) |
@@ -941,8 +1001,9 b' class bzrestapi(bzaccess):' | |||
|
941 | 1001 | result = self._fetch(burl) |
|
942 | 1002 | comments = result['bugs'][pycompat.bytestr(bugid)]['comments'] |
|
943 | 1003 | if any(sn in c['text'] for c in comments): |
|
944 | self.ui.status(_('bug %d already knows about changeset %s\n') % | |
|
945 |
|
|
|
1004 | self.ui.status( | |
|
1005 | _('bug %d already knows about changeset %s\n') % (bugid, sn) | |
|
1006 | ) | |
|
946 | 1007 | del bugs[bugid] |
|
947 | 1008 | |
|
948 | 1009 | def updatebug(self, bugid, newstate, text, committer): |
@@ -969,11 +1030,10 b' class bzrestapi(bzaccess):' | |||
|
969 | 1030 | self.ui.debug('updated bug %s\n' % bugid) |
|
970 | 1031 | else: |
|
971 | 1032 | burl = self.apiurl(('bug', bugid, 'comment')) |
|
972 |
self._submit( |
|
|
973 |
|
|
|
974 | 'is_private': False, | |
|
975 | 'is_markdown': False, | |
|
976 | }) | |
|
1033 | self._submit( | |
|
1034 | burl, | |
|
1035 | {'comment': text, 'is_private': False, 'is_markdown': False,}, | |
|
1036 | ) | |
|
977 | 1037 | self.ui.debug('added comment to bug %s\n' % bugid) |
|
978 | 1038 | |
|
979 | 1039 | def notify(self, bugs, committer): |
@@ -984,6 +1044,7 b' class bzrestapi(bzaccess):' | |||
|
984 | 1044 | ''' |
|
985 | 1045 | pass |
|
986 | 1046 | |
|
1047 | ||
|
987 | 1048 | class bugzilla(object): |
|
988 | 1049 | # supported versions of bugzilla. different versions have |
|
989 | 1050 | # different schemas. |
@@ -1004,14 +1065,17 b' class bugzilla(object):' | |||
|
1004 | 1065 | try: |
|
1005 | 1066 | bzclass = bugzilla._versions[bzversion] |
|
1006 | 1067 | except KeyError: |
|
1007 |
raise error.Abort( |
|
|
1008 | bzversion) | |
|
1068 | raise error.Abort( | |
|
1069 | _('bugzilla version %s not supported') % bzversion | |
|
1070 | ) | |
|
1009 | 1071 | self.bzdriver = bzclass(self.ui) |
|
1010 | 1072 | |
|
1011 | 1073 | self.bug_re = re.compile( |
|
1012 |
self.ui.config('bugzilla', 'regexp'), re.IGNORECASE |
|
|
1074 | self.ui.config('bugzilla', 'regexp'), re.IGNORECASE | |
|
1075 | ) | |
|
1013 | 1076 | self.fix_re = re.compile( |
|
1014 |
self.ui.config('bugzilla', 'fixregexp'), re.IGNORECASE |
|
|
1077 | self.ui.config('bugzilla', 'fixregexp'), re.IGNORECASE | |
|
1078 | ) | |
|
1015 | 1079 | self.split_re = re.compile(br'\D+') |
|
1016 | 1080 | |
|
1017 | 1081 | def find_bugs(self, ctx): |
@@ -1093,31 +1157,39 b' class bugzilla(object):' | |||
|
1093 | 1157 | if not tmpl: |
|
1094 | 1158 | mapfile = self.ui.config('bugzilla', 'style') |
|
1095 | 1159 | if not mapfile and not tmpl: |
|
1096 | tmpl = _('changeset {node|short} in repo {root} refers ' | |
|
1097 | 'to bug {bug}.\ndetails:\n\t{desc|tabindent}') | |
|
1160 | tmpl = _( | |
|
1161 | 'changeset {node|short} in repo {root} refers ' | |
|
1162 | 'to bug {bug}.\ndetails:\n\t{desc|tabindent}' | |
|
1163 | ) | |
|
1098 | 1164 | spec = logcmdutil.templatespec(tmpl, mapfile) |
|
1099 | 1165 | t = logcmdutil.changesettemplater(self.ui, self.repo, spec) |
|
1100 | 1166 | self.ui.pushbuffer() |
|
1101 | t.show(ctx, changes=ctx.changeset(), | |
|
1167 | t.show( | |
|
1168 | ctx, | |
|
1169 | changes=ctx.changeset(), | |
|
1102 | 1170 |
|
|
1103 | 1171 |
|
|
1104 | 1172 |
|
|
1105 |
|
|
|
1173 | webroot=webroot(self.repo.root), | |
|
1174 | ) | |
|
1106 | 1175 | data = self.ui.popbuffer() |
|
1107 |
self.bzdriver.updatebug( |
|
|
1108 |
|
|
|
1176 | self.bzdriver.updatebug( | |
|
1177 | bugid, newstate, data, stringutil.email(ctx.user()) | |
|
1178 | ) | |
|
1109 | 1179 | |
|
1110 | 1180 | def notify(self, bugs, committer): |
|
1111 | 1181 | '''ensure Bugzilla users are notified of bug change.''' |
|
1112 | 1182 | self.bzdriver.notify(bugs, committer) |
|
1113 | 1183 | |
|
1184 | ||
|
1114 | 1185 | def hook(ui, repo, hooktype, node=None, **kwargs): |
|
1115 | 1186 | '''add comment to bugzilla for each changeset that refers to a |
|
1116 | 1187 | bugzilla bug id. only add a comment once per bug, so same change |
|
1117 | 1188 | seen multiple times does not fill bug with duplicate data.''' |
|
1118 | 1189 | if node is None: |
|
1119 | raise error.Abort(_('hook type %s does not pass a changeset id') % | |
|
1120 | hooktype) | |
|
1190 | raise error.Abort( | |
|
1191 | _('hook type %s does not pass a changeset id') % hooktype | |
|
1192 | ) | |
|
1121 | 1193 | try: |
|
1122 | 1194 | bz = bugzilla(ui, repo) |
|
1123 | 1195 | ctx = repo[node] |
@@ -44,15 +44,21 b' command = registrar.command(cmdtable)' | |||
|
44 | 44 | # leave the attribute unspecified. |
|
45 | 45 | testedwith = 'ships-with-hg-core' |
|
46 | 46 | |
|
47 | @command('censor', | |
|
48 | [('r', 'rev', '', _('censor file from specified revision'), _('REV')), | |
|
49 | ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT'))], | |
|
47 | ||
|
48 | @command( | |
|
49 | 'censor', | |
|
50 | [ | |
|
51 | ('r', 'rev', '', _('censor file from specified revision'), _('REV')), | |
|
52 | ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT')), | |
|
53 | ], | |
|
50 | 54 | _('-r REV [-t TEXT] [FILE]'), |
|
51 |
helpcategory=command.CATEGORY_MAINTENANCE |
|
|
55 | helpcategory=command.CATEGORY_MAINTENANCE, | |
|
56 | ) | |
|
52 | 57 | def censor(ui, repo, path, rev='', tombstone='', **opts): |
|
53 | 58 | with repo.wlock(), repo.lock(): |
|
54 | 59 | return _docensor(ui, repo, path, rev, tombstone, **opts) |
|
55 | 60 | |
|
61 | ||
|
56 | 62 | def _docensor(ui, repo, path, rev='', tombstone='', **opts): |
|
57 | 63 | if not path: |
|
58 | 64 | raise error.Abort(_('must specify file path to censor')) |
@@ -88,13 +94,17 b" def _docensor(ui, repo, path, rev='', to" | |||
|
88 | 94 | heads.append(hc) |
|
89 | 95 | if heads: |
|
90 | 96 | headlist = ', '.join([short(c.node()) for c in heads]) |
|
91 | raise error.Abort(_('cannot censor file in heads (%s)') % headlist, | |
|
92 | hint=_('clean/delete and commit first')) | |
|
97 | raise error.Abort( | |
|
98 | _('cannot censor file in heads (%s)') % headlist, | |
|
99 | hint=_('clean/delete and commit first'), | |
|
100 | ) | |
|
93 | 101 | |
|
94 | 102 | wp = wctx.parents() |
|
95 | 103 | if ctx.node() in [p.node() for p in wp]: |
|
96 | raise error.Abort(_('cannot censor working directory'), | |
|
97 | hint=_('clean/delete/update first')) | |
|
104 | raise error.Abort( | |
|
105 | _('cannot censor working directory'), | |
|
106 | hint=_('clean/delete/update first'), | |
|
107 | ) | |
|
98 | 108 | |
|
99 | 109 | with repo.transaction(b'censor') as tr: |
|
100 | 110 | flog.censorrevision(tr, fnode, tombstone=tombstone) |
@@ -35,13 +35,15 b' command = registrar.command(cmdtable)' | |||
|
35 | 35 | # leave the attribute unspecified. |
|
36 | 36 | testedwith = 'ships-with-hg-core' |
|
37 | 37 | |
|
38 | @command('children', | |
|
39 | [('r', 'rev', '.', | |
|
40 | _('show children of the specified revision'), _('REV')), | |
|
41 | ] + templateopts, | |
|
38 | ||
|
39 | @command( | |
|
40 | 'children', | |
|
41 | [('r', 'rev', '.', _('show children of the specified revision'), _('REV')),] | |
|
42 | + templateopts, | |
|
42 | 43 | _('hg children [-r REV] [FILE]'), |
|
43 | 44 | helpcategory=command.CATEGORY_CHANGE_NAVIGATION, |
|
44 |
inferrepo=True |
|
|
45 | inferrepo=True, | |
|
46 | ) | |
|
45 | 47 | def children(ui, repo, file_=None, **opts): |
|
46 | 48 | """show the children of the given or working directory revision |
|
47 | 49 |
@@ -34,6 +34,7 b' command = registrar.command(cmdtable)' | |||
|
34 | 34 | # leave the attribute unspecified. |
|
35 | 35 | testedwith = 'ships-with-hg-core' |
|
36 | 36 | |
|
37 | ||
|
37 | 38 | def changedlines(ui, repo, ctx1, ctx2, fns): |
|
38 | 39 | added, removed = 0, 0 |
|
39 | 40 | fmatch = scmutil.matchfiles(repo, fns) |
@@ -45,31 +46,38 b' def changedlines(ui, repo, ctx1, ctx2, f' | |||
|
45 | 46 | removed += 1 |
|
46 | 47 | return (added, removed) |
|
47 | 48 | |
|
49 | ||
|
48 | 50 | def countrate(ui, repo, amap, *pats, **opts): |
|
49 | 51 | """Calculate stats""" |
|
50 | 52 | opts = pycompat.byteskwargs(opts) |
|
51 | 53 | if opts.get('dateformat'): |
|
54 | ||
|
52 | 55 | def getkey(ctx): |
|
53 | 56 | t, tz = ctx.date() |
|
54 | 57 | date = datetime.datetime(*time.gmtime(float(t) - tz)[:6]) |
|
55 | 58 | return encoding.strtolocal( |
|
56 |
date.strftime(encoding.strfromlocal(opts['dateformat'])) |
|
|
59 | date.strftime(encoding.strfromlocal(opts['dateformat'])) | |
|
60 | ) | |
|
61 | ||
|
57 | 62 | else: |
|
58 | 63 | tmpl = opts.get('oldtemplate') or opts.get('template') |
|
59 | 64 | tmpl = logcmdutil.maketemplater(ui, repo, tmpl) |
|
65 | ||
|
60 | 66 | def getkey(ctx): |
|
61 | 67 | ui.pushbuffer() |
|
62 | 68 | tmpl.show(ctx) |
|
63 | 69 | return ui.popbuffer() |
|
64 | 70 | |
|
65 |
progress = ui.makeprogress( |
|
|
66 | total=len(repo)) | |
|
71 | progress = ui.makeprogress( | |
|
72 | _('analyzing'), unit=_('revisions'), total=len(repo) | |
|
73 | ) | |
|
67 | 74 | rate = {} |
|
68 | 75 | df = False |
|
69 | 76 | if opts.get('date'): |
|
70 | 77 | df = dateutil.matchdate(opts['date']) |
|
71 | 78 | |
|
72 | 79 | m = scmutil.match(repo[None], pats, opts) |
|
80 | ||
|
73 | 81 | def prep(ctx, fns): |
|
74 | 82 | rev = ctx.rev() |
|
75 | 83 | if df and not df(ctx.date()[0]): # doesn't match date format |
@@ -99,25 +107,54 b' def countrate(ui, repo, amap, *pats, **o' | |||
|
99 | 107 | return rate |
|
100 | 108 | |
|
101 | 109 | |
|
102 |
@command( |
|
|
103 | [('r', 'rev', [], | |
|
104 | _('count rate for the specified revision or revset'), _('REV')), | |
|
105 | ('d', 'date', '', | |
|
106 | _('count rate for revisions matching date spec'), _('DATE')), | |
|
107 | ('t', 'oldtemplate', '', | |
|
108 | _('template to group changesets (DEPRECATED)'), _('TEMPLATE')), | |
|
109 | ('T', 'template', '{author|email}', | |
|
110 | _('template to group changesets'), _('TEMPLATE')), | |
|
111 | ('f', 'dateformat', '', | |
|
112 | _('strftime-compatible format for grouping by date'), _('FORMAT')), | |
|
110 | @command( | |
|
111 | 'churn', | |
|
112 | [ | |
|
113 | ( | |
|
114 | 'r', | |
|
115 | 'rev', | |
|
116 | [], | |
|
117 | _('count rate for the specified revision or revset'), | |
|
118 | _('REV'), | |
|
119 | ), | |
|
120 | ( | |
|
121 | 'd', | |
|
122 | 'date', | |
|
123 | '', | |
|
124 | _('count rate for revisions matching date spec'), | |
|
125 | _('DATE'), | |
|
126 | ), | |
|
127 | ( | |
|
128 | 't', | |
|
129 | 'oldtemplate', | |
|
130 | '', | |
|
131 | _('template to group changesets (DEPRECATED)'), | |
|
132 | _('TEMPLATE'), | |
|
133 | ), | |
|
134 | ( | |
|
135 | 'T', | |
|
136 | 'template', | |
|
137 | '{author|email}', | |
|
138 | _('template to group changesets'), | |
|
139 | _('TEMPLATE'), | |
|
140 | ), | |
|
141 | ( | |
|
142 | 'f', | |
|
143 | 'dateformat', | |
|
144 | '', | |
|
145 | _('strftime-compatible format for grouping by date'), | |
|
146 | _('FORMAT'), | |
|
147 | ), | |
|
113 | 148 | ('c', 'changesets', False, _('count rate by number of changesets')), |
|
114 | 149 | ('s', 'sort', False, _('sort by key (default: sort by count)')), |
|
115 | 150 | ('', 'diffstat', False, _('display added/removed lines separately')), |
|
116 | 151 | ('', 'aliases', '', _('file with email aliases'), _('FILE')), |
|
117 | ] + cmdutil.walkopts, | |
|
152 | ] | |
|
153 | + cmdutil.walkopts, | |
|
118 | 154 | _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"), |
|
119 | 155 | helpcategory=command.CATEGORY_MAINTENANCE, |
|
120 |
inferrepo=True |
|
|
156 | inferrepo=True, | |
|
157 | ) | |
|
121 | 158 | def churn(ui, repo, *pats, **opts): |
|
122 | 159 | '''histogram of changes to the repository |
|
123 | 160 | |
@@ -154,6 +191,7 b' def churn(ui, repo, *pats, **opts):' | |||
|
154 | 191 | a .hgchurn file will be looked for in the working directory root. |
|
155 | 192 | Aliases will be split from the rightmost "=". |
|
156 | 193 | ''' |
|
194 | ||
|
157 | 195 | def pad(s, l): |
|
158 | 196 | return s + " " * (l - encoding.colwidth(s)) |
|
159 | 197 | |
@@ -191,19 +229,25 b' def churn(ui, repo, *pats, **opts):' | |||
|
191 | 229 | |
|
192 | 230 | if opts.get(r'diffstat'): |
|
193 | 231 | width -= 15 |
|
232 | ||
|
194 | 233 | def format(name, diffstat): |
|
195 | 234 | added, removed = diffstat |
|
196 |
return "%s %15s %s%s\n" % ( |
|
|
235 | return "%s %15s %s%s\n" % ( | |
|
236 | pad(name, maxname), | |
|
197 | 237 |
|
|
198 |
|
|
|
199 | 'diffstat.inserted'), | |
|
200 | ui.label('-' * charnum(removed), | |
|
201 | 'diffstat.deleted')) | |
|
238 | ui.label('+' * charnum(added), 'diffstat.inserted'), | |
|
239 | ui.label('-' * charnum(removed), 'diffstat.deleted'), | |
|
240 | ) | |
|
241 | ||
|
202 | 242 | else: |
|
203 | 243 | width -= 6 |
|
244 | ||
|
204 | 245 | def format(name, count): |
|
205 |
return "%s %6d %s\n" % ( |
|
|
206 | '*' * charnum(sum(count))) | |
|
246 | return "%s %6d %s\n" % ( | |
|
247 | pad(name, maxname), | |
|
248 | sum(count), | |
|
249 | '*' * charnum(sum(count)), | |
|
250 | ) | |
|
207 | 251 | |
|
208 | 252 | def charnum(count): |
|
209 | 253 | return int(count * width // maxcount) |
@@ -203,6 +203,7 b' from mercurial import (' | |||
|
203 | 203 | |
|
204 | 204 | testedwith = 'ships-with-hg-core' |
|
205 | 205 | |
|
206 | ||
|
206 | 207 | def capabilities(orig, repo, proto): |
|
207 | 208 | caps = orig(repo, proto) |
|
208 | 209 | |
@@ -214,5 +215,6 b' def capabilities(orig, repo, proto):' | |||
|
214 | 215 | |
|
215 | 216 | return caps |
|
216 | 217 | |
|
218 | ||
|
217 | 219 | def extsetup(ui): |
|
218 | 220 | extensions.wrapfunction(wireprotov1server, '_capabilities', capabilities) |
@@ -28,13 +28,16 b" testedwith = 'ships-with-hg-core'" | |||
|
28 | 28 | |
|
29 | 29 | commitopts = cmdutil.commitopts |
|
30 | 30 | commitopts2 = cmdutil.commitopts2 |
|
31 | commitopts3 = [('r', 'rev', [], | |
|
32 | _('revision to check'), _('REV'))] | |
|
31 | commitopts3 = [('r', 'rev', [], _('revision to check'), _('REV'))] | |
|
32 | ||
|
33 | 33 | |
|
34 | @command('close-head|close-heads', commitopts + commitopts2 + commitopts3, | |
|
34 | @command( | |
|
35 | 'close-head|close-heads', | |
|
36 | commitopts + commitopts2 + commitopts3, | |
|
35 | 37 | _('[OPTION]... [REV]...'), |
|
36 | 38 | helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
|
37 |
inferrepo=True |
|
|
39 | inferrepo=True, | |
|
40 | ) | |
|
38 | 41 | def close_branch(ui, repo, *revs, **opts): |
|
39 | 42 | """close the given head revisions |
|
40 | 43 | |
@@ -44,10 +47,18 b' def close_branch(ui, repo, *revs, **opts' | |||
|
44 | 47 | |
|
45 | 48 | The commit message must be specified with -l or -m. |
|
46 | 49 | """ |
|
50 | ||
|
47 | 51 | def docommit(rev): |
|
48 |
cctx = context.memctx( |
|
|
49 | files=[], filectxfn=None, user=opts.get('user'), | |
|
50 | date=opts.get('date'), extra=extra) | |
|
52 | cctx = context.memctx( | |
|
53 | repo, | |
|
54 | parents=[rev, None], | |
|
55 | text=message, | |
|
56 | files=[], | |
|
57 | filectxfn=None, | |
|
58 | user=opts.get('user'), | |
|
59 | date=opts.get('date'), | |
|
60 | extra=extra, | |
|
61 | ) | |
|
51 | 62 | tr = repo.transaction('commit') |
|
52 | 63 | ret = repo.commitctx(cctx, True) |
|
53 | 64 | bookmarks.update(repo, [rev, None], ret) |
@@ -37,36 +37,46 b' usedinternally = {' | |||
|
37 | 37 | 'transplant_source', |
|
38 | 38 | } |
|
39 | 39 | |
|
40 | ||
|
40 | 41 | def extsetup(ui): |
|
41 | 42 | entry = extensions.wrapcommand(commands.table, 'commit', _commit) |
|
42 | 43 | options = entry[1] |
|
43 |
options.append( |
|
|
44 |
_('set a changeset\'s extra values'), _("KEY=VALUE")) |
|
|
44 | options.append( | |
|
45 | ('', 'extra', [], _('set a changeset\'s extra values'), _("KEY=VALUE")) | |
|
46 | ) | |
|
47 | ||
|
45 | 48 | |
|
46 | 49 | def _commit(orig, ui, repo, *pats, **opts): |
|
47 | 50 | if util.safehasattr(repo, 'unfiltered'): |
|
48 | 51 | repo = repo.unfiltered() |
|
52 | ||
|
49 | 53 | class repoextra(repo.__class__): |
|
50 | 54 | def commit(self, *innerpats, **inneropts): |
|
51 | 55 | extras = opts.get(r'extra') |
|
52 | 56 | for raw in extras: |
|
53 | 57 | if '=' not in raw: |
|
54 | msg = _("unable to parse '%s', should follow " | |
|
55 | "KEY=VALUE format") | |
|
58 | msg = _( | |
|
59 | "unable to parse '%s', should follow " | |
|
60 | "KEY=VALUE format" | |
|
61 | ) | |
|
56 | 62 | raise error.Abort(msg % raw) |
|
57 | 63 | k, v = raw.split('=', 1) |
|
58 | 64 | if not k: |
|
59 | 65 | msg = _("unable to parse '%s', keys can't be empty") |
|
60 | 66 | raise error.Abort(msg % raw) |
|
61 | 67 | if re.search(br'[^\w-]', k): |
|
62 | msg = _("keys can only contain ascii letters, digits," | |
|
63 | " '_' and '-'") | |
|
68 | msg = _( | |
|
69 | "keys can only contain ascii letters, digits," | |
|
70 | " '_' and '-'" | |
|
71 | ) | |
|
64 | 72 | raise error.Abort(msg) |
|
65 | 73 | if k in usedinternally: |
|
66 | msg = _("key '%s' is used internally, can't be set " | |
|
67 | "manually") | |
|
74 | msg = _( | |
|
75 | "key '%s' is used internally, can't be set " "manually" | |
|
76 | ) | |
|
68 | 77 | raise error.Abort(msg % k) |
|
69 | 78 | inneropts[r'extra'][k] = v |
|
70 | 79 | return super(repoextra, self).commit(*innerpats, **inneropts) |
|
80 | ||
|
71 | 81 | repo.__class__ = repoextra |
|
72 | 82 | return orig(ui, repo, *pats, **opts) |
@@ -10,9 +10,7 b'' | |||
|
10 | 10 | from __future__ import absolute_import |
|
11 | 11 | |
|
12 | 12 | from mercurial.i18n import _ |
|
13 |
from mercurial import |
|
|
14 | registrar, | |
|
15 | ) | |
|
13 | from mercurial import registrar | |
|
16 | 14 | |
|
17 | 15 | from . import ( |
|
18 | 16 | convcmd, |
@@ -30,28 +28,58 b" testedwith = 'ships-with-hg-core'" | |||
|
30 | 28 | |
|
31 | 29 | # Commands definition was moved elsewhere to ease demandload job. |
|
32 | 30 | |
|
33 | @command('convert', | |
|
34 | [('', 'authors', '', | |
|
35 | _('username mapping filename (DEPRECATED) (use --authormap instead)'), | |
|
36 | _('FILE')), | |
|
31 | ||
|
32 | @command( | |
|
33 | 'convert', | |
|
34 | [ | |
|
35 | ( | |
|
36 | '', | |
|
37 | 'authors', | |
|
38 | '', | |
|
39 | _( | |
|
40 | 'username mapping filename (DEPRECATED) (use --authormap instead)' | |
|
41 | ), | |
|
42 | _('FILE'), | |
|
43 | ), | |
|
37 | 44 | ('s', 'source-type', '', _('source repository type'), _('TYPE')), |
|
38 | 45 | ('d', 'dest-type', '', _('destination repository type'), _('TYPE')), |
|
39 | 46 | ('r', 'rev', [], _('import up to source revision REV'), _('REV')), |
|
40 | 47 | ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')), |
|
41 | ('', 'filemap', '', _('remap file names using contents of file'), | |
|
42 | _('FILE')), | |
|
43 | ('', 'full', None, | |
|
44 | _('apply filemap changes by converting all files again')), | |
|
45 | ('', 'splicemap', '', _('splice synthesized history into place'), | |
|
46 |
_('FILE') |
|
|
47 | ('', 'branchmap', '', _('change branch names while converting'), | |
|
48 | _('FILE')), | |
|
48 | ( | |
|
49 | '', | |
|
50 | 'filemap', | |
|
51 | '', | |
|
52 | _('remap file names using contents of file'), | |
|
53 | _('FILE'), | |
|
54 | ), | |
|
55 | ( | |
|
56 | '', | |
|
57 | 'full', | |
|
58 | None, | |
|
59 | _('apply filemap changes by converting all files again'), | |
|
60 | ), | |
|
61 | ( | |
|
62 | '', | |
|
63 | 'splicemap', | |
|
64 | '', | |
|
65 | _('splice synthesized history into place'), | |
|
66 | _('FILE'), | |
|
67 | ), | |
|
68 | ( | |
|
69 | '', | |
|
70 | 'branchmap', | |
|
71 | '', | |
|
72 | _('change branch names while converting'), | |
|
73 | _('FILE'), | |
|
74 | ), | |
|
49 | 75 | ('', 'branchsort', None, _('try to sort changesets by branches')), |
|
50 | 76 | ('', 'datesort', None, _('try to sort changesets by date')), |
|
51 | 77 | ('', 'sourcesort', None, _('preserve source changesets order')), |
|
52 |
('', 'closesort', None, _('try to reorder closed revisions')) |
|
|
78 | ('', 'closesort', None, _('try to reorder closed revisions')), | |
|
79 | ], | |
|
53 | 80 | _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'), |
|
54 |
norepo=True |
|
|
81 | norepo=True, | |
|
82 | ) | |
|
55 | 83 | def convert(ui, src, dest=None, revmapfile=None, **opts): |
|
56 | 84 | """convert a foreign SCM repository to a Mercurial one. |
|
57 | 85 | |
@@ -454,17 +482,24 b' def convert(ui, src, dest=None, revmapfi' | |||
|
454 | 482 | """ |
|
455 | 483 | return convcmd.convert(ui, src, dest, revmapfile, **opts) |
|
456 | 484 | |
|
485 | ||
|
457 | 486 | @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True) |
|
458 | 487 | def debugsvnlog(ui, **opts): |
|
459 | 488 | return subversion.debugsvnlog(ui, **opts) |
|
460 | 489 | |
|
461 | @command('debugcvsps', | |
|
490 | ||
|
491 | @command( | |
|
492 | 'debugcvsps', | |
|
462 | 493 | [ |
|
463 | 494 | # Main options shared with cvsps-2.1 |
|
464 | 495 | ('b', 'branches', [], _('only return changes on specified branches')), |
|
465 | 496 | ('p', 'prefix', '', _('prefix to remove from file names')), |
|
466 | ('r', 'revisions', [], | |
|
467 | _('only return changes after or between specified tags')), | |
|
497 | ( | |
|
498 | 'r', | |
|
499 | 'revisions', | |
|
500 | [], | |
|
501 | _('only return changes after or between specified tags'), | |
|
502 | ), | |
|
468 | 503 | ('u', 'update-cache', None, _("update cvs log cache")), |
|
469 | 504 | ('x', 'new-cache', None, _("create new cvs log cache")), |
|
470 | 505 | ('z', 'fuzz', 60, _('set commit time fuzz in seconds')), |
@@ -476,7 +511,8 b' def debugsvnlog(ui, **opts):' | |||
|
476 | 511 | ('A', 'cvs-direct', None, _('ignored for compatibility')), |
|
477 | 512 | ], |
|
478 | 513 | _('hg debugcvsps [OPTION]... [PATH]...'), |
|
479 |
norepo=True |
|
|
514 | norepo=True, | |
|
515 | ) | |
|
480 | 516 | def debugcvsps(ui, *args, **opts): |
|
481 | 517 | '''create changeset information from CVS |
|
482 | 518 | |
@@ -490,34 +526,40 b' def debugcvsps(ui, *args, **opts):' | |||
|
490 | 526 | dates.''' |
|
491 | 527 | return cvsps.debugcvsps(ui, *args, **opts) |
|
492 | 528 | |
|
529 | ||
|
493 | 530 | def kwconverted(context, mapping, name): |
|
494 | 531 | ctx = context.resource(mapping, 'ctx') |
|
495 | 532 | rev = ctx.extra().get('convert_revision', '') |
|
496 | 533 | if rev.startswith('svn:'): |
|
497 | 534 | if name == 'svnrev': |
|
498 |
return |
|
|
535 | return b"%d" % subversion.revsplit(rev)[2] | |
|
499 | 536 | elif name == 'svnpath': |
|
500 | 537 | return subversion.revsplit(rev)[1] |
|
501 | 538 | elif name == 'svnuuid': |
|
502 | 539 | return subversion.revsplit(rev)[0] |
|
503 | 540 | return rev |
|
504 | 541 | |
|
542 | ||
|
505 | 543 | templatekeyword = registrar.templatekeyword() |
|
506 | 544 | |
|
545 | ||
|
507 | 546 | @templatekeyword('svnrev', requires={'ctx'}) |
|
508 | 547 | def kwsvnrev(context, mapping): |
|
509 | 548 | """String. Converted subversion revision number.""" |
|
510 | 549 | return kwconverted(context, mapping, 'svnrev') |
|
511 | 550 | |
|
551 | ||
|
512 | 552 | @templatekeyword('svnpath', requires={'ctx'}) |
|
513 | 553 | def kwsvnpath(context, mapping): |
|
514 | 554 | """String. Converted subversion revision project path.""" |
|
515 | 555 | return kwconverted(context, mapping, 'svnpath') |
|
516 | 556 | |
|
557 | ||
|
517 | 558 | @templatekeyword('svnuuid', requires={'ctx'}) |
|
518 | 559 | def kwsvnuuid(context, mapping): |
|
519 | 560 | """String. Converted subversion revision repository identifier.""" |
|
520 | 561 | return kwconverted(context, mapping, 'svnuuid') |
|
521 | 562 | |
|
563 | ||
|
522 | 564 | # tell hggettext to extract docstrings from these functions: |
|
523 | 565 | i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid] |
@@ -12,18 +12,13 b' from __future__ import absolute_import' | |||
|
12 | 12 | import os |
|
13 | 13 | |
|
14 | 14 | from mercurial.i18n import _ |
|
15 |
from mercurial import |
|
|
16 | demandimport, | |
|
17 | error | |
|
18 | ) | |
|
15 | from mercurial import demandimport, error | |
|
19 | 16 | from . import common |
|
20 | 17 | |
|
21 | 18 | # these do not work with demandimport, blacklist |
|
22 |
demandimport.IGNORES.update( |
|
|
23 | 'bzrlib.transactions', | |
|
24 | 'bzrlib.urlutils', | |
|
25 | 'ElementPath', | |
|
26 | ]) | |
|
19 | demandimport.IGNORES.update( | |
|
20 | ['bzrlib.transactions', 'bzrlib.urlutils', 'ElementPath',] | |
|
21 | ) | |
|
27 | 22 | |
|
28 | 23 | try: |
|
29 | 24 | # bazaar imports |
@@ -31,6 +26,7 b' try:' | |||
|
31 | 26 | import bzrlib.errors |
|
32 | 27 | import bzrlib.revision |
|
33 | 28 | import bzrlib.revisionspec |
|
29 | ||
|
34 | 30 | bzrdir = bzrlib.bzrdir |
|
35 | 31 | errors = bzrlib.errors |
|
36 | 32 | revision = bzrlib.revision |
@@ -41,6 +37,7 b' except ImportError:' | |||
|
41 | 37 | |
|
42 | 38 | supportedkinds = ('file', 'symlink') |
|
43 | 39 | |
|
40 | ||
|
44 | 41 | class bzr_source(common.converter_source): |
|
45 | 42 | """Reads Bazaar repositories by using the Bazaar Python libraries""" |
|
46 | 43 | |
@@ -48,8 +45,9 b' class bzr_source(common.converter_source' | |||
|
48 | 45 | super(bzr_source, self).__init__(ui, repotype, path, revs=revs) |
|
49 | 46 | |
|
50 | 47 | if not os.path.exists(os.path.join(path, '.bzr')): |
|
51 | raise common.NoRepo(_('%s does not look like a Bazaar repository') | |
|
52 | % path) | |
|
48 | raise common.NoRepo( | |
|
49 | _('%s does not look like a Bazaar repository') % path | |
|
50 | ) | |
|
53 | 51 | |
|
54 | 52 | try: |
|
55 | 53 | # access bzrlib stuff |
@@ -62,8 +60,9 b' class bzr_source(common.converter_source' | |||
|
62 | 60 | try: |
|
63 | 61 | self.sourcerepo = bzrdir.BzrDir.open(path).open_repository() |
|
64 | 62 | except errors.NoRepositoryPresent: |
|
65 | raise common.NoRepo(_('%s does not look like a Bazaar repository') | |
|
66 | % path) | |
|
63 | raise common.NoRepo( | |
|
64 | _('%s does not look like a Bazaar repository') % path | |
|
65 | ) | |
|
67 | 66 | self._parentids = {} |
|
68 | 67 | self._saverev = ui.configbool('convert', 'bzr.saverev') |
|
69 | 68 | |
@@ -78,11 +77,18 b' class bzr_source(common.converter_source' | |||
|
78 | 77 | except (errors.NoWorkingTree, errors.NotLocalUrl): |
|
79 | 78 | tree = None |
|
80 | 79 | branch = dir.open_branch() |
|
81 | if (tree is not None and tree.bzrdir.root_transport.base != | |
|
82 | branch.bzrdir.root_transport.base): | |
|
83 | self.ui.warn(_('warning: lightweight checkouts may cause ' | |
|
80 | if ( | |
|
81 | tree is not None | |
|
82 | and tree.bzrdir.root_transport.base | |
|
83 | != branch.bzrdir.root_transport.base | |
|
84 | ): | |
|
85 | self.ui.warn( | |
|
86 | _( | |
|
87 | 'warning: lightweight checkouts may cause ' | |
|
84 | 88 |
|
|
85 |
|
|
|
89 | 'branch instead.\n' | |
|
90 | ) | |
|
91 | ) | |
|
86 | 92 | except Exception: |
|
87 | 93 | self.ui.note(_('bzr source type could not be determined\n')) |
|
88 | 94 | |
@@ -119,8 +125,9 b' class bzr_source(common.converter_source' | |||
|
119 | 125 | pass |
|
120 | 126 | revid = info.rev_id |
|
121 | 127 | if revid is None: |
|
122 |
raise error.Abort( |
|
|
123 |
|
|
|
128 | raise error.Abort( | |
|
129 | _('%s is not a valid revision') % self.revs[0] | |
|
130 | ) | |
|
124 | 131 | heads = [revid] |
|
125 | 132 | # Empty repositories return 'null:', which cannot be retrieved |
|
126 | 133 | heads = [h for h in heads if h != 'null:'] |
@@ -139,8 +146,9 b' class bzr_source(common.converter_source' | |||
|
139 | 146 | if kind == 'symlink': |
|
140 | 147 | target = revtree.get_symlink_target(fileid) |
|
141 | 148 | if target is None: |
|
142 |
raise error.Abort( |
|
|
143 |
|
|
|
149 | raise error.Abort( | |
|
150 | _('%s.%s symlink has no target') % (name, rev) | |
|
151 | ) | |
|
144 | 152 | return target, mode |
|
145 | 153 | else: |
|
146 | 154 | sio = revtree.get_file(fileid) |
@@ -171,13 +179,15 b' class bzr_source(common.converter_source' | |||
|
171 | 179 | branch = self.recode(rev.properties.get('branch-nick', u'default')) |
|
172 | 180 | if branch == 'trunk': |
|
173 | 181 | branch = 'default' |
|
174 |
return common.commit( |
|
|
182 | return common.commit( | |
|
183 | parents=parents, | |
|
175 | 184 |
|
|
176 | 185 |
|
|
177 | 186 |
|
|
178 | 187 |
|
|
179 | 188 |
|
|
180 |
|
|
|
189 | saverev=self._saverev, | |
|
190 | ) | |
|
181 | 191 | |
|
182 | 192 | def gettags(self): |
|
183 | 193 | bytetags = {} |
@@ -216,11 +226,21 b' class bzr_source(common.converter_source' | |||
|
216 | 226 | |
|
217 | 227 | # Process the entries by reverse lexicographic name order to |
|
218 | 228 | # handle nested renames correctly, most specific first. |
|
219 |
curchanges = sorted( |
|
|
229 | curchanges = sorted( | |
|
230 | current.iter_changes(origin), | |
|
220 | 231 |
|
|
221 |
|
|
|
222 | for (fileid, paths, changed_content, versioned, parent, name, | |
|
223 | kind, executable) in curchanges: | |
|
232 | reverse=True, | |
|
233 | ) | |
|
234 | for ( | |
|
235 | fileid, | |
|
236 | paths, | |
|
237 | changed_content, | |
|
238 | versioned, | |
|
239 | parent, | |
|
240 | name, | |
|
241 | kind, | |
|
242 | executable, | |
|
243 | ) in curchanges: | |
|
224 | 244 | |
|
225 | 245 | if paths[0] == u'' or paths[1] == u'': |
|
226 | 246 | # ignore changes to tree root |
@@ -260,9 +280,11 b' class bzr_source(common.converter_source' | |||
|
260 | 280 | changes.append((frompath, revid)) |
|
261 | 281 | changes.append((topath, revid)) |
|
262 | 282 | # add to mode cache |
|
263 |
mode = ( |
|
|
283 | mode = ( | |
|
284 | (entry.executable and 'x') | |
|
264 | 285 |
|
|
265 |
|
|
|
286 | or '' | |
|
287 | ) | |
|
266 | 288 | self._modecache[(topath, revid)] = mode |
|
267 | 289 | # register the change as move |
|
268 | 290 | renames[topath] = frompath |
@@ -290,8 +312,7 b' class bzr_source(common.converter_source' | |||
|
290 | 312 | |
|
291 | 313 | # populate the mode cache |
|
292 | 314 | kind, executable = [e[1] for e in (kind, executable)] |
|
293 |
mode = |
|
|
294 | or '') | |
|
315 | mode = (executable and 'x') or (kind == 'symlink' and 'l') or '' | |
|
295 | 316 | self._modecache[(topath, revid)] = mode |
|
296 | 317 | changes.append((topath, revid)) |
|
297 | 318 |
@@ -22,20 +22,19 b' from mercurial import (' | |||
|
22 | 22 | pycompat, |
|
23 | 23 | util, |
|
24 | 24 | ) |
|
25 |
from mercurial.utils import |
|
|
26 | procutil, | |
|
27 | ) | |
|
25 | from mercurial.utils import procutil | |
|
28 | 26 | |
|
29 | 27 | pickle = util.pickle |
|
30 | 28 | propertycache = util.propertycache |
|
31 | 29 | |
|
30 | ||
|
32 | 31 | def _encodeornone(d): |
|
33 | 32 | if d is None: |
|
34 | 33 | return |
|
35 | 34 | return d.encode('latin1') |
|
36 | 35 | |
|
36 | ||
|
37 | 37 | class _shlexpy3proxy(object): |
|
38 | ||
|
39 | 38 | def __init__(self, l): |
|
40 | 39 | self._l = l |
|
41 | 40 | |
@@ -53,6 +52,7 b' class _shlexpy3proxy(object):' | |||
|
53 | 52 | def lineno(self): |
|
54 | 53 | return self._l.lineno |
|
55 | 54 | |
|
55 | ||
|
56 | 56 | def shlexer(data=None, filepath=None, wordchars=None, whitespace=None): |
|
57 | 57 | if data is None: |
|
58 | 58 | if pycompat.ispy3: |
@@ -62,7 +62,8 b' def shlexer(data=None, filepath=None, wo' | |||
|
62 | 62 | else: |
|
63 | 63 | if filepath is not None: |
|
64 | 64 | raise error.ProgrammingError( |
|
65 |
'shlexer only accepts data or filepath, not both' |
|
|
65 | 'shlexer only accepts data or filepath, not both' | |
|
66 | ) | |
|
66 | 67 | if pycompat.ispy3: |
|
67 | 68 | data = data.decode('latin1') |
|
68 | 69 | l = shlex.shlex(data, infile=filepath, posix=True) |
@@ -81,6 +82,7 b' def shlexer(data=None, filepath=None, wo' | |||
|
81 | 82 | return _shlexpy3proxy(l) |
|
82 | 83 | return l |
|
83 | 84 | |
|
85 | ||
|
84 | 86 | def encodeargs(args): |
|
85 | 87 | def encodearg(s): |
|
86 | 88 | lines = base64.encodestring(s) |
@@ -90,13 +92,16 b' def encodeargs(args):' | |||
|
90 | 92 | s = pickle.dumps(args) |
|
91 | 93 | return encodearg(s) |
|
92 | 94 | |
|
95 | ||
|
93 | 96 | def decodeargs(s): |
|
94 | 97 | s = base64.decodestring(s) |
|
95 | 98 | return pickle.loads(s) |
|
96 | 99 | |
|
100 | ||
|
97 | 101 | class MissingTool(Exception): |
|
98 | 102 | pass |
|
99 | 103 | |
|
104 | ||
|
100 | 105 | def checktool(exe, name=None, abort=True): |
|
101 | 106 | name = name or exe |
|
102 | 107 | if not procutil.findexe(exe): |
@@ -106,15 +111,30 b' def checktool(exe, name=None, abort=True' | |||
|
106 | 111 | exc = MissingTool |
|
107 | 112 | raise exc(_('cannot find required "%s" tool') % name) |
|
108 | 113 | |
|
114 | ||
|
109 | 115 | class NoRepo(Exception): |
|
110 | 116 | pass |
|
111 | 117 | |
|
118 | ||
|
112 | 119 | SKIPREV = 'SKIP' |
|
113 | 120 | |
|
121 | ||
|
114 | 122 | class commit(object): |
|
115 | def __init__(self, author, date, desc, parents, branch=None, rev=None, | |
|
116 | extra=None, sortkey=None, saverev=True, phase=phases.draft, | |
|
117 | optparents=None, ctx=None): | |
|
123 | def __init__( | |
|
124 | self, | |
|
125 | author, | |
|
126 | date, | |
|
127 | desc, | |
|
128 | parents, | |
|
129 | branch=None, | |
|
130 | rev=None, | |
|
131 | extra=None, | |
|
132 | sortkey=None, | |
|
133 | saverev=True, | |
|
134 | phase=phases.draft, | |
|
135 | optparents=None, | |
|
136 | ctx=None, | |
|
137 | ): | |
|
118 | 138 | self.author = author or 'unknown' |
|
119 | 139 | self.date = date or '0 0' |
|
120 | 140 | self.desc = desc |
@@ -128,6 +148,7 b' class commit(object):' | |||
|
128 | 148 | self.phase = phase |
|
129 | 149 | self.ctx = ctx # for hg to hg conversions |
|
130 | 150 | |
|
151 | ||
|
131 | 152 | class converter_source(object): |
|
132 | 153 | """Conversion source interface""" |
|
133 | 154 | |
@@ -146,8 +167,10 b' class converter_source(object):' | |||
|
146 | 167 | such format for their revision numbering |
|
147 | 168 | """ |
|
148 | 169 | if not re.match(br'[0-9a-fA-F]{40,40}$', revstr): |
|
149 |
raise error.Abort( |
|
|
150 | ' identifier') % (mapname, revstr)) | |
|
170 | raise error.Abort( | |
|
171 | _('%s entry %s is not a valid revision' ' identifier') | |
|
172 | % (mapname, revstr) | |
|
173 | ) | |
|
151 | 174 | |
|
152 | 175 | def before(self): |
|
153 | 176 | pass |
@@ -223,8 +246,9 b' class converter_source(object):' | |||
|
223 | 246 | try: |
|
224 | 247 | return s.decode("latin-1").encode("utf-8") |
|
225 | 248 | except UnicodeError: |
|
226 | return s.decode(pycompat.sysstr(encoding), | |
|
227 |
|
|
|
249 | return s.decode(pycompat.sysstr(encoding), "replace").encode( | |
|
250 | "utf-8" | |
|
251 | ) | |
|
228 | 252 | |
|
229 | 253 | def getchangedfiles(self, rev, i): |
|
230 | 254 | """Return the files changed by rev compared to parent[i]. |
@@ -275,6 +299,7 b' class converter_source(object):' | |||
|
275 | 299 | """ |
|
276 | 300 | return True |
|
277 | 301 | |
|
302 | ||
|
278 | 303 | class converter_sink(object): |
|
279 | 304 | """Conversion sink (target) interface""" |
|
280 | 305 | |
@@ -301,8 +326,9 b' class converter_sink(object):' | |||
|
301 | 326 | mapping equivalent authors identifiers for each system.""" |
|
302 | 327 | return None |
|
303 | 328 | |
|
304 | def putcommit(self, files, copies, parents, commit, source, revmap, full, | |
|
305 | cleanp2): | |
|
329 | def putcommit( | |
|
330 | self, files, copies, parents, commit, source, revmap, full, cleanp2 | |
|
331 | ): | |
|
306 | 332 | """Create a revision with all changed files listed in 'files' |
|
307 | 333 | and having listed parents. 'commit' is a commit object |
|
308 | 334 | containing at a minimum the author, date, and message for this |
@@ -369,6 +395,7 b' class converter_sink(object):' | |||
|
369 | 395 | special cases.""" |
|
370 | 396 | raise NotImplementedError |
|
371 | 397 | |
|
398 | ||
|
372 | 399 | class commandline(object): |
|
373 | 400 | def __init__(self, ui, command): |
|
374 | 401 | self.ui = ui |
@@ -403,11 +430,15 b' class commandline(object):' | |||
|
403 | 430 | |
|
404 | 431 | def _run(self, cmd, *args, **kwargs): |
|
405 | 432 | def popen(cmdline): |
|
406 |
p = subprocess.Popen( |
|
|
407 | shell=True, bufsize=-1, | |
|
433 | p = subprocess.Popen( | |
|
434 | procutil.tonativestr(cmdline), | |
|
435 | shell=True, | |
|
436 | bufsize=-1, | |
|
408 | 437 |
|
|
409 |
|
|
|
438 | stdout=subprocess.PIPE, | |
|
439 | ) | |
|
410 | 440 | return p |
|
441 | ||
|
411 | 442 | return self._dorun(popen, cmd, *args, **kwargs) |
|
412 | 443 | |
|
413 | 444 | def _run2(self, cmd, *args, **kwargs): |
@@ -495,6 +526,7 b' class commandline(object):' | |||
|
495 | 526 | for l in self._limit_arglist(arglist, cmd, *args, **kwargs): |
|
496 | 527 | self.run0(cmd, *(list(args) + l), **kwargs) |
|
497 | 528 | |
|
529 | ||
|
498 | 530 | class mapfile(dict): |
|
499 | 531 | def __init__(self, ui, path): |
|
500 | 532 | super(mapfile, self).__init__() |
@@ -523,7 +555,8 b' class mapfile(dict):' | |||
|
523 | 555 | except ValueError: |
|
524 | 556 | raise error.Abort( |
|
525 | 557 | _('syntax error in %s(%d): key/value pair expected') |
|
526 |
% (self.path, i + 1) |
|
|
558 | % (self.path, i + 1) | |
|
559 | ) | |
|
527 | 560 | if key not in self: |
|
528 | 561 | self.order.append(key) |
|
529 | 562 | super(mapfile, self).__setitem__(key, value) |
@@ -535,8 +568,9 b' class mapfile(dict):' | |||
|
535 | 568 | self.fp = open(self.path, 'ab') |
|
536 | 569 | except IOError as err: |
|
537 | 570 | raise error.Abort( |
|
538 |
_('could not open map file %r: %s') |
|
|
539 |
(self.path, encoding.strtolocal(err.strerror)) |
|
|
571 | _('could not open map file %r: %s') | |
|
572 | % (self.path, encoding.strtolocal(err.strerror)) | |
|
573 | ) | |
|
540 | 574 | self.fp.write(util.tonativeeol('%s %s\n' % (key, value))) |
|
541 | 575 | self.fp.flush() |
|
542 | 576 | super(mapfile, self).__setitem__(key, value) |
@@ -546,9 +580,11 b' class mapfile(dict):' | |||
|
546 | 580 | self.fp.close() |
|
547 | 581 | self.fp = None |
|
548 | 582 | |
|
583 | ||
|
549 | 584 | def makedatetimestamp(t): |
|
550 | 585 | """Like dateutil.makedate() but for time t instead of current time""" |
|
551 |
delta = |
|
|
552 | datetime.datetime.fromtimestamp(t)) | |
|
586 | delta = datetime.datetime.utcfromtimestamp( | |
|
587 | t | |
|
588 | ) - datetime.datetime.fromtimestamp(t) | |
|
553 | 589 | tz = delta.days * 86400 + delta.seconds |
|
554 | 590 | return t, tz |
@@ -54,12 +54,15 b' svn_source = subversion.svn_source' | |||
|
54 | 54 | |
|
55 | 55 | orig_encoding = 'ascii' |
|
56 | 56 | |
|
57 | ||
|
57 | 58 | def recode(s): |
|
58 | 59 | if isinstance(s, pycompat.unicode): |
|
59 | 60 | return s.encode(pycompat.sysstr(orig_encoding), 'replace') |
|
60 | 61 | else: |
|
61 | 62 | return s.decode('utf-8').encode( |
|
62 |
pycompat.sysstr(orig_encoding), 'replace' |
|
|
63 | pycompat.sysstr(orig_encoding), 'replace' | |
|
64 | ) | |
|
65 | ||
|
63 | 66 | |
|
64 | 67 | def mapbranch(branch, branchmap): |
|
65 | 68 | ''' |
@@ -90,10 +93,11 b' def mapbranch(branch, branchmap):' | |||
|
90 | 93 | branch = branchmap.get(branch or 'default', branch) |
|
91 | 94 | # At some point we used "None" literal to denote the default branch, |
|
92 | 95 | # attempt to use that for backward compatibility. |
|
93 |
if |
|
|
96 | if not branch: | |
|
94 | 97 | branch = branchmap.get('None', branch) |
|
95 | 98 | return branch |
|
96 | 99 | |
|
100 | ||
|
97 | 101 | source_converters = [ |
|
98 | 102 | ('cvs', convert_cvs, 'branchsort'), |
|
99 | 103 | ('git', convert_git, 'branchsort'), |
@@ -111,6 +115,7 b' sink_converters = [' | |||
|
111 | 115 | ('svn', svn_sink), |
|
112 | 116 | ] |
|
113 | 117 | |
|
118 | ||
|
114 | 119 | def convertsource(ui, path, type, revs): |
|
115 | 120 | exceptions = [] |
|
116 | 121 | if type and type not in [s[0] for s in source_converters]: |
@@ -126,6 +131,7 b' def convertsource(ui, path, type, revs):' | |||
|
126 | 131 | ui.write("%s\n" % pycompat.bytestr(inst.args[0])) |
|
127 | 132 | raise error.Abort(_('%s: missing or unsupported repository') % path) |
|
128 | 133 | |
|
134 | ||
|
129 | 135 | def convertsink(ui, path, type): |
|
130 | 136 | if type and type not in [s[0] for s in sink_converters]: |
|
131 | 137 | raise error.Abort(_('%s: invalid destination repository type') % type) |
@@ -139,12 +145,14 b' def convertsink(ui, path, type):' | |||
|
139 | 145 | raise error.Abort('%s\n' % inst) |
|
140 | 146 | raise error.Abort(_('%s: unknown repository type') % path) |
|
141 | 147 | |
|
148 | ||
|
142 | 149 | class progresssource(object): |
|
143 | 150 | def __init__(self, ui, source, filecount): |
|
144 | 151 | self.ui = ui |
|
145 | 152 | self.source = source |
|
146 |
self.progress = ui.makeprogress( |
|
|
147 | total=filecount) | |
|
153 | self.progress = ui.makeprogress( | |
|
154 | _('getting files'), unit=_('files'), total=filecount | |
|
155 | ) | |
|
148 | 156 | |
|
149 | 157 | def getfile(self, file, rev): |
|
150 | 158 | self.progress.increment(item=file) |
@@ -159,6 +167,7 b' class progresssource(object):' | |||
|
159 | 167 | def close(self): |
|
160 | 168 | self.progress.complete() |
|
161 | 169 | |
|
170 | ||
|
162 | 171 | class converter(object): |
|
163 | 172 | def __init__(self, ui, source, dest, revmapfile, opts): |
|
164 | 173 | |
@@ -213,8 +222,13 b' class converter(object):' | |||
|
213 | 222 | line = list(lex) |
|
214 | 223 | # check number of parents |
|
215 | 224 | if not (2 <= len(line) <= 3): |
|
216 |
raise error.Abort( |
|
|
217 | '[,parent2] expected') % (path, i + 1)) | |
|
225 | raise error.Abort( | |
|
226 | _( | |
|
227 | 'syntax error in %s(%d): child parent1' | |
|
228 | '[,parent2] expected' | |
|
229 | ) | |
|
230 | % (path, i + 1) | |
|
231 | ) | |
|
218 | 232 | for part in line: |
|
219 | 233 | self.source.checkrevformat(part) |
|
220 | 234 | child, p1, p2 = line[0], line[1:2], line[2:] |
@@ -224,11 +238,11 b' class converter(object):' | |||
|
224 | 238 | m[child] = p1 + p2 |
|
225 | 239 |
|
|
226 | 240 | except IOError: |
|
227 | raise error.Abort(_('splicemap file not found or error reading %s:') | |
|
228 | % path) | |
|
241 | raise error.Abort( | |
|
242 | _('splicemap file not found or error reading %s:') % path | |
|
243 | ) | |
|
229 | 244 | return m |
|
230 | 245 | |
|
231 | ||
|
232 | 246 | def walktree(self, heads): |
|
233 | 247 | '''Return a mapping that identifies the uncommitted parents of every |
|
234 | 248 | uncommitted changeset.''' |
@@ -236,8 +250,9 b' class converter(object):' | |||
|
236 | 250 | known = set() |
|
237 | 251 | parents = {} |
|
238 | 252 | numcommits = self.source.numcommits() |
|
239 |
progress = self.ui.makeprogress( |
|
|
240 | total=numcommits) | |
|
253 | progress = self.ui.makeprogress( | |
|
254 | _('scanning'), unit=_('revisions'), total=numcommits | |
|
255 | ) | |
|
241 | 256 | while visit: |
|
242 | 257 | n = visit.pop(0) |
|
243 | 258 | if n in known: |
@@ -266,8 +281,13 b' class converter(object):' | |||
|
266 | 281 | if c not in parents: |
|
267 | 282 | if not self.dest.hascommitforsplicemap(self.map.get(c, c)): |
|
268 | 283 | # Could be in source but not converted during this run |
|
269 |
self.ui.warn( |
|
|
270 | 'converted, ignoring\n') % c) | |
|
284 | self.ui.warn( | |
|
285 | _( | |
|
286 | 'splice map revision %s is not being ' | |
|
287 | 'converted, ignoring\n' | |
|
288 | ) | |
|
289 | % c | |
|
290 | ) | |
|
271 | 291 | continue |
|
272 | 292 | pc = [] |
|
273 | 293 | for p in splicemap[c]: |
@@ -325,6 +345,7 b' class converter(object):' | |||
|
325 | 345 | compression. |
|
326 | 346 | """ |
|
327 | 347 | prev = [None] |
|
348 | ||
|
328 | 349 | def picknext(nodes): |
|
329 | 350 | next = nodes[0] |
|
330 | 351 | for n in nodes: |
@@ -333,26 +354,34 b' class converter(object):' | |||
|
333 | 354 | break |
|
334 | 355 | prev[0] = next |
|
335 | 356 | return next |
|
357 | ||
|
336 | 358 | return picknext |
|
337 | 359 | |
|
338 | 360 | def makesourcesorter(): |
|
339 | 361 | """Source specific sort.""" |
|
340 | 362 | keyfn = lambda n: self.commitcache[n].sortkey |
|
363 | ||
|
341 | 364 | def picknext(nodes): |
|
342 | 365 | return sorted(nodes, key=keyfn)[0] |
|
366 | ||
|
343 | 367 | return picknext |
|
344 | 368 | |
|
345 | 369 | def makeclosesorter(): |
|
346 | 370 | """Close order sort.""" |
|
347 |
keyfn = lambda n: ( |
|
|
348 |
|
|
|
371 | keyfn = lambda n: ( | |
|
372 | 'close' not in self.commitcache[n].extra, | |
|
373 | self.commitcache[n].sortkey, | |
|
374 | ) | |
|
375 | ||
|
349 | 376 | def picknext(nodes): |
|
350 | 377 | return sorted(nodes, key=keyfn)[0] |
|
378 | ||
|
351 | 379 | return picknext |
|
352 | 380 | |
|
353 | 381 | def makedatesorter(): |
|
354 | 382 | """Sort revisions by date.""" |
|
355 | 383 | dates = {} |
|
384 | ||
|
356 | 385 | def getdate(n): |
|
357 | 386 | if n not in dates: |
|
358 | 387 | dates[n] = dateutil.parsedate(self.commitcache[n].date) |
@@ -390,8 +419,10 b' class converter(object):' | |||
|
390 | 419 | try: |
|
391 | 420 | pendings[c].remove(n) |
|
392 | 421 | except ValueError: |
|
393 |
raise error.Abort( |
|
|
394 | % (recode(c), recode(n))) | |
|
422 | raise error.Abort( | |
|
423 | _('cycle detected between %s and %s') | |
|
424 | % (recode(c), recode(n)) | |
|
425 | ) | |
|
395 | 426 | if not pendings[c]: |
|
396 | 427 | # Parents are converted, node is eligible |
|
397 | 428 | actives.insert(0, c) |
@@ -408,8 +439,9 b' class converter(object):' | |||
|
408 | 439 | self.ui.status(_('writing author map file %s\n') % authorfile) |
|
409 | 440 | ofile = open(authorfile, 'wb+') |
|
410 | 441 | for author in self.authors: |
|
411 |
ofile.write( |
|
|
412 |
|
|
|
442 | ofile.write( | |
|
443 | util.tonativeeol("%s=%s\n" % (author, self.authors[author])) | |
|
444 | ) | |
|
413 | 445 | ofile.close() |
|
414 | 446 | |
|
415 | 447 | def readauthormap(self, authorfile): |
@@ -464,19 +496,22 b' class converter(object):' | |||
|
464 | 496 | for prev in commit.parents: |
|
465 | 497 | if prev not in self.commitcache: |
|
466 | 498 | self.cachecommit(prev) |
|
467 |
pbranches.append( |
|
|
468 |
|
|
|
499 | pbranches.append( | |
|
500 | (self.map[prev], self.commitcache[prev].branch) | |
|
501 | ) | |
|
469 | 502 | self.dest.setbranch(commit.branch, pbranches) |
|
470 | 503 | try: |
|
471 | 504 | parents = self.splicemap[rev] |
|
472 | self.ui.status(_('spliced in %s as parents of %s\n') % | |
|
473 | (_(' and ').join(parents), rev)) | |
|
505 | self.ui.status( | |
|
506 | _('spliced in %s as parents of %s\n') | |
|
507 | % (_(' and ').join(parents), rev) | |
|
508 | ) | |
|
474 | 509 | parents = [self.map.get(p, p) for p in parents] |
|
475 | 510 | except KeyError: |
|
476 | 511 | parents = [b[0] for b in pbranches] |
|
477 |
parents.extend( |
|
|
478 |
|
|
|
479 | if x in self.map) | |
|
512 | parents.extend( | |
|
513 | self.map[x] for x in commit.optparents if x in self.map | |
|
514 | ) | |
|
480 | 515 | if len(pbranches) != 2: |
|
481 | 516 | cleanp2 = set() |
|
482 | 517 | if len(parents) < 3: |
@@ -486,10 +521,12 b' class converter(object):' | |||
|
486 | 521 | # changed files N-1 times. This tweak to the number of |
|
487 | 522 | # files makes it so the progress bar doesn't overflow |
|
488 | 523 | # itself. |
|
489 |
source = progresssource( |
|
|
490 |
|
|
|
491 | newnode = self.dest.putcommit(files, copies, parents, commit, | |
|
492 | source, self.map, full, cleanp2) | |
|
524 | source = progresssource( | |
|
525 | self.ui, self.source, len(files) * (len(parents) - 1) | |
|
526 | ) | |
|
527 | newnode = self.dest.putcommit( | |
|
528 | files, copies, parents, commit, source, self.map, full, cleanp2 | |
|
529 | ) | |
|
493 | 530 | source.close() |
|
494 | 531 | self.source.converted(rev, newnode) |
|
495 | 532 | self.map[rev] = newnode |
@@ -509,8 +546,9 b' class converter(object):' | |||
|
509 | 546 | c = None |
|
510 | 547 | |
|
511 | 548 | self.ui.status(_("converting...\n")) |
|
512 |
progress = self.ui.makeprogress( |
|
|
513 |
|
|
|
549 | progress = self.ui.makeprogress( | |
|
550 | _('converting'), unit=_('revisions'), total=len(t) | |
|
551 | ) | |
|
514 | 552 | for i, c in enumerate(t): |
|
515 | 553 | num -= 1 |
|
516 | 554 | desc = self.commitcache[c].desc |
@@ -538,8 +576,11 b' class converter(object):' | |||
|
538 | 576 | if nrev and tagsparent: |
|
539 | 577 | # write another hash correspondence to override the |
|
540 | 578 | # previous one so we don't end up with extra tag heads |
|
541 |
tagsparents = [ |
|
|
542 |
|
|
|
579 | tagsparents = [ | |
|
580 | e | |
|
581 | for e in self.map.iteritems() | |
|
582 | if e[1] == tagsparent | |
|
583 | ] | |
|
543 | 584 | if tagsparents: |
|
544 | 585 | self.map[tagsparents[0][0]] = nrev |
|
545 | 586 | |
@@ -564,6 +605,7 b' class converter(object):' | |||
|
564 | 605 | self.source.after() |
|
565 | 606 | self.map.close() |
|
566 | 607 | |
|
608 | ||
|
567 | 609 | def convert(ui, src, dest=None, revmapfile=None, **opts): |
|
568 | 610 | opts = pycompat.byteskwargs(opts) |
|
569 | 611 | global orig_encoding |
@@ -582,8 +624,9 b' def convert(ui, src, dest=None, revmapfi' | |||
|
582 | 624 | destc = scmutil.wrapconvertsink(destc) |
|
583 | 625 | |
|
584 | 626 | try: |
|
585 |
srcc, defaultsort = convertsource( |
|
|
586 | opts.get('rev')) | |
|
627 | srcc, defaultsort = convertsource( | |
|
628 | ui, src, opts.get('source_type'), opts.get('rev') | |
|
629 | ) | |
|
587 | 630 | except Exception: |
|
588 | 631 | for path in destc.created: |
|
589 | 632 | shutil.rmtree(path, True) |
@@ -599,7 +642,8 b' def convert(ui, src, dest=None, revmapfi' | |||
|
599 | 642 | sortmode = defaultsort |
|
600 | 643 | |
|
601 | 644 | if sortmode == 'sourcesort' and not srcc.hasnativeorder(): |
|
602 | raise error.Abort(_('--sourcesort is not supported by this data source') | |
|
645 | raise error.Abort( | |
|
646 | _('--sourcesort is not supported by this data source') | |
|
603 | 647 | ) |
|
604 | 648 | if sortmode == 'closesort' and not srcc.hasnativeclose(): |
|
605 | 649 | raise error.Abort(_('--closesort is not supported by this data source')) |
@@ -34,6 +34,7 b' converter_source = common.converter_sour' | |||
|
34 | 34 | makedatetimestamp = common.makedatetimestamp |
|
35 | 35 | NoRepo = common.NoRepo |
|
36 | 36 | |
|
37 | ||
|
37 | 38 | class convert_cvs(converter_source): |
|
38 | 39 | def __init__(self, ui, repotype, path, revs=None): |
|
39 | 40 | super(convert_cvs, self).__init__(ui, repotype, path, revs=revs) |
@@ -63,15 +64,17 b' class convert_cvs(converter_source):' | |||
|
63 | 64 | maxrev = 0 |
|
64 | 65 | if self.revs: |
|
65 | 66 | if len(self.revs) > 1: |
|
66 |
raise error.Abort( |
|
|
67 |
|
|
|
67 | raise error.Abort( | |
|
68 | _('cvs source does not support specifying ' 'multiple revs') | |
|
69 | ) | |
|
68 | 70 | # TODO: handle tags |
|
69 | 71 | try: |
|
70 | 72 | # patchset number? |
|
71 | 73 | maxrev = int(self.revs[0]) |
|
72 | 74 | except ValueError: |
|
73 |
raise error.Abort( |
|
|
74 |
|
|
|
75 | raise error.Abort( | |
|
76 | _('revision %s is not a patchset number') % self.revs[0] | |
|
77 | ) | |
|
75 | 78 | |
|
76 | 79 | d = encoding.getcwd() |
|
77 | 80 | try: |
@@ -81,15 +84,18 b' class convert_cvs(converter_source):' | |||
|
81 | 84 | if not self.ui.configbool('convert', 'cvsps.cache'): |
|
82 | 85 | cache = None |
|
83 | 86 | db = cvsps.createlog(self.ui, cache=cache) |
|
84 |
db = cvsps.createchangeset( |
|
|
87 | db = cvsps.createchangeset( | |
|
88 | self.ui, | |
|
89 | db, | |
|
85 | 90 | fuzz=int(self.ui.config('convert', 'cvsps.fuzz')), |
|
86 | 91 | mergeto=self.ui.config('convert', 'cvsps.mergeto'), |
|
87 |
mergefrom=self.ui.config('convert', 'cvsps.mergefrom') |
|
|
92 | mergefrom=self.ui.config('convert', 'cvsps.mergefrom'), | |
|
93 | ) | |
|
88 | 94 | |
|
89 | 95 | for cs in db: |
|
90 | 96 | if maxrev and cs.id > maxrev: |
|
91 | 97 | break |
|
92 |
id = |
|
|
98 | id = b"%d" % cs.id | |
|
93 | 99 | cs.author = self.recode(cs.author) |
|
94 | 100 | self.lastbranch[cs.branch] = id |
|
95 | 101 | cs.comment = self.recode(cs.comment) |
@@ -100,14 +106,19 b' class convert_cvs(converter_source):' | |||
|
100 | 106 | |
|
101 | 107 | files = {} |
|
102 | 108 | for f in cs.entries: |
|
103 |
files[f.file] = "%s%s" % ( |
|
|
104 |
|
|
|
105 |
|
|
|
109 | files[f.file] = "%s%s" % ( | |
|
110 | '.'.join([(b"%d" % x) for x in f.revision]), | |
|
111 | ['', '(DEAD)'][f.dead], | |
|
112 | ) | |
|
106 | 113 | |
|
107 | 114 | # add current commit to set |
|
108 |
c = commit( |
|
|
115 | c = commit( | |
|
116 | author=cs.author, | |
|
117 | date=date, | |
|
109 | 118 |
|
|
110 |
|
|
|
119 | desc=cs.comment, | |
|
120 | branch=cs.branch or '', | |
|
121 | ) | |
|
111 | 122 | self.changeset[id] = c |
|
112 | 123 | self.files[id] = files |
|
113 | 124 | |
@@ -125,8 +136,9 b' class convert_cvs(converter_source):' | |||
|
125 | 136 | |
|
126 | 137 | if root.startswith(":pserver:"): |
|
127 | 138 | root = root[9:] |
|
128 | m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', | |
|
129 | root) | |
|
139 | m = re.match( | |
|
140 | r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root | |
|
141 | ) | |
|
130 | 142 | if m: |
|
131 | 143 | conntype = "pserver" |
|
132 | 144 | user, passw, serv, port, root = m.groups() |
@@ -166,8 +178,18 b' class convert_cvs(converter_source):' | |||
|
166 | 178 | |
|
167 | 179 | sck = socket.socket() |
|
168 | 180 | sck.connect((serv, port)) |
|
169 | sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, | |
|
170 | "END AUTH REQUEST", ""])) | |
|
181 | sck.send( | |
|
182 | "\n".join( | |
|
183 | [ | |
|
184 | "BEGIN AUTH REQUEST", | |
|
185 | root, | |
|
186 | user, | |
|
187 | passw, | |
|
188 | "END AUTH REQUEST", | |
|
189 | "", | |
|
190 | ] | |
|
191 | ) | |
|
192 | ) | |
|
171 | 193 | if sck.recv(128) != "I LOVE YOU\n": |
|
172 | 194 | raise error.Abort(_("CVS pserver authentication failed")) |
|
173 | 195 | |
@@ -205,16 +227,22 b' class convert_cvs(converter_source):' | |||
|
205 | 227 | self.realroot = root |
|
206 | 228 | |
|
207 | 229 | self.writep.write("Root %s\n" % root) |
|
208 | self.writep.write("Valid-responses ok error Valid-requests Mode" | |
|
230 | self.writep.write( | |
|
231 | "Valid-responses ok error Valid-requests Mode" | |
|
209 | 232 |
|
|
210 |
|
|
|
233 | " Merged Removed\n" | |
|
234 | ) | |
|
211 | 235 | self.writep.write("valid-requests\n") |
|
212 | 236 | self.writep.flush() |
|
213 | 237 | r = self.readp.readline() |
|
214 | 238 | if not r.startswith("Valid-requests"): |
|
215 |
raise error.Abort( |
|
|
216 | '(expected "Valid-requests", but got %r)') | |
|
217 | % r) | |
|
239 | raise error.Abort( | |
|
240 | _( | |
|
241 | 'unexpected response from CVS server ' | |
|
242 | '(expected "Valid-requests", but got %r)' | |
|
243 | ) | |
|
244 | % r | |
|
245 | ) | |
|
218 | 246 | if "UseUnchanged" in r: |
|
219 | 247 | self.writep.write("UseUnchanged\n") |
|
220 | 248 | self.writep.flush() |
@@ -225,7 +253,6 b' class convert_cvs(converter_source):' | |||
|
225 | 253 | return self.heads |
|
226 | 254 | |
|
227 | 255 | def getfile(self, name, rev): |
|
228 | ||
|
229 | 256 | def chunkedread(fp, count): |
|
230 | 257 | # file-objects returned by socket.makefile() do not handle |
|
231 | 258 | # large read() requests very well. |
@@ -234,8 +261,9 b' class convert_cvs(converter_source):' | |||
|
234 | 261 | while count > 0: |
|
235 | 262 | data = fp.read(min(count, chunksize)) |
|
236 | 263 | if not data: |
|
237 |
raise error.Abort( |
|
|
238 |
|
|
|
264 | raise error.Abort( | |
|
265 | _("%d bytes missing from remote file") % count | |
|
266 | ) | |
|
239 | 267 | count -= len(data) |
|
240 | 268 | output.write(data) |
|
241 | 269 | return output.getvalue() |
@@ -26,6 +26,7 b' from mercurial.utils import (' | |||
|
26 | 26 | |
|
27 | 27 | pickle = util.pickle |
|
28 | 28 | |
|
29 | ||
|
29 | 30 | class logentry(object): |
|
30 | 31 | '''Class logentry has the following attributes: |
|
31 | 32 | .author - author name as CVS knows it |
@@ -46,17 +47,22 b' class logentry(object):' | |||
|
46 | 47 | rlog output) or None |
|
47 | 48 | .branchpoints - the branches that start at the current entry or empty |
|
48 | 49 | ''' |
|
50 | ||
|
49 | 51 | def __init__(self, **entries): |
|
50 | 52 | self.synthetic = False |
|
51 | 53 | self.__dict__.update(entries) |
|
52 | 54 | |
|
53 | 55 | def __repr__(self): |
|
54 | items = (r"%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__)) | |
|
56 | items = ( | |
|
57 | r"%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__) | |
|
58 | ) | |
|
55 | 59 | return r"%s(%s)"%(type(self).__name__, r", ".join(items)) |
|
56 | 60 | |
|
61 | ||
|
57 | 62 | class logerror(Exception): |
|
58 | 63 | pass |
|
59 | 64 | |
|
65 | ||
|
60 | 66 | def getrepopath(cvspath): |
|
61 | 67 | """Return the repository path from a CVS path. |
|
62 | 68 | |
@@ -96,12 +102,14 b' def getrepopath(cvspath):' | |||
|
96 | 102 | repopath = parts[-1][parts[-1].find('/', start):] |
|
97 | 103 | return repopath |
|
98 | 104 | |
|
105 | ||
|
99 | 106 | def createlog(ui, directory=None, root="", rlog=True, cache=None): |
|
100 | 107 | '''Collect the CVS rlog''' |
|
101 | 108 | |
|
102 | 109 | # Because we store many duplicate commit log messages, reusing strings |
|
103 | 110 | # saves a lot of memory and pickle storage space. |
|
104 | 111 | _scache = {} |
|
112 | ||
|
105 | 113 | def scache(s): |
|
106 | 114 | "return a shared version of a string" |
|
107 | 115 | return _scache.setdefault(s, s) |
@@ -114,19 +122,24 b' def createlog(ui, directory=None, root="' | |||
|
114 | 122 | re_00 = re.compile(b'RCS file: (.+)$') |
|
115 | 123 | re_01 = re.compile(b'cvs \\[r?log aborted\\]: (.+)$') |
|
116 | 124 | re_02 = re.compile(b'cvs (r?log|server): (.+)\n$') |
|
117 |
re_03 = re.compile( |
|
|
118 |
|
|
|
125 | re_03 = re.compile( | |
|
126 | b"(Cannot access.+CVSROOT)|" b"(can't create temporary directory.+)$" | |
|
127 | ) | |
|
119 | 128 | re_10 = re.compile(b'Working file: (.+)$') |
|
120 | 129 | re_20 = re.compile(b'symbolic names:') |
|
121 | 130 | re_30 = re.compile(b'\t(.+): ([\\d.]+)$') |
|
122 | 131 | re_31 = re.compile(b'----------------------------$') |
|
123 | re_32 = re.compile(b'=======================================' | |
|
124 |
|
|
|
132 | re_32 = re.compile( | |
|
133 | b'=======================================' | |
|
134 | b'======================================$' | |
|
135 | ) | |
|
125 | 136 | re_50 = re.compile(br'revision ([\d.]+)(\s+locked by:\s+.+;)?$') |
|
126 | re_60 = re.compile(br'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);' | |
|
137 | re_60 = re.compile( | |
|
138 | br'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);' | |
|
127 | 139 |
|
|
128 | 140 |
|
|
129 |
|
|
|
141 | br'(.*mergepoint:\s+([^;]+);)?' | |
|
142 | ) | |
|
130 | 143 | re_70 = re.compile(b'branches: (.+);$') |
|
131 | 144 | |
|
132 | 145 | file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch') |
@@ -178,17 +191,20 b' def createlog(ui, directory=None, root="' | |||
|
178 | 191 | # are mapped to different cache file names. |
|
179 | 192 | cachefile = root.split(":") + [directory, "cache"] |
|
180 | 193 | cachefile = ['-'.join(re.findall(br'\w+', s)) for s in cachefile if s] |
|
181 |
cachefile = os.path.join( |
|
|
182 |
|
|
|
194 | cachefile = os.path.join( | |
|
195 | cachedir, '.'.join([s for s in cachefile if s]) | |
|
196 | ) | |
|
183 | 197 | |
|
184 | 198 | if cache == 'update': |
|
185 | 199 | try: |
|
186 | 200 | ui.note(_('reading cvs log cache %s\n') % cachefile) |
|
187 | 201 | oldlog = pickle.load(open(cachefile, 'rb')) |
|
188 | 202 | for e in oldlog: |
|
189 | if not (util.safehasattr(e, 'branchpoints') and | |
|
190 |
|
|
|
191 |
|
|
|
203 | if not ( | |
|
204 | util.safehasattr(e, 'branchpoints') | |
|
205 | and util.safehasattr(e, 'commitid') | |
|
206 | and util.safehasattr(e, 'mergepoint') | |
|
207 | ): | |
|
192 | 208 | ui.status(_('ignoring old cache\n')) |
|
193 | 209 | oldlog = [] |
|
194 | 210 | break |
@@ -310,8 +326,9 b' def createlog(ui, directory=None, root="' | |||
|
310 | 326 | if re_31.match(line): |
|
311 | 327 | state = 5 |
|
312 | 328 | else: |
|
313 |
assert not re_32.match(line), _( |
|
|
314 | 'some revisions') | |
|
329 | assert not re_32.match(line), _( | |
|
330 | 'must have at least ' 'some revisions' | |
|
331 | ) | |
|
315 | 332 | |
|
316 | 333 | elif state == 5: |
|
317 | 334 | # expecting revision number and possibly (ignored) lock indication |
@@ -319,15 +336,16 b' def createlog(ui, directory=None, root="' | |||
|
319 | 336 | # as this state is re-entered for subsequent revisions of a file. |
|
320 | 337 | match = re_50.match(line) |
|
321 | 338 | assert match, _('expected revision number') |
|
322 |
e = logentry( |
|
|
339 | e = logentry( | |
|
340 | rcs=scache(rcs), | |
|
323 | 341 |
|
|
324 |
|
|
|
325 | match.group(1).split('.')]), | |
|
342 | revision=tuple([int(x) for x in match.group(1).split('.')]), | |
|
326 | 343 |
|
|
327 | 344 |
|
|
328 | 345 |
|
|
329 | 346 |
|
|
330 |
|
|
|
347 | branchpoints=set(), | |
|
348 | ) | |
|
331 | 349 | |
|
332 | 350 | state = 6 |
|
333 | 351 | |
@@ -343,9 +361,10 b' def createlog(ui, directory=None, root="' | |||
|
343 | 361 | if len(d.split()) != 3: |
|
344 | 362 | # cvs log dates always in GMT |
|
345 | 363 | d = d + ' UTC' |
|
346 |
e.date = dateutil.parsedate( |
|
|
347 | '%Y/%m/%d %H:%M:%S', | |
|
348 | '%Y-%m-%d %H:%M:%S']) | |
|
364 | e.date = dateutil.parsedate( | |
|
365 | d, | |
|
366 | ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S'], | |
|
367 | ) | |
|
349 | 368 | e.author = scache(match.group(2)) |
|
350 | 369 | e.dead = match.group(3).lower() == 'dead' |
|
351 | 370 | |
@@ -369,8 +388,9 b' def createlog(ui, directory=None, root="' | |||
|
369 | 388 | else: |
|
370 | 389 | myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]]) |
|
371 | 390 | branches = [b for b in branchmap if branchmap[b] == myrev] |
|
372 |
assert len(branches) == 1, ( |
|
|
373 |
|
|
|
391 | assert len(branches) == 1, ( | |
|
392 | 'unknown branch: %s' % e.mergepoint | |
|
393 | ) | |
|
374 | 394 | e.mergepoint = branches[0] |
|
375 | 395 | |
|
376 | 396 | e.comment = [] |
@@ -381,8 +401,10 b' def createlog(ui, directory=None, root="' | |||
|
381 | 401 | # or store the commit log message otherwise |
|
382 | 402 | m = re_70.match(line) |
|
383 | 403 | if m: |
|
384 | e.branches = [tuple([int(y) for y in x.strip().split('.')]) | |
|
385 |
|
|
|
404 | e.branches = [ | |
|
405 | tuple([int(y) for y in x.strip().split('.')]) | |
|
406 | for x in m.group(1).split(';') | |
|
407 | ] | |
|
386 | 408 | state = 8 |
|
387 | 409 | elif re_31.match(line) and re_50.match(peek): |
|
388 | 410 | state = 5 |
@@ -417,13 +439,16 b' def createlog(ui, directory=None, root="' | |||
|
417 | 439 | # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop |
|
418 | 440 | # these revisions now, but mark them synthetic so |
|
419 | 441 | # createchangeset() can take care of them. |
|
420 |
if ( |
|
|
421 |
|
|
|
422 | e.revision[-1] == 1 and # 1.1 or 1.1.x.1 | |
|
423 |
|
|
|
424 | file_added_re.match(e.comment[0])): | |
|
425 | ui.debug('found synthetic revision in %s: %r\n' | |
|
426 | % (e.rcs, e.comment[0])) | |
|
442 | if ( | |
|
443 | store | |
|
444 | and e.dead | |
|
445 | and e.revision[-1] == 1 | |
|
446 | and len(e.comment) == 1 # 1.1 or 1.1.x.1 | |
|
447 | and file_added_re.match(e.comment[0]) | |
|
448 | ): | |
|
449 | ui.debug( | |
|
450 | 'found synthetic revision in %s: %r\n' % (e.rcs, e.comment[0]) | |
|
451 | ) | |
|
427 | 452 | e.synthetic = True |
|
428 | 453 | |
|
429 | 454 | if store: |
@@ -458,8 +483,9 b' def createlog(ui, directory=None, root="' | |||
|
458 | 483 | rcsmap[e.rcs.replace('/Attic/', '/')] = e.rcs |
|
459 | 484 | |
|
460 | 485 | if len(log) % 100 == 0: |
|
461 | ui.status(stringutil.ellipsis('%d %s' % (len(log), e.file), 80) | |
|
462 | + '\n') | |
|
486 | ui.status( | |
|
487 | stringutil.ellipsis('%d %s' % (len(log), e.file), 80) + '\n' | |
|
488 | ) | |
|
463 | 489 | |
|
464 | 490 | log.sort(key=lambda x: (x.rcs, x.revision)) |
|
465 | 491 | |
@@ -487,8 +513,12 b' def createlog(ui, directory=None, root="' | |||
|
487 | 513 | log.sort(key=lambda x: x.date) |
|
488 | 514 | |
|
489 | 515 | if oldlog and oldlog[-1].date >= log[0].date: |
|
490 | raise logerror(_('log cache overlaps with new log entries,' | |
|
491 | ' re-run without cache.')) | |
|
516 | raise logerror( | |
|
517 | _( | |
|
518 | 'log cache overlaps with new log entries,' | |
|
519 | ' re-run without cache.' | |
|
520 | ) | |
|
521 | ) | |
|
492 | 522 | |
|
493 | 523 | log = oldlog + log |
|
494 | 524 | |
@@ -502,6 +532,7 b' def createlog(ui, directory=None, root="' | |||
|
502 | 532 | |
|
503 | 533 | encodings = ui.configlist('convert', 'cvsps.logencoding') |
|
504 | 534 | if encodings: |
|
535 | ||
|
505 | 536 | def revstr(r): |
|
506 | 537 | # this is needed, because logentry.revision is a tuple of "int" |
|
507 | 538 | # (e.g. (1, 2) for "1.2") |
@@ -511,24 +542,33 b' def createlog(ui, directory=None, root="' | |||
|
511 | 542 | comment = entry.comment |
|
512 | 543 | for e in encodings: |
|
513 | 544 | try: |
|
514 | entry.comment = comment.decode( | |
|
515 |
|
|
|
545 | entry.comment = comment.decode(pycompat.sysstr(e)).encode( | |
|
546 | 'utf-8' | |
|
547 | ) | |
|
516 | 548 | if ui.debugflag: |
|
517 |
ui.debug( |
|
|
518 | (e, revstr(entry.revision), entry.file)) | |
|
549 | ui.debug( | |
|
550 | "transcoding by %s: %s of %s\n" | |
|
551 | % (e, revstr(entry.revision), entry.file) | |
|
552 | ) | |
|
519 | 553 | break |
|
520 | 554 | except UnicodeDecodeError: |
|
521 | 555 | pass # try next encoding |
|
522 | 556 | except LookupError as inst: # unknown encoding, maybe |
|
523 |
raise error.Abort( |
|
|
524 | hint=_('check convert.cvsps.logencoding' | |
|
525 | ' configuration')) | |
|
557 | raise error.Abort( | |
|
558 | inst, | |
|
559 | hint=_( | |
|
560 | 'check convert.cvsps.logencoding' ' configuration' | |
|
561 | ), | |
|
562 | ) | |
|
526 | 563 | else: |
|
527 |
raise error.Abort( |
|
|
528 | " CVS log message for %s of %s") | |
|
564 | raise error.Abort( | |
|
565 | _( | |
|
566 | "no encoding can transcode" | |
|
567 | " CVS log message for %s of %s" | |
|
568 | ) | |
|
529 | 569 |
|
|
530 |
|
|
|
531 | ' configuration')) | |
|
570 | hint=_('check convert.cvsps.logencoding' ' configuration'), | |
|
571 | ) | |
|
532 | 572 | |
|
533 | 573 | hook.hook(ui, None, "cvslog", True, log=log) |
|
534 | 574 | |
@@ -550,6 +590,7 b' class changeset(object):' | |||
|
550 | 590 | .mergepoint- the branch that has been merged from or None |
|
551 | 591 | .branchpoints- the branches that start at the current entry or empty |
|
552 | 592 | ''' |
|
593 | ||
|
553 | 594 | def __init__(self, **entries): |
|
554 | 595 | self.id = None |
|
555 | 596 | self.synthetic = False |
@@ -559,6 +600,7 b' class changeset(object):' | |||
|
559 | 600 | items = ("%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__)) |
|
560 | 601 | return "%s(%s)"%(type(self).__name__, ", ".join(items)) |
|
561 | 602 | |
|
603 | ||
|
562 | 604 | def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None): |
|
563 | 605 | '''Convert log into changesets.''' |
|
564 | 606 | |
@@ -574,9 +616,17 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
574 | 616 | mindate[e.commitid] = min(e.date, mindate[e.commitid]) |
|
575 | 617 | |
|
576 | 618 | # Merge changesets |
|
577 | log.sort(key=lambda x: (mindate.get(x.commitid, (-1, 0)), | |
|
578 | x.commitid or '', x.comment, | |
|
579 | x.author, x.branch or '', x.date, x.branchpoints)) | |
|
619 | log.sort( | |
|
620 | key=lambda x: ( | |
|
621 | mindate.get(x.commitid, (-1, 0)), | |
|
622 | x.commitid or '', | |
|
623 | x.comment, | |
|
624 | x.author, | |
|
625 | x.branch or '', | |
|
626 | x.date, | |
|
627 | x.branchpoints, | |
|
628 | ) | |
|
629 | ) | |
|
580 | 630 | |
|
581 | 631 | changesets = [] |
|
582 | 632 | files = set() |
@@ -599,22 +649,35 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
599 | 649 | # first changeset and bar the next and MYBRANCH and MYBRANCH2 |
|
600 | 650 | # should both start off of the bar changeset. No provisions are |
|
601 | 651 | # made to ensure that this is, in fact, what happens. |
|
602 | if not (c and e.branchpoints == c.branchpoints and | |
|
603 | (# cvs commitids | |
|
604 | (e.commitid is not None and e.commitid == c.commitid) or | |
|
605 | (# no commitids, use fuzzy commit detection | |
|
606 |
|
|
|
607 |
|
|
|
608 | e.author == c.author and | |
|
609 |
e. |
|
|
610 |
|
|
|
611 |
|
|
|
612 | (c.date[0] + c.date[1]) + fuzz) and | |
|
613 | e.file not in files))): | |
|
614 | c = changeset(comment=e.comment, author=e.author, | |
|
615 | branch=e.branch, date=e.date, | |
|
616 | entries=[], mergepoint=e.mergepoint, | |
|
617 | branchpoints=e.branchpoints, commitid=e.commitid) | |
|
652 | if not ( | |
|
653 | c | |
|
654 | and e.branchpoints == c.branchpoints | |
|
655 | and ( # cvs commitids | |
|
656 | (e.commitid is not None and e.commitid == c.commitid) | |
|
657 | or ( # no commitids, use fuzzy commit detection | |
|
658 | (e.commitid is None or c.commitid is None) | |
|
659 | and e.comment == c.comment | |
|
660 | and e.author == c.author | |
|
661 | and e.branch == c.branch | |
|
662 | and ( | |
|
663 | (c.date[0] + c.date[1]) | |
|
664 | <= (e.date[0] + e.date[1]) | |
|
665 | <= (c.date[0] + c.date[1]) + fuzz | |
|
666 | ) | |
|
667 | and e.file not in files | |
|
668 | ) | |
|
669 | ) | |
|
670 | ): | |
|
671 | c = changeset( | |
|
672 | comment=e.comment, | |
|
673 | author=e.author, | |
|
674 | branch=e.branch, | |
|
675 | date=e.date, | |
|
676 | entries=[], | |
|
677 | mergepoint=e.mergepoint, | |
|
678 | branchpoints=e.branchpoints, | |
|
679 | commitid=e.commitid, | |
|
680 | ) | |
|
618 | 681 | changesets.append(c) |
|
619 | 682 | |
|
620 | 683 | files = set() |
@@ -665,6 +728,7 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
665 | 728 | # Sort changesets by date |
|
666 | 729 | |
|
667 | 730 | odd = set() |
|
731 | ||
|
668 | 732 | def cscmp(l, r): |
|
669 | 733 | d = sum(l.date) - sum(r.date) |
|
670 | 734 | if d: |
@@ -777,8 +841,9 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
777 | 841 | |
|
778 | 842 | # Ensure no changeset has a synthetic changeset as a parent. |
|
779 | 843 | while p.synthetic: |
|
780 | assert len(p.parents) <= 1, ( | |
|
781 |
|
|
|
844 | assert len(p.parents) <= 1, _( | |
|
845 | 'synthetic changeset cannot have multiple parents' | |
|
846 | ) | |
|
782 | 847 | if p.parents: |
|
783 | 848 | p = p.parents[0] |
|
784 | 849 | else: |
@@ -802,9 +867,13 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
802 | 867 | try: |
|
803 | 868 | candidate = changesets[branches[m]] |
|
804 | 869 | except KeyError: |
|
805 | ui.warn(_("warning: CVS commit message references " | |
|
806 | "non-existent branch %r:\n%s\n") | |
|
807 | % (pycompat.bytestr(m), c.comment)) | |
|
870 | ui.warn( | |
|
871 | _( | |
|
872 | "warning: CVS commit message references " | |
|
873 | "non-existent branch %r:\n%s\n" | |
|
874 | ) | |
|
875 | % (pycompat.bytestr(m), c.comment) | |
|
876 | ) | |
|
808 | 877 | if m in branches and c.branch != m and not candidate.synthetic: |
|
809 | 878 | c.parents.append(candidate) |
|
810 | 879 | |
@@ -820,11 +889,15 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
820 | 889 | if m in branches and c.branch != m: |
|
821 | 890 | # insert empty changeset for merge |
|
822 | 891 | cc = changeset( |
|
823 |
author=c.author, |
|
|
892 | author=c.author, | |
|
893 | branch=m, | |
|
894 | date=c.date, | |
|
824 | 895 | comment='convert-repo: CVS merge from branch %s' |
|
825 | 896 | % c.branch, |
|
826 |
entries=[], |
|
|
827 |
|
|
|
897 | entries=[], | |
|
898 | tags=[], | |
|
899 | parents=[changesets[branches[m]], c], | |
|
900 | ) | |
|
828 | 901 | changesets.insert(i + 1, cc) |
|
829 | 902 | branches[m] = i + 1 |
|
830 | 903 | |
@@ -853,8 +926,10 b' def createchangeset(ui, log, fuzz=60, me' | |||
|
853 | 926 | if odd: |
|
854 | 927 | for l, r in odd: |
|
855 | 928 | if l.id is not None and r.id is not None: |
|
856 | ui.warn(_('changeset %d is both before and after %d\n') | |
|
857 | % (l.id, r.id)) | |
|
929 | ui.warn( | |
|
930 | _('changeset %d is both before and after %d\n') | |
|
931 | % (l.id, r.id) | |
|
932 | ) | |
|
858 | 933 | |
|
859 | 934 | ui.status(_('%d changeset entries\n') % len(changesets)) |
|
860 | 935 | |
@@ -901,8 +976,10 b' def debugcvsps(ui, *args, **opts):' | |||
|
901 | 976 | |
|
902 | 977 | if opts["ancestors"]: |
|
903 | 978 | if cs.branch not in branches and cs.parents and cs.parents[0].id: |
|
904 |
ancestors[cs.branch] = ( |
|
|
905 |
|
|
|
979 | ancestors[cs.branch] = ( | |
|
980 | changesets[cs.parents[0].id - 1].branch, | |
|
981 | cs.parents[0].id, | |
|
982 | ) | |
|
906 | 983 | branches[cs.branch] = cs.id |
|
907 | 984 | |
|
908 | 985 | # limit by branches |
@@ -914,19 +991,35 b' def debugcvsps(ui, *args, **opts):' | |||
|
914 | 991 | # bug-for-bug compatibility with cvsps. |
|
915 | 992 | ui.write('---------------------\n') |
|
916 | 993 | ui.write(('PatchSet %d \n' % cs.id)) |
|
917 | ui.write(('Date: %s\n' % dateutil.datestr(cs.date, | |
|
918 | '%Y/%m/%d %H:%M:%S %1%2'))) | |
|
994 | ui.write( | |
|
995 | ( | |
|
996 | 'Date: %s\n' | |
|
997 | % dateutil.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2') | |
|
998 | ) | |
|
999 | ) | |
|
919 | 1000 | ui.write(('Author: %s\n' % cs.author)) |
|
920 | 1001 | ui.write(('Branch: %s\n' % (cs.branch or 'HEAD'))) |
|
921 | ui.write(('Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1], | |
|
922 | ','.join(cs.tags) or '(none)'))) | |
|
1002 | ui.write( | |
|
1003 | ( | |
|
1004 | 'Tag%s: %s \n' | |
|
1005 | % ( | |
|
1006 | ['', 's'][len(cs.tags) > 1], | |
|
1007 | ','.join(cs.tags) or '(none)', | |
|
1008 | ) | |
|
1009 | ) | |
|
1010 | ) | |
|
923 | 1011 | if cs.branchpoints: |
|
924 |
ui.write( |
|
|
925 |
|
|
|
1012 | ui.write( | |
|
1013 | 'Branchpoints: %s \n' % ', '.join(sorted(cs.branchpoints)) | |
|
1014 | ) | |
|
926 | 1015 | if opts["parents"] and cs.parents: |
|
927 | 1016 | if len(cs.parents) > 1: |
|
928 |
ui.write( |
|
|
929 | (','.join([(b"%d" % p.id) for p in cs.parents])))) | |
|
1017 | ui.write( | |
|
1018 | ( | |
|
1019 | 'Parents: %s\n' | |
|
1020 | % (','.join([(b"%d" % p.id) for p in cs.parents])) | |
|
1021 | ) | |
|
1022 | ) | |
|
930 | 1023 | else: |
|
931 | 1024 | ui.write(('Parent: %d\n' % cs.parents[0].id)) |
|
932 | 1025 | |
@@ -939,28 +1032,30 b' def debugcvsps(ui, *args, **opts):' | |||
|
939 | 1032 | if r: |
|
940 | 1033 | ui.write(('Ancestors: %s\n' % (','.join(r)))) |
|
941 | 1034 | |
|
942 |
ui.write( |
|
|
1035 | ui.write('Log:\n') | |
|
943 | 1036 | ui.write('%s\n\n' % cs.comment) |
|
944 |
ui.write( |
|
|
1037 | ui.write('Members: \n') | |
|
945 | 1038 | for f in cs.entries: |
|
946 | 1039 | fn = f.file |
|
947 | 1040 | if fn.startswith(opts["prefix"]): |
|
948 | 1041 | fn = fn[len(opts["prefix"]):] |
|
949 |
ui.write( |
|
|
1042 | ui.write( | |
|
1043 | '\t%s:%s->%s%s \n' | |
|
1044 | % ( | |
|
950 | 1045 | fn, |
|
951 | 1046 | '.'.join([b"%d" % x for x in f.parent]) or 'INITIAL', |
|
952 | 1047 | '.'.join([(b"%d" % x) for x in f.revision]), |
|
953 |
['', '(DEAD)'][f.dead] |
|
|
1048 | ['', '(DEAD)'][f.dead], | |
|
1049 | ) | |
|
1050 | ) | |
|
954 | 1051 | ui.write('\n') |
|
955 | 1052 | |
|
956 | 1053 | # have we seen the start tag? |
|
957 | 1054 | if revisions and off: |
|
958 |
if |
|
|
959 | revisions[0] in cs.tags): | |
|
1055 | if revisions[0] == (b"%d" % cs.id) or revisions[0] in cs.tags: | |
|
960 | 1056 | off = False |
|
961 | 1057 | |
|
962 | 1058 | # see if we reached the end tag |
|
963 | 1059 | if len(revisions) > 1 and not off: |
|
964 |
if |
|
|
965 | revisions[1] in cs.tags): | |
|
1060 | if revisions[1] == (b"%d" % cs.id) or revisions[1] in cs.tags: | |
|
966 | 1061 | break |
@@ -19,6 +19,7 b' from mercurial import (' | |||
|
19 | 19 | ) |
|
20 | 20 | from mercurial.utils import dateutil |
|
21 | 21 | from . import common |
|
22 | ||
|
22 | 23 | NoRepo = common.NoRepo |
|
23 | 24 | |
|
24 | 25 | # The naming drift of ElementTree is fun! |
@@ -41,6 +42,7 b' except ImportError:' | |||
|
41 | 42 | except ImportError: |
|
42 | 43 | pass |
|
43 | 44 | |
|
45 | ||
|
44 | 46 | class darcs_source(common.converter_source, common.commandline): |
|
45 | 47 | def __init__(self, ui, repotype, path, revs=None): |
|
46 | 48 | common.converter_source.__init__(self, ui, repotype, path, revs=revs) |
@@ -54,8 +56,9 b' class darcs_source(common.converter_sour' | |||
|
54 | 56 | common.checktool('darcs') |
|
55 | 57 | version = self.run0('--version').splitlines()[0].strip() |
|
56 | 58 | if version < '2.1': |
|
57 | raise error.Abort(_('darcs version 2.1 or newer needed (found %r)') | |
|
58 | % version) | |
|
59 | raise error.Abort( | |
|
60 | _('darcs version 2.1 or newer needed (found %r)') % version | |
|
61 | ) | |
|
59 | 62 | |
|
60 | 63 | if "ElementTree" not in globals(): |
|
61 | 64 | raise error.Abort(_("Python ElementTree module is not available")) |
@@ -71,19 +74,23 b' class darcs_source(common.converter_sour' | |||
|
71 | 74 | format = self.format() |
|
72 | 75 | if format: |
|
73 | 76 | if format in ('darcs-1.0', 'hashed'): |
|
74 | raise NoRepo(_("%s repository format is unsupported, " | |
|
75 |
|
|
|
77 | raise NoRepo( | |
|
78 | _("%s repository format is unsupported, " "please upgrade") | |
|
79 | % format | |
|
80 | ) | |
|
76 | 81 | else: |
|
77 | 82 | self.ui.warn(_('failed to detect repository format!')) |
|
78 | 83 | |
|
79 | 84 | def before(self): |
|
80 | 85 | self.tmppath = pycompat.mkdtemp( |
|
81 |
prefix='convert-' + os.path.basename(self.path) + '-' |
|
|
86 | prefix='convert-' + os.path.basename(self.path) + '-' | |
|
87 | ) | |
|
82 | 88 | output, status = self.run('init', repodir=self.tmppath) |
|
83 | 89 | self.checkexit(status) |
|
84 | 90 | |
|
85 | tree = self.xml('changes', xml_output=True, summary=True, | |
|
86 | repodir=self.path) | |
|
91 | tree = self.xml( | |
|
92 | 'changes', xml_output=True, summary=True, repodir=self.path | |
|
93 | ) | |
|
87 | 94 | tagname = None |
|
88 | 95 | child = None |
|
89 | 96 | for elt in tree.findall('patch'): |
@@ -135,8 +142,9 b' class darcs_source(common.converter_sour' | |||
|
135 | 142 | |
|
136 | 143 | def manifest(self): |
|
137 | 144 | man = [] |
|
138 |
output, status = self.run( |
|
|
139 | repodir=self.tmppath) | |
|
145 | output, status = self.run( | |
|
146 | 'show', 'files', no_directories=True, repodir=self.tmppath | |
|
147 | ) | |
|
140 | 148 | self.checkexit(status) |
|
141 | 149 | for line in output.split('\n'): |
|
142 | 150 | path = line[2:] |
@@ -155,17 +163,24 b' class darcs_source(common.converter_sour' | |||
|
155 | 163 | # etree can return unicode objects for name, comment, and author, |
|
156 | 164 | # so recode() is used to ensure str objects are emitted. |
|
157 | 165 | newdateformat = '%Y-%m-%d %H:%M:%S %1%2' |
|
158 |
return common.commit( |
|
|
166 | return common.commit( | |
|
167 | author=self.recode(elt.get('author')), | |
|
159 | 168 |
|
|
160 | 169 |
|
|
161 |
|
|
|
170 | parents=self.parents[rev], | |
|
171 | ) | |
|
162 | 172 | |
|
163 | 173 | def pull(self, rev): |
|
164 |
output, status = self.run( |
|
|
174 | output, status = self.run( | |
|
175 | 'pull', | |
|
176 | self.path, | |
|
177 | all=True, | |
|
165 | 178 |
|
|
166 | no_test=True, no_posthook=True, | |
|
179 | no_test=True, | |
|
180 | no_posthook=True, | |
|
167 | 181 |
|
|
168 |
|
|
|
182 | repodir=self.tmppath, | |
|
183 | ) | |
|
169 | 184 | if status: |
|
170 | 185 | if output.find('We have conflicts in') == -1: |
|
171 | 186 | self.checkexit(status, output) |
@@ -14,8 +14,10 b' from mercurial import (' | |||
|
14 | 14 | pycompat, |
|
15 | 15 | ) |
|
16 | 16 | from . import common |
|
17 | ||
|
17 | 18 | SKIPREV = common.SKIPREV |
|
18 | 19 | |
|
20 | ||
|
19 | 21 | def rpairs(path): |
|
20 | 22 | '''Yield tuples with path split at '/', starting with the full path. |
|
21 | 23 | No leading, trailing or double '/', please. |
@@ -31,6 +33,7 b' def rpairs(path):' | |||
|
31 | 33 | i = path.rfind('/', 0, i) |
|
32 | 34 | yield '.', path |
|
33 | 35 | |
|
36 | ||
|
34 | 37 | def normalize(path): |
|
35 | 38 | ''' We use posixpath.normpath to support cross-platform path format. |
|
36 | 39 | However, it doesn't handle None input. So we wrap it up. ''' |
@@ -38,6 +41,7 b' def normalize(path):' | |||
|
38 | 41 | return None |
|
39 | 42 | return posixpath.normpath(path) |
|
40 | 43 | |
|
44 | ||
|
41 | 45 | class filemapper(object): |
|
42 | 46 | '''Map and filter filenames when importing. |
|
43 | 47 | A name can be mapped to itself, a new name, or None (omit from new |
@@ -55,25 +59,31 b' class filemapper(object):' | |||
|
55 | 59 | |
|
56 | 60 | def parse(self, path): |
|
57 | 61 | errs = 0 |
|
62 | ||
|
58 | 63 | def check(name, mapping, listname): |
|
59 | 64 | if not name: |
|
60 |
self.ui.warn( |
|
|
61 | (lex.infile, lex.lineno, listname)) | |
|
65 | self.ui.warn( | |
|
66 | _('%s:%d: path to %s is missing\n') | |
|
67 | % (lex.infile, lex.lineno, listname) | |
|
68 | ) | |
|
62 | 69 | return 1 |
|
63 | 70 | if name in mapping: |
|
64 |
self.ui.warn( |
|
|
65 | (lex.infile, lex.lineno, name, listname)) | |
|
71 | self.ui.warn( | |
|
72 | _('%s:%d: %r already in %s list\n') | |
|
73 | % (lex.infile, lex.lineno, name, listname) | |
|
74 | ) | |
|
66 | 75 | return 1 |
|
67 |
if |
|
|
68 | name.endswith('/') or | |
|
69 | '//' in name): | |
|
70 | self.ui.warn(_('%s:%d: superfluous / in %s %r\n') % | |
|
71 | (lex.infile, lex.lineno, listname, | |
|
72 | pycompat.bytestr(name))) | |
|
76 | if name.startswith('/') or name.endswith('/') or '//' in name: | |
|
77 | self.ui.warn( | |
|
78 | _('%s:%d: superfluous / in %s %r\n') | |
|
79 | % (lex.infile, lex.lineno, listname, pycompat.bytestr(name)) | |
|
80 | ) | |
|
73 | 81 | return 1 |
|
74 | 82 | return 0 |
|
83 | ||
|
75 | 84 | lex = common.shlexer( |
|
76 |
filepath=path, wordchars='!@#$%^&*()-=+[]{}|;:,./<>?' |
|
|
85 | filepath=path, wordchars='!@#$%^&*()-=+[]{}|;:,./<>?' | |
|
86 | ) | |
|
77 | 87 | cmd = lex.get_token() |
|
78 | 88 | while cmd: |
|
79 | 89 | if cmd == 'include': |
@@ -93,8 +103,10 b' class filemapper(object):' | |||
|
93 | 103 | elif cmd == 'source': |
|
94 | 104 | errs += self.parse(normalize(lex.get_token())) |
|
95 | 105 | else: |
|
96 |
self.ui.warn( |
|
|
97 | (lex.infile, lex.lineno, pycompat.bytestr(cmd))) | |
|
106 | self.ui.warn( | |
|
107 | _('%s:%d: unknown directive %r\n') | |
|
108 | % (lex.infile, lex.lineno, pycompat.bytestr(cmd)) | |
|
109 | ) | |
|
98 | 110 | errs += 1 |
|
99 | 111 | cmd = lex.get_token() |
|
100 | 112 | return errs |
@@ -157,6 +169,7 b' class filemapper(object):' | |||
|
157 | 169 | def active(self): |
|
158 | 170 | return bool(self.include or self.exclude or self.rename) |
|
159 | 171 | |
|
172 | ||
|
160 | 173 | # This class does two additional things compared to a regular source: |
|
161 | 174 | # |
|
162 | 175 | # - Filter and rename files. This is mostly wrapped by the filemapper |
@@ -171,6 +184,7 b' class filemapper(object):' | |||
|
171 | 184 | # touch files we're interested in, but also merges that merge two |
|
172 | 185 | # or more interesting revisions. |
|
173 | 186 | |
|
187 | ||
|
174 | 188 | class filemap_source(common.converter_source): |
|
175 | 189 | def __init__(self, ui, baseconverter, filemap): |
|
176 | 190 | super(filemap_source, self).__init__(ui, baseconverter.repotype) |
@@ -189,8 +203,9 b' class filemap_source(common.converter_so' | |||
|
189 | 203 | self.children = {} |
|
190 | 204 | self.seenchildren = {} |
|
191 | 205 | # experimental config: convert.ignoreancestorcheck |
|
192 |
self.ignoreancestorcheck = self.ui.configbool( |
|
|
193 | 'ignoreancestorcheck') | |
|
206 | self.ignoreancestorcheck = self.ui.configbool( | |
|
207 | 'convert', 'ignoreancestorcheck' | |
|
208 | ) | |
|
194 | 209 | |
|
195 | 210 | def before(self): |
|
196 | 211 | self.base.before() |
@@ -348,8 +363,7 b' class filemap_source(common.converter_so' | |||
|
348 | 363 | if p in self.wantedancestors: |
|
349 | 364 | wrev.update(self.wantedancestors[p]) |
|
350 | 365 | else: |
|
351 | self.ui.warn(_('warning: %s parent %s is missing\n') % | |
|
352 | (rev, p)) | |
|
366 | self.ui.warn(_('warning: %s parent %s is missing\n') % (rev, p)) | |
|
353 | 367 | wrev.add(rev) |
|
354 | 368 | self.wantedancestors[rev] = wrev |
|
355 | 369 | |
@@ -382,10 +396,13 b' class filemap_source(common.converter_so' | |||
|
382 | 396 | if mp1 == SKIPREV or mp1 in knownparents: |
|
383 | 397 | continue |
|
384 | 398 | |
|
385 |
isancestor = |
|
|
386 | any(p2 for p2 in parents | |
|
387 | if p1 != p2 and mp1 != self.parentmap[p2] | |
|
388 | and mp1 in self.wantedancestors[p2])) | |
|
399 | isancestor = not self.ignoreancestorcheck and any( | |
|
400 | p2 | |
|
401 | for p2 in parents | |
|
402 | if p1 != p2 | |
|
403 | and mp1 != self.parentmap[p2] | |
|
404 | and mp1 in self.wantedancestors[p2] | |
|
405 | ) | |
|
389 | 406 | if not isancestor and not hasbranchparent and len(parents) > 1: |
|
390 | 407 | # This could be expensive, avoid unnecessary calls. |
|
391 | 408 | if self._cachedcommit(p1).branch == branch: |
@@ -16,9 +16,8 b' from mercurial import (' | |||
|
16 | 16 | pycompat, |
|
17 | 17 | ) |
|
18 | 18 | |
|
19 |
from . import |
|
|
20 | common, | |
|
21 | ) | |
|
19 | from . import common | |
|
20 | ||
|
22 | 21 | |
|
23 | 22 | class submodule(object): |
|
24 | 23 | def __init__(self, path, node, url): |
@@ -32,6 +31,7 b' class submodule(object):' | |||
|
32 | 31 | def hgsubstate(self): |
|
33 | 32 | return "%s %s" % (self.node, self.path) |
|
34 | 33 | |
|
34 | ||
|
35 | 35 | # Keys in extra fields that should not be copied if the user requests. |
|
36 | 36 | bannedextrakeys = { |
|
37 | 37 | # Git commit object built-ins. |
@@ -44,6 +44,7 b' bannedextrakeys = {' | |||
|
44 | 44 | 'close', |
|
45 | 45 | } |
|
46 | 46 | |
|
47 | ||
|
47 | 48 | class convert_git(common.converter_source, common.commandline): |
|
48 | 49 | # Windows does not support GIT_DIR= construct while other systems |
|
49 | 50 | # cannot remove environment variable. Just assume none have |
@@ -78,8 +79,9 b' class convert_git(common.converter_sourc' | |||
|
78 | 79 | if os.path.isdir(path + "/.git"): |
|
79 | 80 | path += "/.git" |
|
80 | 81 | if not os.path.exists(path + "/objects"): |
|
81 | raise common.NoRepo(_("%s does not look like a Git repository") % | |
|
82 | path) | |
|
82 | raise common.NoRepo( | |
|
83 | _("%s does not look like a Git repository") % path | |
|
84 | ) | |
|
83 | 85 | |
|
84 | 86 | # The default value (50) is based on the default for 'git diff'. |
|
85 | 87 | similarity = ui.configint('convert', 'git.similarity') |
@@ -106,8 +108,10 b' class convert_git(common.converter_sourc' | |||
|
106 | 108 | self.copyextrakeys = self.ui.configlist('convert', 'git.extrakeys') |
|
107 | 109 | banned = set(self.copyextrakeys) & bannedextrakeys |
|
108 | 110 | if banned: |
|
109 | raise error.Abort(_('copying of extra key is forbidden: %s') % | |
|
110 | _(', ').join(sorted(banned))) | |
|
111 | raise error.Abort( | |
|
112 | _('copying of extra key is forbidden: %s') | |
|
113 | % _(', ').join(sorted(banned)) | |
|
114 | ) | |
|
111 | 115 | |
|
112 | 116 | committeractions = self.ui.configlist('convert', 'git.committeractions') |
|
113 | 117 | |
@@ -126,19 +130,31 b' class convert_git(common.converter_sourc' | |||
|
126 | 130 | messagealways = v or 'committer:' |
|
127 | 131 | |
|
128 | 132 | if messagedifferent and messagealways: |
|
129 |
raise error.Abort( |
|
|
130 | 'messagedifferent and messagealways')) | |
|
133 | raise error.Abort( | |
|
134 | _( | |
|
135 | 'committeractions cannot define both ' | |
|
136 | 'messagedifferent and messagealways' | |
|
137 | ) | |
|
138 | ) | |
|
131 | 139 | |
|
132 | 140 | dropcommitter = 'dropcommitter' in committeractions |
|
133 | 141 | replaceauthor = 'replaceauthor' in committeractions |
|
134 | 142 | |
|
135 | 143 | if dropcommitter and replaceauthor: |
|
136 |
raise error.Abort( |
|
|
137 | 'dropcommitter and replaceauthor')) | |
|
144 | raise error.Abort( | |
|
145 | _( | |
|
146 | 'committeractions cannot define both ' | |
|
147 | 'dropcommitter and replaceauthor' | |
|
148 | ) | |
|
149 | ) | |
|
138 | 150 | |
|
139 | 151 | if dropcommitter and messagealways: |
|
140 |
raise error.Abort( |
|
|
141 | 'dropcommitter and messagealways')) | |
|
152 | raise error.Abort( | |
|
153 | _( | |
|
154 | 'committeractions cannot define both ' | |
|
155 | 'dropcommitter and messagealways' | |
|
156 | ) | |
|
157 | ) | |
|
142 | 158 | |
|
143 | 159 | if not messagedifferent and not messagealways: |
|
144 | 160 | messagedifferent = 'committer:' |
@@ -176,13 +192,16 b' class convert_git(common.converter_sourc' | |||
|
176 | 192 | self.catfilepipe[0].flush() |
|
177 | 193 | info = self.catfilepipe[1].readline().split() |
|
178 | 194 | if info[1] != ftype: |
|
179 |
raise error.Abort( |
|
|
180 | pycompat.bytestr(ftype), rev)) | |
|
195 | raise error.Abort( | |
|
196 | _('cannot read %r object at %s') | |
|
197 | % (pycompat.bytestr(ftype), rev) | |
|
198 | ) | |
|
181 | 199 | size = int(info[2]) |
|
182 | 200 | data = self.catfilepipe[1].read(size) |
|
183 | 201 | if len(data) < size: |
|
184 | raise error.Abort(_('cannot read %r object at %s: unexpected size') | |
|
185 |
|
|
|
202 | raise error.Abort( | |
|
203 | _('cannot read %r object at %s: unexpected size') % (ftype, rev) | |
|
204 | ) | |
|
186 | 205 | # read the trailing newline |
|
187 | 206 | self.catfilepipe[1].read(1) |
|
188 | 207 | return data |
@@ -216,8 +235,10 b' class convert_git(common.converter_sourc' | |||
|
216 | 235 | self.submodules = [] |
|
217 | 236 | c = config.config() |
|
218 | 237 | # Each item in .gitmodules starts with whitespace that cant be parsed |
|
219 | c.parse('.gitmodules', '\n'.join(line.strip() for line in | |
|
220 | content.split('\n'))) | |
|
238 | c.parse( | |
|
239 | '.gitmodules', | |
|
240 | '\n'.join(line.strip() for line in content.split('\n')), | |
|
241 | ) | |
|
221 | 242 | for sec in c.sections(): |
|
222 | 243 | s = c[sec] |
|
223 | 244 | if 'url' in s and 'path' in s: |
@@ -228,15 +249,18 b' class convert_git(common.converter_sourc' | |||
|
228 | 249 | if ret: |
|
229 | 250 | # This can happen if a file is in the repo that has permissions |
|
230 | 251 | # 160000, but there is no .gitmodules file. |
|
231 | self.ui.warn(_("warning: cannot read submodules config file in " | |
|
232 | "%s\n") % version) | |
|
252 | self.ui.warn( | |
|
253 | _("warning: cannot read submodules config file in " "%s\n") | |
|
254 | % version | |
|
255 | ) | |
|
233 | 256 | return |
|
234 | 257 | |
|
235 | 258 | try: |
|
236 | 259 | self.parsegitmodules(modules) |
|
237 | 260 | except error.ParseError: |
|
238 | self.ui.warn(_("warning: unable to parse .gitmodules in %s\n") | |
|
239 | % version) | |
|
261 | self.ui.warn( | |
|
262 | _("warning: unable to parse .gitmodules in %s\n") % version | |
|
263 | ) | |
|
240 | 264 | return |
|
241 | 265 | |
|
242 | 266 | for m in self.submodules: |
@@ -249,7 +273,9 b' class convert_git(common.converter_sourc' | |||
|
249 | 273 | if full: |
|
250 | 274 | raise error.Abort(_("convert from git does not support --full")) |
|
251 | 275 | self.modecache = {} |
|
252 | cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version] | |
|
276 | cmd = ( | |
|
277 | ['diff-tree', '-z', '--root', '-m', '-r'] + self.simopt + [version] | |
|
278 | ) | |
|
253 | 279 | output, status = self.gitrun(*cmd) |
|
254 | 280 | if status: |
|
255 | 281 | raise error.Abort(_('cannot read changes in %s') % version) |
@@ -264,12 +290,13 b' class convert_git(common.converter_sourc' | |||
|
264 | 290 | i = 0 |
|
265 | 291 | |
|
266 | 292 | skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules') |
|
293 | ||
|
267 | 294 | def add(entry, f, isdest): |
|
268 | 295 | seen.add(f) |
|
269 | 296 | h = entry[3] |
|
270 |
p = |
|
|
271 |
s = |
|
|
272 |
renamesource = |
|
|
297 | p = entry[1] == "100755" | |
|
298 | s = entry[1] == "120000" | |
|
299 | renamesource = not isdest and entry[4][0] == 'R' | |
|
273 | 300 | |
|
274 | 301 | if f == '.gitmodules': |
|
275 | 302 | if skipsubmodules: |
@@ -377,18 +404,23 b' class convert_git(common.converter_sourc' | |||
|
377 | 404 | date = tm + " " + (b"%d" % tz) |
|
378 | 405 | saverev = self.ui.configbool('convert', 'git.saverev') |
|
379 | 406 | |
|
380 | c = common.commit(parents=parents, date=date, author=author, | |
|
407 | c = common.commit( | |
|
408 | parents=parents, | |
|
409 | date=date, | |
|
410 | author=author, | |
|
381 | 411 |
|
|
382 | 412 |
|
|
383 | 413 |
|
|
384 |
|
|
|
414 | saverev=saverev, | |
|
415 | ) | |
|
385 | 416 | return c |
|
386 | 417 | |
|
387 | 418 | def numcommits(self): |
|
388 | 419 | output, ret = self.gitrunlines('rev-list', '--all') |
|
389 | 420 | if ret: |
|
390 | raise error.Abort(_('cannot retrieve number of commits in %s') | |
|
391 | % self.path) | |
|
421 | raise error.Abort( | |
|
422 | _('cannot retrieve number of commits in %s') % self.path | |
|
423 | ) | |
|
392 | 424 | return len(output) |
|
393 | 425 | |
|
394 | 426 | def gettags(self): |
@@ -425,8 +457,9 b' class convert_git(common.converter_sourc' | |||
|
425 | 457 | def getchangedfiles(self, version, i): |
|
426 | 458 | changes = [] |
|
427 | 459 | if i is None: |
|
428 |
output, status = self.gitrunlines( |
|
|
429 | '-r', version) | |
|
460 | output, status = self.gitrunlines( | |
|
461 | 'diff-tree', '--root', '-m', '-r', version | |
|
462 | ) | |
|
430 | 463 | if status: |
|
431 | 464 | raise error.Abort(_('cannot read changes in %s') % version) |
|
432 | 465 | for l in output: |
@@ -435,9 +468,15 b' class convert_git(common.converter_sourc' | |||
|
435 | 468 | m, f = l[:-1].split("\t") |
|
436 | 469 | changes.append(f) |
|
437 | 470 | else: |
|
438 |
output, status = self.gitrunlines( |
|
|
439 | '--root', '-r', version, | |
|
440 | '%s^%d' % (version, i + 1), '--') | |
|
471 | output, status = self.gitrunlines( | |
|
472 | 'diff-tree', | |
|
473 | '--name-only', | |
|
474 | '--root', | |
|
475 | '-r', | |
|
476 | version, | |
|
477 | '%s^%d' % (version, i + 1), | |
|
478 | '--', | |
|
479 | ) | |
|
441 | 480 | if status: |
|
442 | 481 | raise error.Abort(_('cannot read changes in %s') % version) |
|
443 | 482 | changes = [f.rstrip('\n') for f in output] |
@@ -452,7 +491,7 b' class convert_git(common.converter_sourc' | |||
|
452 | 491 | reftypes = [ |
|
453 | 492 | # (git prefix, hg prefix) |
|
454 | 493 | ('refs/remotes/origin/', remoteprefix + '/'), |
|
455 | ('refs/heads/', '') | |
|
494 | ('refs/heads/', ''), | |
|
456 | 495 | ] |
|
457 | 496 | |
|
458 | 497 | exclude = { |
@@ -26,8 +26,8 b' from mercurial.utils import (' | |||
|
26 | 26 | ) |
|
27 | 27 | from . import common |
|
28 | 28 | |
|
29 | ||
|
29 | 30 | class gnuarch_source(common.converter_source, common.commandline): |
|
30 | ||
|
31 | 31 | class gnuarch_rev(object): |
|
32 | 32 | def __init__(self, rev): |
|
33 | 33 | self.rev = rev |
@@ -45,8 +45,9 b' class gnuarch_source(common.converter_so' | |||
|
45 | 45 | super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs) |
|
46 | 46 | |
|
47 | 47 | if not os.path.exists(os.path.join(path, '{arch}')): |
|
48 | raise common.NoRepo(_("%s does not look like a GNU Arch repository") | |
|
49 |
% path |
|
|
48 | raise common.NoRepo( | |
|
49 | _("%s does not look like a GNU Arch repository") % path | |
|
50 | ) | |
|
50 | 51 | |
|
51 | 52 | # Could use checktool, but we want to check for baz or tla. |
|
52 | 53 | self.execmd = None |
@@ -74,8 +75,9 b' class gnuarch_source(common.converter_so' | |||
|
74 | 75 | |
|
75 | 76 | def before(self): |
|
76 | 77 | # Get registered archives |
|
77 |
self.archives = [ |
|
|
78 |
|
|
|
78 | self.archives = [ | |
|
79 | i.rstrip('\n') for i in self.runlines0('archives', '-n') | |
|
80 | ] | |
|
79 | 81 | |
|
80 | 82 | if self.execmd == 'tla': |
|
81 | 83 | output = self.run0('tree-version', self.path) |
@@ -85,8 +87,9 b' class gnuarch_source(common.converter_so' | |||
|
85 | 87 | |
|
86 | 88 | # Get name of temporary directory |
|
87 | 89 | version = self.treeversion.split('/') |
|
88 |
self.tmppath = os.path.join( |
|
|
89 | 'hg-%s' % version[1]) | |
|
90 | self.tmppath = os.path.join( | |
|
91 | pycompat.fsencode(tempfile.gettempdir()), 'hg-%s' % version[1] | |
|
92 | ) | |
|
90 | 93 | |
|
91 | 94 | # Generate parents dictionary |
|
92 | 95 | self.parents[None] = [] |
@@ -97,14 +100,20 b' class gnuarch_source(common.converter_so' | |||
|
97 | 100 | |
|
98 | 101 | archive = treeversion.split('/')[0] |
|
99 | 102 | if archive not in self.archives: |
|
100 | self.ui.status(_('tree analysis stopped because it points to ' | |
|
101 | 'an unregistered archive %s...\n') % archive) | |
|
103 | self.ui.status( | |
|
104 | _( | |
|
105 | 'tree analysis stopped because it points to ' | |
|
106 | 'an unregistered archive %s...\n' | |
|
107 | ) | |
|
108 | % archive | |
|
109 | ) | |
|
102 | 110 | break |
|
103 | 111 | |
|
104 | 112 | # Get the complete list of revisions for that tree version |
|
105 | 113 | output, status = self.runlines('revisions', '-r', '-f', treeversion) |
|
106 | self.checkexit(status, 'failed retrieving revisions for %s' | |
|
107 |
|
|
|
114 | self.checkexit( | |
|
115 | status, 'failed retrieving revisions for %s' % treeversion | |
|
116 | ) | |
|
108 | 117 | |
|
109 | 118 | # No new iteration unless a revision has a continuation-of header |
|
110 | 119 | treeversion = None |
@@ -132,7 +141,8 b' class gnuarch_source(common.converter_so' | |||
|
132 | 141 | # by the continuation-of header. |
|
133 | 142 | if self.changes[rev].continuationof: |
|
134 | 143 | treeversion = '--'.join( |
|
135 |
self.changes[rev].continuationof.split('--')[:-1] |
|
|
144 | self.changes[rev].continuationof.split('--')[:-1] | |
|
145 | ) | |
|
136 | 146 | break |
|
137 | 147 | |
|
138 | 148 | # If we reached a base-0 revision w/o any continuation-of |
@@ -189,9 +199,13 b' class gnuarch_source(common.converter_so' | |||
|
189 | 199 | |
|
190 | 200 | def getcommit(self, rev): |
|
191 | 201 | changes = self.changes[rev] |
|
192 | return common.commit(author=changes.author, date=changes.date, | |
|
193 | desc=changes.summary, parents=self.parents[rev], | |
|
194 | rev=rev) | |
|
202 | return common.commit( | |
|
203 | author=changes.author, | |
|
204 | date=changes.date, | |
|
205 | desc=changes.summary, | |
|
206 | parents=self.parents[rev], | |
|
207 | rev=rev, | |
|
208 | ) | |
|
195 | 209 | |
|
196 | 210 | def gettags(self): |
|
197 | 211 | return self.tags |
@@ -207,8 +221,7 b' class gnuarch_source(common.converter_so' | |||
|
207 | 221 | |
|
208 | 222 | def _update(self, rev): |
|
209 | 223 | self.ui.debug('applying revision %s...\n' % rev) |
|
210 | changeset, status = self.runlines('replay', '-d', self.tmppath, | |
|
211 | rev) | |
|
224 | changeset, status = self.runlines('replay', '-d', self.tmppath, rev) | |
|
212 | 225 | if status: |
|
213 | 226 | # Something went wrong while merging (baz or tla |
|
214 | 227 | # issue?), get latest revision and try from there |
@@ -216,8 +229,9 b' class gnuarch_source(common.converter_so' | |||
|
216 | 229 | self._obtainrevision(rev) |
|
217 | 230 | else: |
|
218 | 231 | old_rev = self.parents[rev][0] |
|
219 | self.ui.debug('computing changeset between %s and %s...\n' | |
|
220 |
|
|
|
232 | self.ui.debug( | |
|
233 | 'computing changeset between %s and %s...\n' % (old_rev, rev) | |
|
234 | ) | |
|
221 | 235 | self._parsechangeset(changeset, rev) |
|
222 | 236 | |
|
223 | 237 | def _getfile(self, name, rev): |
@@ -286,21 +300,23 b' class gnuarch_source(common.converter_so' | |||
|
286 | 300 | |
|
287 | 301 | # Commit date |
|
288 | 302 | self.changes[rev].date = dateutil.datestr( |
|
289 | dateutil.strdate(catlog['Standard-date'], | |
|
290 | '%Y-%m-%d %H:%M:%S')) | |
|
303 | dateutil.strdate(catlog['Standard-date'], '%Y-%m-%d %H:%M:%S') | |
|
304 | ) | |
|
291 | 305 | |
|
292 | 306 | # Commit author |
|
293 | 307 | self.changes[rev].author = self.recode(catlog['Creator']) |
|
294 | 308 | |
|
295 | 309 | # Commit description |
|
296 |
self.changes[rev].summary = '\n\n'.join( |
|
|
297 | catlog.get_payload())) | |
|
310 | self.changes[rev].summary = '\n\n'.join( | |
|
311 | (catlog['Summary'], catlog.get_payload()) | |
|
312 | ) | |
|
298 | 313 | self.changes[rev].summary = self.recode(self.changes[rev].summary) |
|
299 | 314 | |
|
300 | 315 | # Commit revision origin when dealing with a branch or tag |
|
301 | 316 | if 'Continuation-of' in catlog: |
|
302 | 317 | self.changes[rev].continuationof = self.recode( |
|
303 |
catlog['Continuation-of'] |
|
|
318 | catlog['Continuation-of'] | |
|
319 | ) | |
|
304 | 320 | except Exception: |
|
305 | 321 | raise error.Abort(_('could not parse cat-log of %s') % rev) |
|
306 | 322 |
@@ -37,14 +37,17 b' from mercurial import (' | |||
|
37 | 37 | util, |
|
38 | 38 | ) |
|
39 | 39 | from mercurial.utils import dateutil |
|
40 | ||
|
40 | 41 | stringio = util.stringio |
|
41 | 42 | |
|
42 | 43 | from . import common |
|
44 | ||
|
43 | 45 | mapfile = common.mapfile |
|
44 | 46 | NoRepo = common.NoRepo |
|
45 | 47 | |
|
46 | 48 | sha1re = re.compile(br'\b[0-9a-f]{12,40}\b') |
|
47 | 49 | |
|
50 | ||
|
48 | 51 | class mercurial_sink(common.converter_sink): |
|
49 | 52 | def __init__(self, ui, repotype, path): |
|
50 | 53 | common.converter_sink.__init__(self, ui, repotype, path) |
@@ -56,8 +59,9 b' class mercurial_sink(common.converter_si' | |||
|
56 | 59 | try: |
|
57 | 60 | self.repo = hg.repository(self.ui, path) |
|
58 | 61 | if not self.repo.local(): |
|
59 |
raise NoRepo( |
|
|
60 |
|
|
|
62 | raise NoRepo( | |
|
63 | _('%s is not a local Mercurial repository') % path | |
|
64 | ) | |
|
61 | 65 | except error.RepoError as err: |
|
62 | 66 | ui.traceback() |
|
63 | 67 | raise NoRepo(err.args[0]) |
@@ -66,13 +70,15 b' class mercurial_sink(common.converter_si' | |||
|
66 | 70 | ui.status(_('initializing destination %s repository\n') % path) |
|
67 | 71 | self.repo = hg.repository(self.ui, path, create=True) |
|
68 | 72 | if not self.repo.local(): |
|
69 |
raise NoRepo( |
|
|
70 |
|
|
|
73 | raise NoRepo( | |
|
74 | _('%s is not a local Mercurial repository') % path | |
|
75 | ) | |
|
71 | 76 | self.created.append(path) |
|
72 | 77 | except error.RepoError: |
|
73 | 78 | ui.traceback() |
|
74 | raise NoRepo(_("could not create hg repository %s as sink") | |
|
75 |
|
|
|
79 | raise NoRepo( | |
|
80 | _("could not create hg repository %s as sink") % path | |
|
81 | ) | |
|
76 | 82 | self.lock = None |
|
77 | 83 | self.wlock = None |
|
78 | 84 | self.filemapmode = False |
@@ -100,7 +106,7 b' class mercurial_sink(common.converter_si' | |||
|
100 | 106 | if not self.clonebranches: |
|
101 | 107 | return |
|
102 | 108 | |
|
103 |
setbranch = |
|
|
109 | setbranch = branch != self.lastbranch | |
|
104 | 110 | self.lastbranch = branch |
|
105 | 111 | if not branch: |
|
106 | 112 | branch = 'default' |
@@ -130,8 +136,9 b' class mercurial_sink(common.converter_si' | |||
|
130 | 136 | pbranchpath = os.path.join(self.path, pbranch) |
|
131 | 137 | prepo = hg.peer(self.ui, {}, pbranchpath) |
|
132 | 138 | self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch)) |
|
133 |
exchange.pull( |
|
|
134 |
|
|
|
139 | exchange.pull( | |
|
140 | self.repo, prepo, [prepo.lookup(h) for h in heads] | |
|
141 | ) | |
|
135 | 142 | self.before() |
|
136 | 143 | |
|
137 | 144 | def _rewritetags(self, source, revmap, data): |
@@ -166,8 +173,9 b' class mercurial_sink(common.converter_si' | |||
|
166 | 173 | if revid != nodemod.nullhex: |
|
167 | 174 | revmap = self.subrevmaps.get(subpath) |
|
168 | 175 | if revmap is None: |
|
169 |
revmap = mapfile( |
|
|
170 |
|
|
|
176 | revmap = mapfile( | |
|
177 | self.ui, self.repo.wjoin(subpath, '.hg/shamap') | |
|
178 | ) | |
|
171 | 179 | self.subrevmaps[subpath] = revmap |
|
172 | 180 | |
|
173 | 181 | # It is reasonable that one or more of the subrepos don't |
@@ -184,8 +192,10 b' class mercurial_sink(common.converter_si' | |||
|
184 | 192 | newid = revmap.get(revid) |
|
185 | 193 | if not newid: |
|
186 | 194 | if len(revmap) > 0: |
|
187 |
self.ui.warn( |
|
|
188 | (revid, subpath)) | |
|
195 | self.ui.warn( | |
|
196 | _("%s is missing from %s/.hg/shamap\n") | |
|
197 | % (revid, subpath) | |
|
198 | ) | |
|
189 | 199 | else: |
|
190 | 200 | revid = newid |
|
191 | 201 | |
@@ -204,8 +214,15 b' class mercurial_sink(common.converter_si' | |||
|
204 | 214 | anc = [p1ctx.ancestor(p2ctx)] |
|
205 | 215 | # Calculate what files are coming from p2 |
|
206 | 216 | actions, diverge, rename = mergemod.calculateupdates( |
|
207 | self.repo, p1ctx, p2ctx, anc, branchmerge=True, | |
|
208 | force=True, acceptremote=False, followcopies=False) | |
|
217 | self.repo, | |
|
218 | p1ctx, | |
|
219 | p2ctx, | |
|
220 | anc, | |
|
221 | branchmerge=True, | |
|
222 | force=True, | |
|
223 | acceptremote=False, | |
|
224 | followcopies=False, | |
|
225 | ) | |
|
209 | 226 | |
|
210 | 227 | for file, (action, info, msg) in actions.iteritems(): |
|
211 | 228 | if source.targetfilebelongstosource(file): |
@@ -216,10 +233,14 b' class mercurial_sink(common.converter_si' | |||
|
216 | 233 | # If the file requires actual merging, abort. We don't have enough |
|
217 | 234 | # context to resolve merges correctly. |
|
218 | 235 | if action in ['m', 'dm', 'cd', 'dc']: |
|
219 |
raise error.Abort( |
|
|
236 | raise error.Abort( | |
|
237 | _( | |
|
238 | "unable to convert merge commit " | |
|
220 | 239 | "since target parents do not merge cleanly (file " |
|
221 |
"%s, parents %s and %s)" |
|
|
222 | p2ctx)) | |
|
240 | "%s, parents %s and %s)" | |
|
241 | ) | |
|
242 | % (file, p1ctx, p2ctx) | |
|
243 | ) | |
|
223 | 244 | elif action == 'k': |
|
224 | 245 | # 'keep' means nothing changed from p1 |
|
225 | 246 | continue |
@@ -227,8 +248,9 b' class mercurial_sink(common.converter_si' | |||
|
227 | 248 | # Any other change means we want to take the p2 version |
|
228 | 249 | yield file |
|
229 | 250 | |
|
230 | def putcommit(self, files, copies, parents, commit, source, revmap, full, | |
|
231 | cleanp2): | |
|
251 | def putcommit( | |
|
252 | self, files, copies, parents, commit, source, revmap, full, cleanp2 | |
|
253 | ): | |
|
232 | 254 | files = dict(files) |
|
233 | 255 | |
|
234 | 256 | def getfilectx(repo, memctx, f): |
@@ -251,8 +273,15 b' class mercurial_sink(common.converter_si' | |||
|
251 | 273 | data = self._rewritetags(source, revmap, data) |
|
252 | 274 | if f == '.hgsubstate': |
|
253 | 275 | data = self._rewritesubstate(source, data) |
|
254 |
return context.memfilectx( |
|
|
255 | 'x' in mode, copies.get(f)) | |
|
276 | return context.memfilectx( | |
|
277 | self.repo, | |
|
278 | memctx, | |
|
279 | f, | |
|
280 | data, | |
|
281 | 'l' in mode, | |
|
282 | 'x' in mode, | |
|
283 | copies.get(f), | |
|
284 | ) | |
|
256 | 285 | |
|
257 | 286 | pl = [] |
|
258 | 287 | for p in parents: |
@@ -285,8 +314,12 b' class mercurial_sink(common.converter_si' | |||
|
285 | 314 | if sourcename: |
|
286 | 315 | extra['convert_source'] = sourcename |
|
287 | 316 | |
|
288 | for label in ('source', 'transplant_source', 'rebase_source', | |
|
289 |
|
|
|
317 | for label in ( | |
|
318 | 'source', | |
|
319 | 'transplant_source', | |
|
320 | 'rebase_source', | |
|
321 | 'intermediate-source', | |
|
322 | ): | |
|
290 | 323 | node = extra.get(label) |
|
291 | 324 | |
|
292 | 325 | if node is None: |
@@ -326,13 +359,25 b' class mercurial_sink(common.converter_si' | |||
|
326 | 359 | p2files.add(file) |
|
327 | 360 | fileset.add(file) |
|
328 | 361 | |
|
329 |
ctx = context.memctx( |
|
|
330 | getfilectx, commit.author, commit.date, extra) | |
|
362 | ctx = context.memctx( | |
|
363 | self.repo, | |
|
364 | (p1, p2), | |
|
365 | text, | |
|
366 | fileset, | |
|
367 | getfilectx, | |
|
368 | commit.author, | |
|
369 | commit.date, | |
|
370 | extra, | |
|
371 | ) | |
|
331 | 372 | |
|
332 | 373 | # We won't know if the conversion changes the node until after the |
|
333 | 374 | # commit, so copy the source's phase for now. |
|
334 |
self.repo.ui.setconfig( |
|
|
335 | phases.phasenames[commit.phase], 'convert') | |
|
375 | self.repo.ui.setconfig( | |
|
376 | 'phases', | |
|
377 | 'new-commit', | |
|
378 | phases.phasenames[commit.phase], | |
|
379 | 'convert', | |
|
380 | ) | |
|
336 | 381 | |
|
337 | 382 | with self.repo.transaction("convert") as tr: |
|
338 | 383 | if self.repo.ui.config('convert', 'hg.preserve-hash'): |
@@ -347,8 +392,9 b' class mercurial_sink(common.converter_si' | |||
|
347 | 392 | if commit.rev != node: |
|
348 | 393 | ctx = self.repo[node] |
|
349 | 394 | if ctx.phase() < phases.draft: |
|
350 |
phases.registernew( |
|
|
351 |
|
|
|
395 | phases.registernew( | |
|
396 | self.repo, tr, phases.draft, [ctx.node()] | |
|
397 | ) | |
|
352 | 398 | |
|
353 | 399 | text = "(octopus merge fixup)\n" |
|
354 | 400 | p2 = node |
@@ -372,7 +418,8 b' class mercurial_sink(common.converter_si' | |||
|
372 | 418 | for h in heads: |
|
373 | 419 | if '.hgtags' in self.repo[h]: |
|
374 | 420 | oldlines.update( |
|
375 |
set(self.repo[h]['.hgtags'].data().splitlines(True)) |
|
|
421 | set(self.repo[h]['.hgtags'].data().splitlines(True)) | |
|
422 | ) | |
|
376 | 423 | oldlines = sorted(list(oldlines)) |
|
377 | 424 | |
|
378 | 425 | newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags]) |
@@ -398,15 +445,23 b' class mercurial_sink(common.converter_si' | |||
|
398 | 445 | return None, None |
|
399 | 446 | |
|
400 | 447 | data = "".join(newlines) |
|
448 | ||
|
401 | 449 | def getfilectx(repo, memctx, f): |
|
402 | 450 | return context.memfilectx(repo, memctx, f, data, False, False, None) |
|
403 | 451 | |
|
404 | 452 | self.ui.status(_("updating tags\n")) |
|
405 | 453 | date = "%d 0" % int(time.mktime(time.gmtime())) |
|
406 | 454 | extra = {'branch': self.tagsbranch} |
|
407 | ctx = context.memctx(self.repo, (tagparent, None), "update tags", | |
|
408 | [".hgtags"], getfilectx, "convert-repo", date, | |
|
409 | extra) | |
|
455 | ctx = context.memctx( | |
|
456 | self.repo, | |
|
457 | (tagparent, None), | |
|
458 | "update tags", | |
|
459 | [".hgtags"], | |
|
460 | getfilectx, | |
|
461 | "convert-repo", | |
|
462 | date, | |
|
463 | extra, | |
|
464 | ) | |
|
410 | 465 | node = self.repo.commitctx(ctx) |
|
411 | 466 | return nodemod.hex(node), nodemod.hex(tagparent) |
|
412 | 467 | |
@@ -423,8 +478,10 b' class mercurial_sink(common.converter_si' | |||
|
423 | 478 | tr = self.repo.transaction('bookmark') |
|
424 | 479 | self.ui.status(_("updating bookmarks\n")) |
|
425 | 480 | destmarks = self.repo._bookmarks |
|
426 | changes = [(bookmark, nodemod.bin(updatedbookmark[bookmark])) | |
|
427 |
|
|
|
481 | changes = [ | |
|
482 | (bookmark, nodemod.bin(updatedbookmark[bookmark])) | |
|
483 | for bookmark in updatedbookmark | |
|
484 | ] | |
|
428 | 485 | destmarks.applychanges(self.repo, tr, changes) |
|
429 | 486 | tr.close() |
|
430 | 487 | finally: |
@@ -436,11 +493,17 b' class mercurial_sink(common.converter_si' | |||
|
436 | 493 | |
|
437 | 494 | def hascommitforsplicemap(self, rev): |
|
438 | 495 | if rev not in self.repo and self.clonebranches: |
|
439 | raise error.Abort(_('revision %s not found in destination ' | |
|
496 | raise error.Abort( | |
|
497 | _( | |
|
498 | 'revision %s not found in destination ' | |
|
440 | 499 |
|
|
441 |
|
|
|
500 | 'are not implemented)' | |
|
501 | ) | |
|
502 | % rev | |
|
503 | ) | |
|
442 | 504 | return rev in self.repo |
|
443 | 505 | |
|
506 | ||
|
444 | 507 | class mercurial_source(common.converter_source): |
|
445 | 508 | def __init__(self, ui, repotype, path, revs=None): |
|
446 | 509 | common.converter_source.__init__(self, ui, repotype, path, revs) |
@@ -468,8 +531,9 b' class mercurial_source(common.converter_' | |||
|
468 | 531 | try: |
|
469 | 532 | startnode = self.repo.lookup(startnode) |
|
470 | 533 | except error.RepoError: |
|
471 |
raise error.Abort( |
|
|
472 |
|
|
|
534 | raise error.Abort( | |
|
535 | _('%s is not a valid start revision') % startnode | |
|
536 | ) | |
|
473 | 537 | startrev = self.repo.changelog.rev(startnode) |
|
474 | 538 | children = {startnode: 1} |
|
475 | 539 | for r in self.repo.changelog.descendants([startrev]): |
@@ -483,8 +547,9 b' class mercurial_source(common.converter_' | |||
|
483 | 547 | self._heads = self.repo.heads() |
|
484 | 548 | else: |
|
485 | 549 | if revs or startnode is not None: |
|
486 |
raise error.Abort( |
|
|
487 |
|
|
|
550 | raise error.Abort( | |
|
551 | _('hg.revs cannot be combined with ' 'hg.startrev or --rev') | |
|
552 | ) | |
|
488 | 553 | nodes = set() |
|
489 | 554 | parents = set() |
|
490 | 555 | for r in scmutil.revrange(self.repo, [hgrevs]): |
@@ -580,9 +645,9 b' class mercurial_source(common.converter_' | |||
|
580 | 645 | optparents = [p.hex() for p in ctx.parents() if p and p not in _parents] |
|
581 | 646 | crev = rev |
|
582 | 647 | |
|
583 |
return common.commit( |
|
|
584 | date=dateutil.datestr(ctx.date(), | |
|
585 | '%Y-%m-%d %H:%M:%S %1%2'), | |
|
648 | return common.commit( | |
|
649 | author=ctx.user(), | |
|
650 | date=dateutil.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'), | |
|
586 | 651 |
|
|
587 | 652 |
|
|
588 | 653 |
|
@@ -592,17 +657,26 b' class mercurial_source(common.converter_' | |||
|
592 | 657 |
|
|
593 | 658 |
|
|
594 | 659 |
|
|
595 |
|
|
|
660 | ctx=ctx, | |
|
661 | ) | |
|
596 | 662 | |
|
597 | 663 | def numcommits(self): |
|
598 | 664 | return len(self.repo) |
|
599 | 665 | |
|
600 | 666 | def gettags(self): |
|
601 | 667 | # This will get written to .hgtags, filter non global tags out. |
|
602 | tags = [t for t in self.repo.tagslist() | |
|
603 | if self.repo.tagtype(t[0]) == 'global'] | |
|
604 | return dict([(name, nodemod.hex(node)) for name, node in tags | |
|
605 | if self.keep(node)]) | |
|
668 | tags = [ | |
|
669 | t | |
|
670 | for t in self.repo.tagslist() | |
|
671 | if self.repo.tagtype(t[0]) == 'global' | |
|
672 | ] | |
|
673 | return dict( | |
|
674 | [ | |
|
675 | (name, nodemod.hex(node)) | |
|
676 | for name, node in tags | |
|
677 | if self.keep(node) | |
|
678 | ] | |
|
679 | ) | |
|
606 | 680 | |
|
607 | 681 | def getchangedfiles(self, rev, i): |
|
608 | 682 | ctx = self._changectx(rev) |
@@ -19,12 +19,17 b' from mercurial.utils import dateutil' | |||
|
19 | 19 | |
|
20 | 20 | from . import common |
|
21 | 21 | |
|
22 | ||
|
22 | 23 | class monotone_source(common.converter_source, common.commandline): |
|
23 | 24 | def __init__(self, ui, repotype, path=None, revs=None): |
|
24 | 25 | common.converter_source.__init__(self, ui, repotype, path, revs) |
|
25 | 26 | if revs and len(revs) > 1: |
|
26 | raise error.Abort(_('monotone source does not support specifying ' | |
|
27 | 'multiple revs')) | |
|
27 | raise error.Abort( | |
|
28 | _( | |
|
29 | 'monotone source does not support specifying ' | |
|
30 | 'multiple revs' | |
|
31 | ) | |
|
32 | ) | |
|
28 | 33 | common.commandline.__init__(self, ui, 'mtn') |
|
29 | 34 | |
|
30 | 35 | self.ui = ui |
@@ -32,8 +37,9 b' class monotone_source(common.converter_s' | |||
|
32 | 37 | self.automatestdio = False |
|
33 | 38 | self.revs = revs |
|
34 | 39 | |
|
35 | norepo = common.NoRepo(_("%s does not look like a monotone repository") | |
|
36 | % path) | |
|
40 | norepo = common.NoRepo( | |
|
41 | _("%s does not look like a monotone repository") % path | |
|
42 | ) | |
|
37 | 43 | if not os.path.exists(os.path.join(path, '_MTN')): |
|
38 | 44 | # Could be a monotone repository (SQLite db file) |
|
39 | 45 | try: |
@@ -53,22 +59,24 b' class monotone_source(common.converter_s' | |||
|
53 | 59 |
lines |
|
54 | 60 | |
|
55 | 61 |
self.dir_re |
|
56 |
self.file_re |
|
|
57 | "content" + revision) | |
|
58 | self.add_file_re = re.compile(space + "add_file" + name + | |
|
59 | "content" + revision) | |
|
60 |
self.patch_re |
|
|
61 |
|
|
|
62 | self.file_re = re.compile(space + "file" + name + "content" + revision) | |
|
63 | self.add_file_re = re.compile( | |
|
64 | space + "add_file" + name + "content" + revision | |
|
65 | ) | |
|
66 | self.patch_re = re.compile( | |
|
67 | space + "patch" + name + "from" + revision + "to" + revision | |
|
68 | ) | |
|
62 | 69 |
self.rename_re |
|
63 | 70 |
self.delete_re |
|
64 |
self.tag_re |
|
|
65 | revision) | |
|
66 |
|
|
|
67 | "value" + value) | |
|
71 | self.tag_re = re.compile(space + "tag" + name + "revision" + revision) | |
|
72 | self.cert_re = re.compile( | |
|
73 | lines + space + "name" + name + "value" + value | |
|
74 | ) | |
|
68 | 75 | |
|
69 | 76 | attr = space + "file" + lines + space + "attr" + space |
|
70 |
self.attr_execute_re = re.compile( |
|
|
71 | space + '"true"') | |
|
77 | self.attr_execute_re = re.compile( | |
|
78 | attr + '"mtn:execute"' + space + '"true"' | |
|
79 | ) | |
|
72 | 80 | |
|
73 | 81 | # cached data |
|
74 | 82 | self.manifest_rev = None |
@@ -140,13 +148,19 b' class monotone_source(common.converter_s' | |||
|
140 | 148 | try: |
|
141 | 149 | length = pycompat.long(lengthstr[:-1]) |
|
142 | 150 | except TypeError: |
|
143 |
raise error.Abort( |
|
|
144 | % lengthstr) | |
|
151 | raise error.Abort( | |
|
152 | _('bad mtn packet - bad packet size %s') % lengthstr | |
|
153 | ) | |
|
145 | 154 | |
|
146 | 155 | read = self.mtnreadfp.read(length) |
|
147 | 156 | if len(read) != length: |
|
148 | raise error.Abort(_("bad mtn packet - unable to read full packet " | |
|
149 | "read %s of %s") % (len(read), length)) | |
|
157 | raise error.Abort( | |
|
158 | _( | |
|
159 | "bad mtn packet - unable to read full packet " | |
|
160 | "read %s of %s" | |
|
161 | ) | |
|
162 | % (len(read), length) | |
|
163 | ) | |
|
150 | 164 | |
|
151 | 165 | return (commandnbr, stream, length, read) |
|
152 | 166 | |
@@ -154,14 +168,16 b' class monotone_source(common.converter_s' | |||
|
154 | 168 | retval = [] |
|
155 | 169 | while True: |
|
156 | 170 | commandnbr, stream, length, output = self.mtnstdioreadpacket() |
|
157 |
self.ui.debug( |
|
|
158 |
(commandnbr, stream, length) |
|
|
171 | self.ui.debug( | |
|
172 | 'mtn: read packet %s:%s:%d\n' % (commandnbr, stream, length) | |
|
173 | ) | |
|
159 | 174 | |
|
160 | 175 | if stream == 'l': |
|
161 | 176 | # End of command |
|
162 | 177 | if output != '0': |
|
163 |
raise error.Abort( |
|
|
164 |
(command, output) |
|
|
178 | raise error.Abort( | |
|
179 | _("mtn command '%s' returned %s") % (command, output) | |
|
180 | ) | |
|
165 | 181 | break |
|
166 | 182 | elif stream in 'ew': |
|
167 | 183 | # Error, warning output |
@@ -207,8 +223,12 b' class monotone_source(common.converter_s' | |||
|
207 | 223 | return name in self.dirs |
|
208 | 224 | |
|
209 | 225 | def mtngetcerts(self, rev): |
|
210 | certs = {"author":"<missing>", "date":"<missing>", | |
|
211 |
" |
|
|
226 | certs = { | |
|
227 | "author": "<missing>", | |
|
228 | "date": "<missing>", | |
|
229 | "changelog": "<missing>", | |
|
230 | "branch": "<missing>", | |
|
231 | } | |
|
212 | 232 | certlist = self.mtnrun("certs", rev) |
|
213 | 233 | # mtn < 0.45: |
|
214 | 234 | # key "test@selenic.com" |
@@ -237,8 +257,9 b' class monotone_source(common.converter_s' | |||
|
237 | 257 | |
|
238 | 258 | def getchanges(self, rev, full): |
|
239 | 259 | if full: |
|
240 | raise error.Abort(_("convert from monotone does not support " | |
|
241 | "--full")) | |
|
260 | raise error.Abort( | |
|
261 | _("convert from monotone does not support " "--full") | |
|
262 | ) | |
|
242 | 263 | revision = self.mtnrun("get_revision", rev).split("\n\n") |
|
243 | 264 | files = {} |
|
244 | 265 | ignoremove = {} |
@@ -286,7 +307,9 b' class monotone_source(common.converter_s' | |||
|
286 | 307 | for tofile, fromfile in renamed.items(): |
|
287 | 308 | self.ui.debug( |
|
288 | 309 | "copying file in renamed directory from '%s' to '%s'" |
|
289 |
% (fromfile, tofile), |
|
|
310 | % (fromfile, tofile), | |
|
311 | '\n', | |
|
312 | ) | |
|
290 | 313 | files[tofile] = rev |
|
291 | 314 | copies[tofile] = fromfile |
|
292 | 315 | for fromfile in renamed.values(): |
@@ -318,7 +341,8 b' class monotone_source(common.converter_s' | |||
|
318 | 341 | rev=rev, |
|
319 | 342 | parents=self.mtnrun("parents", rev).splitlines(), |
|
320 | 343 | branch=certs["branch"], |
|
321 |
extra=extra |
|
|
344 | extra=extra, | |
|
345 | ) | |
|
322 | 346 | |
|
323 | 347 | def gettags(self): |
|
324 | 348 | tags = {} |
@@ -339,30 +363,40 b' class monotone_source(common.converter_s' | |||
|
339 | 363 | versionstr = self.mtnrunsingle("interface_version") |
|
340 | 364 | version = float(versionstr) |
|
341 | 365 | except Exception: |
|
342 | raise error.Abort(_("unable to determine mtn automate interface " | |
|
343 | "version")) | |
|
366 | raise error.Abort( | |
|
367 | _("unable to determine mtn automate interface " "version") | |
|
368 | ) | |
|
344 | 369 | |
|
345 | 370 | if version >= 12.0: |
|
346 | 371 | self.automatestdio = True |
|
347 | self.ui.debug("mtn automate version %f - using automate stdio\n" % | |
|
348 | version) | |
|
372 | self.ui.debug( | |
|
373 | "mtn automate version %f - using automate stdio\n" % version | |
|
374 | ) | |
|
349 | 375 | |
|
350 | 376 | # launch the long-running automate stdio process |
|
351 |
self.mtnwritefp, self.mtnreadfp = self._run2( |
|
|
352 |
'-d', self.path |
|
|
377 | self.mtnwritefp, self.mtnreadfp = self._run2( | |
|
378 | 'automate', 'stdio', '-d', self.path | |
|
379 | ) | |
|
353 | 380 | # read the headers |
|
354 | 381 | read = self.mtnreadfp.readline() |
|
355 | 382 | if read != 'format-version: 2\n': |
|
356 |
raise error.Abort( |
|
|
357 | % read) | |
|
383 | raise error.Abort( | |
|
384 | _('mtn automate stdio header unexpected: %s') % read | |
|
385 | ) | |
|
358 | 386 | while read != '\n': |
|
359 | 387 | read = self.mtnreadfp.readline() |
|
360 | 388 | if not read: |
|
361 |
raise error.Abort( |
|
|
362 |
|
|
|
389 | raise error.Abort( | |
|
390 | _( | |
|
391 | "failed to reach end of mtn automate " | |
|
392 | "stdio headers" | |
|
393 | ) | |
|
394 | ) | |
|
363 | 395 | else: |
|
364 | self.ui.debug("mtn automate version %s - not using automate stdio " | |
|
365 | "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version) | |
|
396 | self.ui.debug( | |
|
397 | "mtn automate version %s - not using automate stdio " | |
|
398 | "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version | |
|
399 | ) | |
|
366 | 400 | |
|
367 | 401 | def after(self): |
|
368 | 402 | if self.automatestdio: |
@@ -22,6 +22,7 b' from mercurial.utils import (' | |||
|
22 | 22 | |
|
23 | 23 | from . import common |
|
24 | 24 | |
|
25 | ||
|
25 | 26 | def loaditer(f): |
|
26 | 27 | "Yield the dictionary objects generated by p4" |
|
27 | 28 | try: |
@@ -33,6 +34,7 b' def loaditer(f):' | |||
|
33 | 34 | except EOFError: |
|
34 | 35 | pass |
|
35 | 36 | |
|
37 | ||
|
36 | 38 | def decodefilename(filename): |
|
37 | 39 | """Perforce escapes special characters @, #, *, or % |
|
38 | 40 | with %40, %23, %2A, or %25 respectively |
@@ -47,6 +49,7 b' def decodefilename(filename):' | |||
|
47 | 49 | filename = filename.replace(k, v) |
|
48 | 50 | return filename |
|
49 | 51 | |
|
52 | ||
|
50 | 53 | class p4_source(common.converter_source): |
|
51 | 54 | def __init__(self, ui, repotype, path, revs=None): |
|
52 | 55 | # avoid import cycle |
@@ -55,25 +58,30 b' class p4_source(common.converter_source)' | |||
|
55 | 58 | super(p4_source, self).__init__(ui, repotype, path, revs=revs) |
|
56 | 59 | |
|
57 | 60 | if "/" in path and not path.startswith('//'): |
|
58 | raise common.NoRepo(_('%s does not look like a P4 repository') % | |
|
59 | path) | |
|
61 | raise common.NoRepo( | |
|
62 | _('%s does not look like a P4 repository') % path | |
|
63 | ) | |
|
60 | 64 | |
|
61 | 65 | common.checktool('p4', abort=False) |
|
62 | 66 | |
|
63 | 67 | self.revmap = {} |
|
64 |
self.encoding = self.ui.config( |
|
|
65 | convcmd.orig_encoding) | |
|
68 | self.encoding = self.ui.config( | |
|
69 | 'convert', 'p4.encoding', convcmd.orig_encoding | |
|
70 | ) | |
|
66 | 71 | self.re_type = re.compile( |
|
67 | 72 | br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)" |
|
68 |
br"(\+\w+)?$" |
|
|
73 | br"(\+\w+)?$" | |
|
74 | ) | |
|
69 | 75 | self.re_keywords = re.compile( |
|
70 | 76 | br"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)" |
|
71 |
br":[^$\n]*\$" |
|
|
77 | br":[^$\n]*\$" | |
|
78 | ) | |
|
72 | 79 | self.re_keywords_old = re.compile(br"\$(Id|Header):[^$\n]*\$") |
|
73 | 80 | |
|
74 | 81 | if revs and len(revs) > 1: |
|
75 | raise error.Abort(_("p4 source does not support specifying " | |
|
76 |
|
|
|
82 | raise error.Abort( | |
|
83 | _("p4 source does not support specifying " "multiple revisions") | |
|
84 | ) | |
|
77 | 85 | |
|
78 | 86 | def setrevmap(self, revmap): |
|
79 | 87 | """Sets the parsed revmap dictionary. |
@@ -189,7 +197,7 b' class p4_source(common.converter_source)' | |||
|
189 | 197 | if filename: |
|
190 | 198 | files.append((filename, d["rev%d" % i])) |
|
191 | 199 | depotname[filename] = oldname |
|
192 |
if |
|
|
200 | if d.get("action%d" % i) == "move/add": | |
|
193 | 201 | copiedfiles.append(filename) |
|
194 | 202 | localname[oldname] = filename |
|
195 | 203 | i += 1 |
@@ -198,8 +206,7 b' class p4_source(common.converter_source)' | |||
|
198 | 206 | for filename in copiedfiles: |
|
199 | 207 | oldname = depotname[filename] |
|
200 | 208 | |
|
201 |
flcmd = |
|
|
202 | % procutil.shellquote(oldname)) | |
|
209 | flcmd = 'p4 -G filelog %s' % procutil.shellquote(oldname) | |
|
203 | 210 | flstdout = procutil.popen(flcmd, mode='rb') |
|
204 | 211 | |
|
205 | 212 | copiedfilename = None |
@@ -208,8 +215,10 b' class p4_source(common.converter_source)' | |||
|
208 | 215 | |
|
209 | 216 | i = 0 |
|
210 | 217 | while ("change%d" % i) in d: |
|
211 |
if ( |
|
|
212 |
d[" |
|
|
218 | if ( | |
|
219 | d["change%d" % i] == change | |
|
220 | and d["action%d" % i] == "move/add" | |
|
221 | ): | |
|
213 | 222 | j = 0 |
|
214 | 223 | while ("file%d,%d" % (i, j)) in d: |
|
215 | 224 | if d["how%d,%d" % (i, j)] == "moved from": |
@@ -225,8 +234,10 b' class p4_source(common.converter_source)' | |||
|
225 | 234 | if copiedfilename: |
|
226 | 235 | copies[filename] = copiedfilename |
|
227 | 236 | else: |
|
228 | ui.warn(_("cannot find source for copied file: %s@%s\n") | |
|
229 | % (filename, change)) | |
|
237 | ui.warn( | |
|
238 | _("cannot find source for copied file: %s@%s\n") | |
|
239 | % (filename, change) | |
|
240 | ) | |
|
230 | 241 | |
|
231 | 242 | changeset[change] = c |
|
232 | 243 | files_map[change] = files |
@@ -272,8 +283,9 b' class p4_source(common.converter_source)' | |||
|
272 | 283 | return self.heads |
|
273 | 284 | |
|
274 | 285 | def getfile(self, name, rev): |
|
275 |
cmd = |
|
|
276 |
|
|
|
286 | cmd = 'p4 -G print %s' % procutil.shellquote( | |
|
287 | "%s#%s" % (self.depotname[name], rev) | |
|
288 | ) | |
|
277 | 289 | |
|
278 | 290 | lasterror = None |
|
279 | 291 | while True: |
@@ -304,8 +316,9 b' class p4_source(common.converter_source)' | |||
|
304 | 316 | p4type = self.re_type.match(d["type"]) |
|
305 | 317 | if p4type: |
|
306 | 318 | mode = "" |
|
307 |
flags = |
|
|
308 |
|
|
|
319 | flags = (p4type.group(1) or "") + ( | |
|
320 | p4type.group(3) or "" | |
|
321 | ) | |
|
309 | 322 | if "x" in flags: |
|
310 | 323 | mode = "x" |
|
311 | 324 | if p4type.group(2) == "symlink": |
@@ -350,10 +363,15 b' class p4_source(common.converter_source)' | |||
|
350 | 363 | if parents is None: |
|
351 | 364 | parents = [] |
|
352 | 365 | |
|
353 |
return common.commit( |
|
|
366 | return common.commit( | |
|
367 | author=self.recode(obj["user"]), | |
|
354 | 368 | date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'), |
|
355 | parents=parents, desc=desc, branch=None, rev=obj['change'], | |
|
356 | extra={"p4": obj['change'], "convert_revision": obj['change']}) | |
|
369 | parents=parents, | |
|
370 | desc=desc, | |
|
371 | branch=None, | |
|
372 | rev=obj['change'], | |
|
373 | extra={"p4": obj['change'], "convert_revision": obj['change']}, | |
|
374 | ) | |
|
357 | 375 | |
|
358 | 376 | def _fetch_revision(self, rev): |
|
359 | 377 | """Return an output of `p4 describe` including author, commit date as |
@@ -369,7 +387,8 b' class p4_source(common.converter_source)' | |||
|
369 | 387 | d = self._fetch_revision(rev) |
|
370 | 388 | return self._construct_commit(d, parents=None) |
|
371 | 389 | raise error.Abort( |
|
372 |
_("cannot find %s in the revmap or parsed changesets") % rev |
|
|
390 | _("cannot find %s in the revmap or parsed changesets") % rev | |
|
391 | ) | |
|
373 | 392 | |
|
374 | 393 | def gettags(self): |
|
375 | 394 | return {} |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now