diff options
Diffstat (limited to 'manifest_xml.py')
| -rw-r--r-- | manifest_xml.py | 189 |
1 files changed, 144 insertions, 45 deletions
diff --git a/manifest_xml.py b/manifest_xml.py index 0c2b45e5..68ead53c 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. | 13 | # limitations under the License. |
| 14 | 14 | ||
| 15 | import collections | ||
| 15 | import itertools | 16 | import itertools |
| 16 | import os | 17 | import os |
| 17 | import platform | 18 | import platform |
| @@ -24,14 +25,21 @@ import gitc_utils | |||
| 24 | from git_config import GitConfig, IsId | 25 | from git_config import GitConfig, IsId |
| 25 | from git_refs import R_HEADS, HEAD | 26 | from git_refs import R_HEADS, HEAD |
| 26 | import platform_utils | 27 | import platform_utils |
| 27 | from project import RemoteSpec, Project, MetaProject | 28 | from project import Annotation, RemoteSpec, Project, MetaProject |
| 28 | from error import (ManifestParseError, ManifestInvalidPathError, | 29 | from error import (ManifestParseError, ManifestInvalidPathError, |
| 29 | ManifestInvalidRevisionError) | 30 | ManifestInvalidRevisionError) |
| 31 | from wrapper import Wrapper | ||
| 30 | 32 | ||
| 31 | MANIFEST_FILE_NAME = 'manifest.xml' | 33 | MANIFEST_FILE_NAME = 'manifest.xml' |
| 32 | LOCAL_MANIFEST_NAME = 'local_manifest.xml' | 34 | LOCAL_MANIFEST_NAME = 'local_manifest.xml' |
| 33 | LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' | 35 | LOCAL_MANIFESTS_DIR_NAME = 'local_manifests' |
| 34 | 36 | ||
| 37 | # Add all projects from local manifest into a group. | ||
| 38 | LOCAL_MANIFEST_GROUP_PREFIX = 'local:' | ||
| 39 | |||
| 40 | # ContactInfo has the self-registered bug url, supplied by the manifest authors. | ||
| 41 | ContactInfo = collections.namedtuple('ContactInfo', 'bugurl') | ||
| 42 | |||
| 35 | # urljoin gets confused if the scheme is not known. | 43 | # urljoin gets confused if the scheme is not known. |
| 36 | urllib.parse.uses_relative.extend([ | 44 | urllib.parse.uses_relative.extend([ |
| 37 | 'ssh', | 45 | 'ssh', |
| @@ -114,9 +122,13 @@ class _Default(object): | |||
| 114 | sync_tags = True | 122 | sync_tags = True |
| 115 | 123 | ||
| 116 | def __eq__(self, other): | 124 | def __eq__(self, other): |
| 125 | if not isinstance(other, _Default): | ||
| 126 | return False | ||
| 117 | return self.__dict__ == other.__dict__ | 127 | return self.__dict__ == other.__dict__ |
| 118 | 128 | ||
| 119 | def __ne__(self, other): | 129 | def __ne__(self, other): |
| 130 | if not isinstance(other, _Default): | ||
| 131 | return True | ||
| 120 | return self.__dict__ != other.__dict__ | 132 | return self.__dict__ != other.__dict__ |
| 121 | 133 | ||
| 122 | 134 | ||
| @@ -137,14 +149,22 @@ class _XmlRemote(object): | |||
| 137 | self.reviewUrl = review | 149 | self.reviewUrl = review |
| 138 | self.revision = revision | 150 | self.revision = revision |
| 139 | self.resolvedFetchUrl = self._resolveFetchUrl() | 151 | self.resolvedFetchUrl = self._resolveFetchUrl() |
| 152 | self.annotations = [] | ||
| 140 | 153 | ||
| 141 | def __eq__(self, other): | 154 | def __eq__(self, other): |
| 142 | return self.__dict__ == other.__dict__ | 155 | if not isinstance(other, _XmlRemote): |
| 156 | return False | ||
| 157 | return (sorted(self.annotations) == sorted(other.annotations) and | ||
| 158 | self.name == other.name and self.fetchUrl == other.fetchUrl and | ||
| 159 | self.pushUrl == other.pushUrl and self.remoteAlias == other.remoteAlias | ||
| 160 | and self.reviewUrl == other.reviewUrl and self.revision == other.revision) | ||
| 143 | 161 | ||
| 144 | def __ne__(self, other): | 162 | def __ne__(self, other): |
| 145 | return self.__dict__ != other.__dict__ | 163 | return not self.__eq__(other) |
| 146 | 164 | ||
| 147 | def _resolveFetchUrl(self): | 165 | def _resolveFetchUrl(self): |
| 166 | if self.fetchUrl is None: | ||
| 167 | return '' | ||
| 148 | url = self.fetchUrl.rstrip('/') | 168 | url = self.fetchUrl.rstrip('/') |
| 149 | manifestUrl = self.manifestUrl.rstrip('/') | 169 | manifestUrl = self.manifestUrl.rstrip('/') |
| 150 | # urljoin will gets confused over quite a few things. The ones we care | 170 | # urljoin will gets confused over quite a few things. The ones we care |
| @@ -173,6 +193,9 @@ class _XmlRemote(object): | |||
| 173 | orig_name=self.name, | 193 | orig_name=self.name, |
| 174 | fetchUrl=self.fetchUrl) | 194 | fetchUrl=self.fetchUrl) |
| 175 | 195 | ||
| 196 | def AddAnnotation(self, name, value, keep): | ||
| 197 | self.annotations.append(Annotation(name, value, keep)) | ||
| 198 | |||
| 176 | 199 | ||
| 177 | class XmlManifest(object): | 200 | class XmlManifest(object): |
| 178 | """manages the repo configuration file""" | 201 | """manages the repo configuration file""" |
| @@ -247,8 +270,7 @@ class XmlManifest(object): | |||
| 247 | self.Override(name) | 270 | self.Override(name) |
| 248 | 271 | ||
| 249 | # Old versions of repo would generate symlinks we need to clean up. | 272 | # Old versions of repo would generate symlinks we need to clean up. |
| 250 | if os.path.lexists(self.manifestFile): | 273 | platform_utils.remove(self.manifestFile, missing_ok=True) |
| 251 | platform_utils.remove(self.manifestFile) | ||
| 252 | # This file is interpreted as if it existed inside the manifest repo. | 274 | # This file is interpreted as if it existed inside the manifest repo. |
| 253 | # That allows us to use <include> with the relative file name. | 275 | # That allows us to use <include> with the relative file name. |
| 254 | with open(self.manifestFile, 'w') as fp: | 276 | with open(self.manifestFile, 'w') as fp: |
| @@ -282,6 +304,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 282 | if r.revision is not None: | 304 | if r.revision is not None: |
| 283 | e.setAttribute('revision', r.revision) | 305 | e.setAttribute('revision', r.revision) |
| 284 | 306 | ||
| 307 | for a in r.annotations: | ||
| 308 | if a.keep == 'true': | ||
| 309 | ae = doc.createElement('annotation') | ||
| 310 | ae.setAttribute('name', a.name) | ||
| 311 | ae.setAttribute('value', a.value) | ||
| 312 | e.appendChild(ae) | ||
| 313 | |||
| 285 | def _ParseList(self, field): | 314 | def _ParseList(self, field): |
| 286 | """Parse fields that contain flattened lists. | 315 | """Parse fields that contain flattened lists. |
| 287 | 316 | ||
| @@ -477,6 +506,15 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 477 | if not d.remote or remote.orig_name != remoteName: | 506 | if not d.remote or remote.orig_name != remoteName: |
| 478 | remoteName = remote.orig_name | 507 | remoteName = remote.orig_name |
| 479 | e.setAttribute('remote', remoteName) | 508 | e.setAttribute('remote', remoteName) |
| 509 | revision = remote.revision or d.revisionExpr | ||
| 510 | if not revision or revision != self._superproject['revision']: | ||
| 511 | e.setAttribute('revision', self._superproject['revision']) | ||
| 512 | root.appendChild(e) | ||
| 513 | |||
| 514 | if self._contactinfo.bugurl != Wrapper().BUG_URL: | ||
| 515 | root.appendChild(doc.createTextNode('')) | ||
| 516 | e = doc.createElement('contactinfo') | ||
| 517 | e.setAttribute('bugurl', self._contactinfo.bugurl) | ||
| 480 | root.appendChild(e) | 518 | root.appendChild(e) |
| 481 | 519 | ||
| 482 | return doc | 520 | return doc |
| @@ -490,6 +528,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 490 | 'manifest-server', | 528 | 'manifest-server', |
| 491 | 'repo-hooks', | 529 | 'repo-hooks', |
| 492 | 'superproject', | 530 | 'superproject', |
| 531 | 'contactinfo', | ||
| 493 | } | 532 | } |
| 494 | # Elements that may be repeated. | 533 | # Elements that may be repeated. |
| 495 | MULTI_ELEMENTS = { | 534 | MULTI_ELEMENTS = { |
| @@ -566,6 +605,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 566 | return self._superproject | 605 | return self._superproject |
| 567 | 606 | ||
| 568 | @property | 607 | @property |
| 608 | def contactinfo(self): | ||
| 609 | self._Load() | ||
| 610 | return self._contactinfo | ||
| 611 | |||
| 612 | @property | ||
| 569 | def notice(self): | 613 | def notice(self): |
| 570 | self._Load() | 614 | self._Load() |
| 571 | return self._notice | 615 | return self._notice |
| @@ -596,6 +640,17 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 596 | return set(x.strip() for x in exclude.split(',')) | 640 | return set(x.strip() for x in exclude.split(',')) |
| 597 | 641 | ||
| 598 | @property | 642 | @property |
| 643 | def UseLocalManifests(self): | ||
| 644 | return self._load_local_manifests | ||
| 645 | |||
| 646 | def SetUseLocalManifests(self, value): | ||
| 647 | self._load_local_manifests = value | ||
| 648 | |||
| 649 | @property | ||
| 650 | def HasLocalManifests(self): | ||
| 651 | return self._load_local_manifests and self.local_manifests | ||
| 652 | |||
| 653 | @property | ||
| 599 | def IsMirror(self): | 654 | def IsMirror(self): |
| 600 | return self.manifestProject.config.GetBoolean('repo.mirror') | 655 | return self.manifestProject.config.GetBoolean('repo.mirror') |
| 601 | 656 | ||
| @@ -630,6 +685,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 630 | self._default = None | 685 | self._default = None |
| 631 | self._repo_hooks_project = None | 686 | self._repo_hooks_project = None |
| 632 | self._superproject = {} | 687 | self._superproject = {} |
| 688 | self._contactinfo = ContactInfo(Wrapper().BUG_URL) | ||
| 633 | self._notice = None | 689 | self._notice = None |
| 634 | self.branch = None | 690 | self.branch = None |
| 635 | self._manifest_server = None | 691 | self._manifest_server = None |
| @@ -657,7 +713,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 657 | # Since local manifests are entirely managed by the user, allow | 713 | # Since local manifests are entirely managed by the user, allow |
| 658 | # them to point anywhere the user wants. | 714 | # them to point anywhere the user wants. |
| 659 | nodes.append(self._ParseManifestXml( | 715 | nodes.append(self._ParseManifestXml( |
| 660 | local, self.repodir, restrict_includes=False)) | 716 | local, self.repodir, |
| 717 | parent_groups=f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}', | ||
| 718 | restrict_includes=False)) | ||
| 661 | except OSError: | 719 | except OSError: |
| 662 | pass | 720 | pass |
| 663 | 721 | ||
| @@ -754,9 +812,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 754 | for node in itertools.chain(*node_list): | 812 | for node in itertools.chain(*node_list): |
| 755 | if node.nodeName == 'default': | 813 | if node.nodeName == 'default': |
| 756 | new_default = self._ParseDefault(node) | 814 | new_default = self._ParseDefault(node) |
| 815 | emptyDefault = not node.hasAttributes() and not node.hasChildNodes() | ||
| 757 | if self._default is None: | 816 | if self._default is None: |
| 758 | self._default = new_default | 817 | self._default = new_default |
| 759 | elif new_default != self._default: | 818 | elif not emptyDefault and new_default != self._default: |
| 760 | raise ManifestParseError('duplicate default in %s' % | 819 | raise ManifestParseError('duplicate default in %s' % |
| 761 | (self.manifestFile)) | 820 | (self.manifestFile)) |
| 762 | 821 | ||
| @@ -795,6 +854,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 795 | for subproject in project.subprojects: | 854 | for subproject in project.subprojects: |
| 796 | recursively_add_projects(subproject) | 855 | recursively_add_projects(subproject) |
| 797 | 856 | ||
| 857 | repo_hooks_project = None | ||
| 858 | enabled_repo_hooks = None | ||
| 798 | for node in itertools.chain(*node_list): | 859 | for node in itertools.chain(*node_list): |
| 799 | if node.nodeName == 'project': | 860 | if node.nodeName == 'project': |
| 800 | project = self._ParseProject(node) | 861 | project = self._ParseProject(node) |
| @@ -807,6 +868,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 807 | 'project: %s' % name) | 868 | 'project: %s' % name) |
| 808 | 869 | ||
| 809 | path = node.getAttribute('path') | 870 | path = node.getAttribute('path') |
| 871 | dest_path = node.getAttribute('dest-path') | ||
| 810 | groups = node.getAttribute('groups') | 872 | groups = node.getAttribute('groups') |
| 811 | if groups: | 873 | if groups: |
| 812 | groups = self._ParseList(groups) | 874 | groups = self._ParseList(groups) |
| @@ -815,46 +877,37 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 815 | if remote: | 877 | if remote: |
| 816 | remote = self._get_remote(node) | 878 | remote = self._get_remote(node) |
| 817 | 879 | ||
| 880 | named_projects = self._projects[name] | ||
| 881 | if dest_path and not path and len(named_projects) > 1: | ||
| 882 | raise ManifestParseError('extend-project cannot use dest-path when ' | ||
| 883 | 'matching multiple projects: %s' % name) | ||
| 818 | for p in self._projects[name]: | 884 | for p in self._projects[name]: |
| 819 | if path and p.relpath != path: | 885 | if path and p.relpath != path: |
| 820 | continue | 886 | continue |
| 821 | if groups: | 887 | if groups: |
| 822 | p.groups.extend(groups) | 888 | p.groups.extend(groups) |
| 823 | if revision: | 889 | if revision: |
| 824 | p.revisionExpr = revision | 890 | p.SetRevision(revision) |
| 825 | if IsId(revision): | 891 | |
| 826 | p.revisionId = revision | ||
| 827 | else: | ||
| 828 | p.revisionId = None | ||
| 829 | if remote: | 892 | if remote: |
| 830 | p.remote = remote.ToRemoteSpec(name) | 893 | p.remote = remote.ToRemoteSpec(name) |
| 831 | if node.nodeName == 'repo-hooks': | ||
| 832 | # Get the name of the project and the (space-separated) list of enabled. | ||
| 833 | repo_hooks_project = self._reqatt(node, 'in-project') | ||
| 834 | enabled_repo_hooks = self._ParseList(self._reqatt(node, 'enabled-list')) | ||
| 835 | 894 | ||
| 895 | if dest_path: | ||
| 896 | del self._paths[p.relpath] | ||
| 897 | relpath, worktree, gitdir, objdir, _ = self.GetProjectPaths(name, dest_path) | ||
| 898 | p.UpdatePaths(relpath, worktree, gitdir, objdir) | ||
| 899 | self._paths[p.relpath] = p | ||
| 900 | |||
| 901 | if node.nodeName == 'repo-hooks': | ||
| 836 | # Only one project can be the hooks project | 902 | # Only one project can be the hooks project |
| 837 | if self._repo_hooks_project is not None: | 903 | if repo_hooks_project is not None: |
| 838 | raise ManifestParseError( | 904 | raise ManifestParseError( |
| 839 | 'duplicate repo-hooks in %s' % | 905 | 'duplicate repo-hooks in %s' % |
| 840 | (self.manifestFile)) | 906 | (self.manifestFile)) |
| 841 | 907 | ||
| 842 | # Store a reference to the Project. | 908 | # Get the name of the project and the (space-separated) list of enabled. |
| 843 | try: | 909 | repo_hooks_project = self._reqatt(node, 'in-project') |
| 844 | repo_hooks_projects = self._projects[repo_hooks_project] | 910 | enabled_repo_hooks = self._ParseList(self._reqatt(node, 'enabled-list')) |
| 845 | except KeyError: | ||
| 846 | raise ManifestParseError( | ||
| 847 | 'project %s not found for repo-hooks' % | ||
| 848 | (repo_hooks_project)) | ||
| 849 | |||
| 850 | if len(repo_hooks_projects) != 1: | ||
| 851 | raise ManifestParseError( | ||
| 852 | 'internal error parsing repo-hooks in %s' % | ||
| 853 | (self.manifestFile)) | ||
| 854 | self._repo_hooks_project = repo_hooks_projects[0] | ||
| 855 | |||
| 856 | # Store the enabled hooks in the Project object. | ||
| 857 | self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks | ||
| 858 | if node.nodeName == 'superproject': | 911 | if node.nodeName == 'superproject': |
| 859 | name = self._reqatt(node, 'name') | 912 | name = self._reqatt(node, 'name') |
| 860 | # There can only be one superproject. | 913 | # There can only be one superproject. |
| @@ -872,21 +925,51 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 872 | raise ManifestParseError("no remote for superproject %s within %s" % | 925 | raise ManifestParseError("no remote for superproject %s within %s" % |
| 873 | (name, self.manifestFile)) | 926 | (name, self.manifestFile)) |
| 874 | self._superproject['remote'] = remote.ToRemoteSpec(name) | 927 | self._superproject['remote'] = remote.ToRemoteSpec(name) |
| 928 | revision = node.getAttribute('revision') or remote.revision | ||
| 929 | if not revision: | ||
| 930 | revision = self._default.revisionExpr | ||
| 931 | if not revision: | ||
| 932 | raise ManifestParseError('no revision for superproject %s within %s' % | ||
| 933 | (name, self.manifestFile)) | ||
| 934 | self._superproject['revision'] = revision | ||
| 935 | if node.nodeName == 'contactinfo': | ||
| 936 | bugurl = self._reqatt(node, 'bugurl') | ||
| 937 | # This element can be repeated, later entries will clobber earlier ones. | ||
| 938 | self._contactinfo = ContactInfo(bugurl) | ||
| 939 | |||
| 875 | if node.nodeName == 'remove-project': | 940 | if node.nodeName == 'remove-project': |
| 876 | name = self._reqatt(node, 'name') | 941 | name = self._reqatt(node, 'name') |
| 877 | 942 | ||
| 878 | if name not in self._projects: | 943 | if name in self._projects: |
| 944 | for p in self._projects[name]: | ||
| 945 | del self._paths[p.relpath] | ||
| 946 | del self._projects[name] | ||
| 947 | |||
| 948 | # If the manifest removes the hooks project, treat it as if it deleted | ||
| 949 | # the repo-hooks element too. | ||
| 950 | if repo_hooks_project == name: | ||
| 951 | repo_hooks_project = None | ||
| 952 | elif not XmlBool(node, 'optional', False): | ||
| 879 | raise ManifestParseError('remove-project element specifies non-existent ' | 953 | raise ManifestParseError('remove-project element specifies non-existent ' |
| 880 | 'project: %s' % name) | 954 | 'project: %s' % name) |
| 881 | 955 | ||
| 882 | for p in self._projects[name]: | 956 | # Store repo hooks project information. |
| 883 | del self._paths[p.relpath] | 957 | if repo_hooks_project: |
| 884 | del self._projects[name] | 958 | # Store a reference to the Project. |
| 959 | try: | ||
| 960 | repo_hooks_projects = self._projects[repo_hooks_project] | ||
| 961 | except KeyError: | ||
| 962 | raise ManifestParseError( | ||
| 963 | 'project %s not found for repo-hooks' % | ||
| 964 | (repo_hooks_project)) | ||
| 885 | 965 | ||
| 886 | # If the manifest removes the hooks project, treat it as if it deleted | 966 | if len(repo_hooks_projects) != 1: |
| 887 | # the repo-hooks element too. | 967 | raise ManifestParseError( |
| 888 | if self._repo_hooks_project and (self._repo_hooks_project.name == name): | 968 | 'internal error parsing repo-hooks in %s' % |
| 889 | self._repo_hooks_project = None | 969 | (self.manifestFile)) |
| 970 | self._repo_hooks_project = repo_hooks_projects[0] | ||
| 971 | # Store the enabled hooks in the Project object. | ||
| 972 | self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks | ||
| 890 | 973 | ||
| 891 | def _AddMetaProjectMirror(self, m): | 974 | def _AddMetaProjectMirror(self, m): |
| 892 | name = None | 975 | name = None |
| @@ -945,7 +1028,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 945 | if revision == '': | 1028 | if revision == '': |
| 946 | revision = None | 1029 | revision = None |
| 947 | manifestUrl = self.manifestProject.config.GetString('remote.origin.url') | 1030 | manifestUrl = self.manifestProject.config.GetString('remote.origin.url') |
| 948 | return _XmlRemote(name, alias, fetch, pushUrl, manifestUrl, review, revision) | 1031 | |
| 1032 | remote = _XmlRemote(name, alias, fetch, pushUrl, manifestUrl, review, revision) | ||
| 1033 | |||
| 1034 | for n in node.childNodes: | ||
| 1035 | if n.nodeName == 'annotation': | ||
| 1036 | self._ParseAnnotation(remote, n) | ||
| 1037 | |||
| 1038 | return remote | ||
| 949 | 1039 | ||
| 950 | def _ParseDefault(self, node): | 1040 | def _ParseDefault(self, node): |
| 951 | """ | 1041 | """ |
| @@ -1199,6 +1289,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 1199 | if '~' in path: | 1289 | if '~' in path: |
| 1200 | return '~ not allowed (due to 8.3 filenames on Windows filesystems)' | 1290 | return '~ not allowed (due to 8.3 filenames on Windows filesystems)' |
| 1201 | 1291 | ||
| 1292 | path_codepoints = set(path) | ||
| 1293 | |||
| 1202 | # Some filesystems (like Apple's HFS+) try to normalize Unicode codepoints | 1294 | # Some filesystems (like Apple's HFS+) try to normalize Unicode codepoints |
| 1203 | # which means there are alternative names for ".git". Reject paths with | 1295 | # which means there are alternative names for ".git". Reject paths with |
| 1204 | # these in it as there shouldn't be any reasonable need for them here. | 1296 | # these in it as there shouldn't be any reasonable need for them here. |
| @@ -1222,10 +1314,17 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 1222 | u'\u206F', # NOMINAL DIGIT SHAPES | 1314 | u'\u206F', # NOMINAL DIGIT SHAPES |
| 1223 | u'\uFEFF', # ZERO WIDTH NO-BREAK SPACE | 1315 | u'\uFEFF', # ZERO WIDTH NO-BREAK SPACE |
| 1224 | } | 1316 | } |
| 1225 | if BAD_CODEPOINTS & set(path): | 1317 | if BAD_CODEPOINTS & path_codepoints: |
| 1226 | # This message is more expansive than reality, but should be fine. | 1318 | # This message is more expansive than reality, but should be fine. |
| 1227 | return 'Unicode combining characters not allowed' | 1319 | return 'Unicode combining characters not allowed' |
| 1228 | 1320 | ||
| 1321 | # Reject newlines as there shouldn't be any legitmate use for them, they'll | ||
| 1322 | # be confusing to users, and they can easily break tools that expect to be | ||
| 1323 | # able to iterate over newline delimited lists. This even applies to our | ||
| 1324 | # own code like .repo/project.list. | ||
| 1325 | if {'\r', '\n'} & path_codepoints: | ||
| 1326 | return 'Newlines not allowed' | ||
| 1327 | |||
| 1229 | # Assume paths might be used on case-insensitive filesystems. | 1328 | # Assume paths might be used on case-insensitive filesystems. |
| 1230 | path = path.lower() | 1329 | path = path.lower() |
| 1231 | 1330 | ||
| @@ -1303,7 +1402,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 1303 | self._ValidateFilePaths('linkfile', src, dest) | 1402 | self._ValidateFilePaths('linkfile', src, dest) |
| 1304 | project.AddLinkFile(src, dest, self.topdir) | 1403 | project.AddLinkFile(src, dest, self.topdir) |
| 1305 | 1404 | ||
| 1306 | def _ParseAnnotation(self, project, node): | 1405 | def _ParseAnnotation(self, element, node): |
| 1307 | name = self._reqatt(node, 'name') | 1406 | name = self._reqatt(node, 'name') |
| 1308 | value = self._reqatt(node, 'value') | 1407 | value = self._reqatt(node, 'value') |
| 1309 | try: | 1408 | try: |
| @@ -1313,7 +1412,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
| 1313 | if keep != "true" and keep != "false": | 1412 | if keep != "true" and keep != "false": |
| 1314 | raise ManifestParseError('optional "keep" attribute must be ' | 1413 | raise ManifestParseError('optional "keep" attribute must be ' |
| 1315 | '"true" or "false"') | 1414 | '"true" or "false"') |
| 1316 | project.AddAnnotation(name, value, keep) | 1415 | element.AddAnnotation(name, value, keep) |
| 1317 | 1416 | ||
| 1318 | def _get_remote(self, node): | 1417 | def _get_remote(self, node): |
| 1319 | name = node.getAttribute('remote') | 1418 | name = node.getAttribute('remote') |
