From c16f584725a4cadafc6e113abef45f4ea52d03b3 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 13:55:47 -0700 Subject: Add Regex to match content of "includeIf" section --- git/config.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index 43f854f2..4de050a5 100644 --- a/git/config.py +++ b/git/config.py @@ -38,6 +38,9 @@ log.addHandler(logging.NullHandler()) # represents the configuration level of a configuration file CONFIG_LEVELS = ("system", "user", "global", "repository") +# Section pattern to detect conditional includes. +# https://git-scm.com/docs/git-config#_conditional_includes +CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"") class MetaParserBuilder(abc.ABCMeta): -- cgit v1.2.3 From 9766832e11cdd8afed16dfd2d64529c2ae9c3382 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 13:57:06 -0700 Subject: Update check method to find all includes --- git/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index 4de050a5..e686dcd3 100644 --- a/git/config.py +++ b/git/config.py @@ -446,7 +446,10 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje raise e def _has_includes(self): - return self._merge_includes and self.has_section('include') + return self._merge_includes and any( + section == 'include' or section.startswith('includeIf ') + for section in self.sections() + ) def read(self): """Reads the data stored in the files we have been initialized with. It will -- cgit v1.2.3 From d5262acbd33b70fb676284991207fb24fa9ac895 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 14:03:17 -0700 Subject: Add reference to repository to config. This is necessary when working with conditional include sections as it requires the git directory or active branch name. https://git-scm.com/docs/git-config#_conditional_includes --- git/config.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index e686dcd3..6d4135f5 100644 --- a/git/config.py +++ b/git/config.py @@ -250,7 +250,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje # list of RawConfigParser methods able to change the instance _mutating_methods_ = ("add_section", "remove_section", "remove_option", "set") - def __init__(self, file_or_files=None, read_only=True, merge_includes=True, config_level=None): + def __init__(self, file_or_files=None, read_only=True, merge_includes=True, config_level=None, repo=None): """Initialize a configuration reader to read the given file_or_files and to possibly allow changes to it by setting read_only False @@ -265,7 +265,10 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje :param merge_includes: if True, we will read files mentioned in [include] sections and merge their contents into ours. This makes it impossible to write back an individual configuration file. Thus, if you want to modify a single configuration file, turn this off to leave the original - dataset unaltered when reading it.""" + dataset unaltered when reading it. + :param repo: Reference to repository to use if [includeIf] sections are found in configuration files. + + """ cp.RawConfigParser.__init__(self, dict_type=_OMD) # Used in python 3, needs to stay in sync with sections for underlying implementation to work @@ -287,6 +290,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje self._dirty = False self._is_initialized = False self._merge_includes = merge_includes + self._repo = repo self._lock = None self._acquire_lock() -- cgit v1.2.3 From 8e263b8f972f78c673f36f2bbc1f8563ce6acb10 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 14:05:06 -0700 Subject: Add method to retrieve all possible paths to include --- git/config.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index 6d4135f5..0618c8ac 100644 --- a/git/config.py +++ b/git/config.py @@ -13,6 +13,8 @@ from io import IOBase import logging import os import re +import glob +import fnmatch from collections import OrderedDict from git.compat import ( @@ -455,6 +457,39 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje for section in self.sections() ) + def _included_paths(self): + """Return all paths that must be included to configuration. + """ + paths = [] + + for section in self.sections(): + if section == "include": + paths += self.items(section) + + match = CONDITIONAL_INCLUDE_REGEXP.search(section) + if match is not None and self._repo is not None: + keyword = match.group(1) + value = match.group(2).strip() + + if keyword in ["gitdir", "gitdir/i"]: + value = osp.expanduser(value) + flags = [re.IGNORECASE] if keyword == "gitdir/i" else [] + regexp = re.compile(fnmatch.translate(value), *flags) + + if any( + regexp.match(path) is not None + and self._repo.git_dir.startswith(path) + for path in glob.glob(value) + ): + paths += self.items(section) + + + elif keyword == "onbranch": + if value == self._repo.active_branch.name: + paths += self.items(section) + + return paths + def read(self): """Reads the data stored in the files we have been initialized with. It will ignore files that cannot be read, possibly leaving an empty configuration @@ -492,7 +527,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje # Read includes and append those that we didn't handle yet # We expect all paths to be normalized and absolute (and will assure that is the case) if self._has_includes(): - for _, include_path in self.items('include'): + for _, include_path in self._included_paths(): if include_path.startswith('~'): include_path = osp.expanduser(include_path) if not osp.isabs(include_path): -- cgit v1.2.3 From f37011d7627e4a46cff26f07ea7ade48b284edee Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 17:14:14 -0700 Subject: Fix logic to properly compare glob pattern to value --- git/config.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index 0618c8ac..e36af9fe 100644 --- a/git/config.py +++ b/git/config.py @@ -13,7 +13,6 @@ from io import IOBase import logging import os import re -import glob import fnmatch from collections import OrderedDict @@ -452,10 +451,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje raise e def _has_includes(self): - return self._merge_includes and any( - section == 'include' or section.startswith('includeIf ') - for section in self.sections() - ) + return self._merge_includes and len(self._included_paths()) def _included_paths(self): """Return all paths that must be included to configuration. @@ -471,21 +467,26 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje keyword = match.group(1) value = match.group(2).strip() - if keyword in ["gitdir", "gitdir/i"]: + if keyword == "gitdir": value = osp.expanduser(value) - flags = [re.IGNORECASE] if keyword == "gitdir/i" else [] - regexp = re.compile(fnmatch.translate(value), *flags) - - if any( - regexp.match(path) is not None - and self._repo.git_dir.startswith(path) - for path in glob.glob(value) - ): + if fnmatch.fnmatchcase(self._repo.git_dir, value): paths += self.items(section) + elif keyword == "gitdir/i": + value = osp.expanduser(value) + + # Ensure that glob is always case insensitive. + value = re.sub( + r"[a-zA-Z]", + lambda m: f"[{m.group().lower()}{m.group().upper()}]", + value + ) + + if fnmatch.fnmatchcase(self._repo.git_dir, value): + paths += self.items(section) elif keyword == "onbranch": - if value == self._repo.active_branch.name: + if fnmatch.fnmatchcase(self._repo.active_branch.name, value): paths += self.items(section) return paths -- cgit v1.2.3 From c3fc83f2333eaee5fbcbef6df9f4ed9eb320fd11 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 17:38:43 -0700 Subject: Add missing rules to match hierarchy path --- git/config.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index e36af9fe..eb46c41b 100644 --- a/git/config.py +++ b/git/config.py @@ -467,20 +467,24 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje keyword = match.group(1) value = match.group(2).strip() - if keyword == "gitdir": - value = osp.expanduser(value) - if fnmatch.fnmatchcase(self._repo.git_dir, value): - paths += self.items(section) - - elif keyword == "gitdir/i": + if keyword in ["gitdir", "gitdir/i"]: value = osp.expanduser(value) - # Ensure that glob is always case insensitive. - value = re.sub( - r"[a-zA-Z]", - lambda m: f"[{m.group().lower()}{m.group().upper()}]", - value - ) + if not any(value.startswith(s) for s in ["./", "/"]): + value = "**/" + value + if value.endswith("/"): + value += "**" + + # Ensure that glob is always case insensitive if required. + if keyword.endswith("/i"): + value = re.sub( + r"[a-zA-Z]", + lambda m: "[{}{}]".format( + m.group().lower(), + m.group().upper() + ), + value + ) if fnmatch.fnmatchcase(self._repo.git_dir, value): paths += self.items(section) -- cgit v1.2.3 From c3eae2cdd688d8969a4f36b96a8b41fa55c0d3ed Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Wed, 2 Sep 2020 17:44:47 -0700 Subject: Add missing blank line --- git/config.py | 1 + 1 file changed, 1 insertion(+) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index eb46c41b..f9a5a46b 100644 --- a/git/config.py +++ b/git/config.py @@ -43,6 +43,7 @@ CONFIG_LEVELS = ("system", "user", "global", "repository") # https://git-scm.com/docs/git-config#_conditional_includes CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"") + class MetaParserBuilder(abc.ABCMeta): """Utlity class wrapping base-class methods into decorators that assure read-only properties""" -- cgit v1.2.3 From 3787f622e59c2fecfa47efc114c409f51a27bbe7 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Thu, 3 Sep 2020 11:11:34 -0700 Subject: Reformat code to remove unnecessary indentation --- git/config.py | 60 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 29 deletions(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index f9a5a46b..b49f0790 100644 --- a/git/config.py +++ b/git/config.py @@ -464,35 +464,37 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje paths += self.items(section) match = CONDITIONAL_INCLUDE_REGEXP.search(section) - if match is not None and self._repo is not None: - keyword = match.group(1) - value = match.group(2).strip() - - if keyword in ["gitdir", "gitdir/i"]: - value = osp.expanduser(value) - - if not any(value.startswith(s) for s in ["./", "/"]): - value = "**/" + value - if value.endswith("/"): - value += "**" - - # Ensure that glob is always case insensitive if required. - if keyword.endswith("/i"): - value = re.sub( - r"[a-zA-Z]", - lambda m: "[{}{}]".format( - m.group().lower(), - m.group().upper() - ), - value - ) - - if fnmatch.fnmatchcase(self._repo.git_dir, value): - paths += self.items(section) - - elif keyword == "onbranch": - if fnmatch.fnmatchcase(self._repo.active_branch.name, value): - paths += self.items(section) + if match is None or self._repo is None: + continue + + keyword = match.group(1) + value = match.group(2).strip() + + if keyword in ["gitdir", "gitdir/i"]: + value = osp.expanduser(value) + + if not any(value.startswith(s) for s in ["./", "/"]): + value = "**/" + value + if value.endswith("/"): + value += "**" + + # Ensure that glob is always case insensitive if required. + if keyword.endswith("/i"): + value = re.sub( + r"[a-zA-Z]", + lambda m: "[{}{}]".format( + m.group().lower(), + m.group().upper() + ), + value + ) + + if fnmatch.fnmatchcase(self._repo.git_dir, value): + paths += self.items(section) + + elif keyword == "onbranch": + if fnmatch.fnmatchcase(self._repo.active_branch.name, value): + paths += self.items(section) return paths -- cgit v1.2.3 From 5b88532a5346a9a7e8f0e45fec14632a9bfe2c89 Mon Sep 17 00:00:00 2001 From: Jeremy Retailleau Date: Thu, 3 Sep 2020 11:24:10 -0700 Subject: Ensure that detached HEAD does not raise when comparing branch name. --- git/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'git/config.py') diff --git a/git/config.py b/git/config.py index b49f0790..9f09efe2 100644 --- a/git/config.py +++ b/git/config.py @@ -493,7 +493,13 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje paths += self.items(section) elif keyword == "onbranch": - if fnmatch.fnmatchcase(self._repo.active_branch.name, value): + try: + branch_name = self._repo.active_branch.name + except TypeError: + # Ignore section if active branch cannot be retrieved. + continue + + if fnmatch.fnmatchcase(branch_name, value): paths += self.items(section) return paths -- cgit v1.2.3