Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit94bbe51

Browse files
authored
Merge pull request#1054 from buddly27/read-conditional-include
Read conditional include
2 parentsfb7fd31 +5b88532 commit94bbe51

File tree

3 files changed

+189
-6
lines changed

3 files changed

+189
-6
lines changed

‎git/config.py

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
importlogging
1414
importos
1515
importre
16+
importfnmatch
1617
fromcollectionsimportOrderedDict
1718

1819
fromgit.compatimport (
@@ -38,6 +39,10 @@
3839
# represents the configuration level of a configuration file
3940
CONFIG_LEVELS= ("system","user","global","repository")
4041

42+
# Section pattern to detect conditional includes.
43+
# https://git-scm.com/docs/git-config#_conditional_includes
44+
CONDITIONAL_INCLUDE_REGEXP=re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"")
45+
4146

4247
classMetaParserBuilder(abc.ABCMeta):
4348

@@ -247,7 +252,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje
247252
# list of RawConfigParser methods able to change the instance
248253
_mutating_methods_= ("add_section","remove_section","remove_option","set")
249254

250-
def__init__(self,file_or_files=None,read_only=True,merge_includes=True,config_level=None):
255+
def__init__(self,file_or_files=None,read_only=True,merge_includes=True,config_level=None,repo=None):
251256
"""Initialize a configuration reader to read the given file_or_files and to
252257
possibly allow changes to it by setting read_only False
253258
@@ -262,7 +267,10 @@ def __init__(self, file_or_files=None, read_only=True, merge_includes=True, conf
262267
:param merge_includes: if True, we will read files mentioned in [include] sections and merge their
263268
contents into ours. This makes it impossible to write back an individual configuration file.
264269
Thus, if you want to modify a single configuration file, turn this off to leave the original
265-
dataset unaltered when reading it."""
270+
dataset unaltered when reading it.
271+
:param repo: Reference to repository to use if [includeIf] sections are found in configuration files.
272+
273+
"""
266274
cp.RawConfigParser.__init__(self,dict_type=_OMD)
267275

268276
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
@@ -284,6 +292,7 @@ def __init__(self, file_or_files=None, read_only=True, merge_includes=True, conf
284292
self._dirty=False
285293
self._is_initialized=False
286294
self._merge_includes=merge_includes
295+
self._repo=repo
287296
self._lock=None
288297
self._acquire_lock()
289298

@@ -443,7 +452,57 @@ def string_decode(v):
443452
raisee
444453

445454
def_has_includes(self):
446-
returnself._merge_includesandself.has_section('include')
455+
returnself._merge_includesandlen(self._included_paths())
456+
457+
def_included_paths(self):
458+
"""Return all paths that must be included to configuration.
459+
"""
460+
paths= []
461+
462+
forsectioninself.sections():
463+
ifsection=="include":
464+
paths+=self.items(section)
465+
466+
match=CONDITIONAL_INCLUDE_REGEXP.search(section)
467+
ifmatchisNoneorself._repoisNone:
468+
continue
469+
470+
keyword=match.group(1)
471+
value=match.group(2).strip()
472+
473+
ifkeywordin ["gitdir","gitdir/i"]:
474+
value=osp.expanduser(value)
475+
476+
ifnotany(value.startswith(s)forsin ["./","/"]):
477+
value="**/"+value
478+
ifvalue.endswith("/"):
479+
value+="**"
480+
481+
# Ensure that glob is always case insensitive if required.
482+
ifkeyword.endswith("/i"):
483+
value=re.sub(
484+
r"[a-zA-Z]",
485+
lambdam:"[{}{}]".format(
486+
m.group().lower(),
487+
m.group().upper()
488+
),
489+
value
490+
)
491+
492+
iffnmatch.fnmatchcase(self._repo.git_dir,value):
493+
paths+=self.items(section)
494+
495+
elifkeyword=="onbranch":
496+
try:
497+
branch_name=self._repo.active_branch.name
498+
exceptTypeError:
499+
# Ignore section if active branch cannot be retrieved.
500+
continue
501+
502+
iffnmatch.fnmatchcase(branch_name,value):
503+
paths+=self.items(section)
504+
505+
returnpaths
447506

448507
defread(self):
449508
"""Reads the data stored in the files we have been initialized with. It will
@@ -482,7 +541,7 @@ def read(self):
482541
# Read includes and append those that we didn't handle yet
483542
# We expect all paths to be normalized and absolute (and will assure that is the case)
484543
ifself._has_includes():
485-
for_,include_pathinself.items('include'):
544+
for_,include_pathinself._included_paths():
486545
ifinclude_path.startswith('~'):
487546
include_path=osp.expanduser(include_path)
488547
ifnotosp.isabs(include_path):

‎git/repo/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ def config_reader(self, config_level=None):
452452
files= [self._get_config_path(f)forfinself.config_level]
453453
else:
454454
files= [self._get_config_path(config_level)]
455-
returnGitConfigParser(files,read_only=True)
455+
returnGitConfigParser(files,read_only=True,repo=self)
456456

457457
defconfig_writer(self,config_level="repository"):
458458
"""
@@ -467,7 +467,7 @@ def config_writer(self, config_level="repository"):
467467
system = system wide configuration file
468468
global = user level configuration file
469469
repository = configuration file for this repostory only"""
470-
returnGitConfigParser(self._get_config_path(config_level),read_only=False)
470+
returnGitConfigParser(self._get_config_path(config_level),read_only=False,repo=self)
471471

472472
defcommit(self,rev=None):
473473
"""The Commit object for the specified revision

‎test/test_config.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
importglob
88
importio
9+
importos
10+
fromunittestimportmock
911

1012
fromgitimport (
1113
GitConfigParser
@@ -238,6 +240,128 @@ def check_test_value(cr, value):
238240
withGitConfigParser(fpa,read_only=True)ascr:
239241
check_test_value(cr,tv)
240242

243+
@with_rw_directory
244+
deftest_conditional_includes_from_git_dir(self,rw_dir):
245+
# Initiate repository path
246+
git_dir=osp.join(rw_dir,"target1","repo1")
247+
os.makedirs(git_dir)
248+
249+
# Initiate mocked repository
250+
repo=mock.Mock(git_dir=git_dir)
251+
252+
# Initiate config files.
253+
path1=osp.join(rw_dir,"config1")
254+
path2=osp.join(rw_dir,"config2")
255+
template="[includeIf\"{}:{}\"]\n path={}\n"
256+
257+
withopen(path1,"w")asstream:
258+
stream.write(template.format("gitdir",git_dir,path2))
259+
260+
# Ensure that config is ignored if no repo is set.
261+
withGitConfigParser(path1)asconfig:
262+
assertnotconfig._has_includes()
263+
assertconfig._included_paths()== []
264+
265+
# Ensure that config is included if path is matching git_dir.
266+
withGitConfigParser(path1,repo=repo)asconfig:
267+
assertconfig._has_includes()
268+
assertconfig._included_paths()== [("path",path2)]
269+
270+
# Ensure that config is ignored if case is incorrect.
271+
withopen(path1,"w")asstream:
272+
stream.write(template.format("gitdir",git_dir.upper(),path2))
273+
274+
withGitConfigParser(path1,repo=repo)asconfig:
275+
assertnotconfig._has_includes()
276+
assertconfig._included_paths()== []
277+
278+
# Ensure that config is included if case is ignored.
279+
withopen(path1,"w")asstream:
280+
stream.write(template.format("gitdir/i",git_dir.upper(),path2))
281+
282+
withGitConfigParser(path1,repo=repo)asconfig:
283+
assertconfig._has_includes()
284+
assertconfig._included_paths()== [("path",path2)]
285+
286+
# Ensure that config is included with path using glob pattern.
287+
withopen(path1,"w")asstream:
288+
stream.write(template.format("gitdir","**/repo1",path2))
289+
290+
withGitConfigParser(path1,repo=repo)asconfig:
291+
assertconfig._has_includes()
292+
assertconfig._included_paths()== [("path",path2)]
293+
294+
# Ensure that config is ignored if path is not matching git_dir.
295+
withopen(path1,"w")asstream:
296+
stream.write(template.format("gitdir","incorrect",path2))
297+
298+
withGitConfigParser(path1,repo=repo)asconfig:
299+
assertnotconfig._has_includes()
300+
assertconfig._included_paths()== []
301+
302+
# Ensure that config is included if path in hierarchy.
303+
withopen(path1,"w")asstream:
304+
stream.write(template.format("gitdir","target1/",path2))
305+
306+
withGitConfigParser(path1,repo=repo)asconfig:
307+
assertconfig._has_includes()
308+
assertconfig._included_paths()== [("path",path2)]
309+
310+
@with_rw_directory
311+
deftest_conditional_includes_from_branch_name(self,rw_dir):
312+
# Initiate mocked branch
313+
branch=mock.Mock()
314+
type(branch).name=mock.PropertyMock(return_value="/foo/branch")
315+
316+
# Initiate mocked repository
317+
repo=mock.Mock(active_branch=branch)
318+
319+
# Initiate config files.
320+
path1=osp.join(rw_dir,"config1")
321+
path2=osp.join(rw_dir,"config2")
322+
template="[includeIf\"onbranch:{}\"]\n path={}\n"
323+
324+
# Ensure that config is included is branch is correct.
325+
withopen(path1,"w")asstream:
326+
stream.write(template.format("/foo/branch",path2))
327+
328+
withGitConfigParser(path1,repo=repo)asconfig:
329+
assertconfig._has_includes()
330+
assertconfig._included_paths()== [("path",path2)]
331+
332+
# Ensure that config is included is branch is incorrect.
333+
withopen(path1,"w")asstream:
334+
stream.write(template.format("incorrect",path2))
335+
336+
withGitConfigParser(path1,repo=repo)asconfig:
337+
assertnotconfig._has_includes()
338+
assertconfig._included_paths()== []
339+
340+
# Ensure that config is included with branch using glob pattern.
341+
withopen(path1,"w")asstream:
342+
stream.write(template.format("/foo/**",path2))
343+
344+
withGitConfigParser(path1,repo=repo)asconfig:
345+
assertconfig._has_includes()
346+
assertconfig._included_paths()== [("path",path2)]
347+
348+
@with_rw_directory
349+
deftest_conditional_includes_from_branch_name_error(self,rw_dir):
350+
# Initiate mocked repository to raise an error if HEAD is detached.
351+
repo=mock.Mock()
352+
type(repo).active_branch=mock.PropertyMock(side_effect=TypeError)
353+
354+
# Initiate config file.
355+
path1=osp.join(rw_dir,"config1")
356+
357+
# Ensure that config is ignored when active branch cannot be found.
358+
withopen(path1,"w")asstream:
359+
stream.write("[includeIf\"onbranch:foo\"]\n path=/path\n")
360+
361+
withGitConfigParser(path1,repo=repo)asconfig:
362+
assertnotconfig._has_includes()
363+
assertconfig._included_paths()== []
364+
241365
deftest_rename(self):
242366
file_obj=self._to_memcache(fixture_path('git_config'))
243367
withGitConfigParser(file_obj,read_only=False,merge_includes=False)ascw:

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp