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

Commit619c989

Browse files
committed
GitConfigParser now respects and merges 'include' sections
We implement it as described in this article:http://stackoverflow.com/questions/1557183/is-it-possible-to-include-a-file-in-your-gitconfigThus we handle* cycles* relative and absolute include paths* write-backs in case of writable GitConfigParser instancesFixesgitpython-developers#201
1 parentbe074c6 commit619c989

File tree

5 files changed

+143
-17
lines changed

5 files changed

+143
-17
lines changed

‎.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[submodule "gitdb"]
2-
path=git/ext/gitdb
32
url=https://github.com/gitpython-developers/gitdb.git
3+
path=git/ext/gitdb

‎git/config.py

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
importinspect
1616
importlogging
1717
importabc
18+
importos
1819

1920
fromgit.odictimportOrderedDict
2021
fromgit.utilimportLockFile
@@ -164,7 +165,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje
164165
# list of RawConfigParser methods able to change the instance
165166
_mutating_methods_= ("add_section","remove_section","remove_option","set")
166167

167-
def__init__(self,file_or_files,read_only=True):
168+
def__init__(self,file_or_files,read_only=True,merge_includes=True):
168169
"""Initialize a configuration reader to read the given file_or_files and to
169170
possibly allow changes to it by setting read_only False
170171
@@ -173,7 +174,13 @@ def __init__(self, file_or_files, read_only=True):
173174
174175
:param read_only:
175176
If True, the ConfigParser may only read the data , but not change it.
176-
If False, only a single file path or file object may be given."""
177+
If False, only a single file path or file object may be given. We will write back the changes
178+
when they happen, or when the ConfigParser is released. This will not happen if other
179+
configuration files have been included
180+
:param merge_includes: if True, we will read files mentioned in [include] sections and merge their
181+
contents into ours. This makes it impossible to write back an individual configuration file.
182+
Thus, if you want to modify a single conifguration file, turn this off to leave the original
183+
dataset unaltered when reading it."""
177184
cp.RawConfigParser.__init__(self,dict_type=OrderedDict)
178185

179186
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
@@ -183,6 +190,7 @@ def __init__(self, file_or_files, read_only=True):
183190
self._file_or_files=file_or_files
184191
self._read_only=read_only
185192
self._is_initialized=False
193+
self._merge_includes=merge_includes
186194
self._lock=None
187195

188196
ifnotread_only:
@@ -313,7 +321,6 @@ def string_decode(v):
313321
ifnote:
314322
e=cp.ParsingError(fpname)
315323
e.append(lineno,repr(line))
316-
print(lineno,line)
317324
continue
318325
else:
319326
line=line.rstrip()
@@ -329,6 +336,9 @@ def string_decode(v):
329336
ife:
330337
raisee
331338

339+
def_has_includes(self):
340+
returnself._merge_includesandself.has_section('include')
341+
332342
defread(self):
333343
"""Reads the data stored in the files we have been initialized with. It will
334344
ignore files that cannot be read, possibly leaving an empty configuration
@@ -337,18 +347,25 @@ def read(self):
337347
:raise IOError: if a file cannot be handled"""
338348
ifself._is_initialized:
339349
return
350+
self._is_initialized=True
340351

341-
files_to_read=self._file_or_files
342-
ifnotisinstance(files_to_read, (tuple,list)):
343-
files_to_read= [files_to_read]
344-
345-
forfile_objectinfiles_to_read:
346-
fp=file_object
352+
ifnotisinstance(self._file_or_files, (tuple,list)):
353+
files_to_read= [self._file_or_files]
354+
else:
355+
files_to_read=list(self._file_or_files)
356+
# end assure we have a copy of the paths to handle
357+
358+
seen=set(files_to_read)
359+
num_read_include_files=0
360+
whilefiles_to_read:
361+
file_path=files_to_read.pop(0)
362+
fp=file_path
347363
close_fp=False
364+
348365
# assume a path if it is not a file-object
349-
ifnothasattr(file_object,"seek"):
366+
ifnothasattr(fp,"seek"):
350367
try:
351-
fp=open(file_object,'rb')
368+
fp=open(file_path,'rb')
352369
close_fp=True
353370
exceptIOError:
354371
continue
@@ -360,8 +377,33 @@ def read(self):
360377
ifclose_fp:
361378
fp.close()
362379
# END read-handling
363-
# END for each file object to read
364-
self._is_initialized=True
380+
381+
# Read includes and append those that we didn't handle yet
382+
# We expect all paths to be normalized and absolute (and will assure that is the case)
383+
ifself._has_includes():
384+
for_,include_pathinself.items('include'):
385+
ifnotos.path.isabs(include_path):
386+
ifnotclose_fp:
387+
continue
388+
# end ignore relative paths if we don't know the configuration file path
389+
assertos.path.isabs(file_path),"Need absolute paths to be sure our cycle checks will work"
390+
include_path=os.path.join(os.path.dirname(file_path),include_path)
391+
# end make include path absolute
392+
include_path=os.path.normpath(include_path)
393+
ifinclude_pathinseenornotos.access(include_path,os.R_OK):
394+
continue
395+
seen.add(include_path)
396+
files_to_read.append(include_path)
397+
num_read_include_files+=1
398+
# each include path in configuration file
399+
# end handle includes
400+
# END for each file object to read
401+
402+
# If there was no file included, we can safely write back (potentially) the configuration file
403+
# without altering it's meaning
404+
ifnum_read_include_files==0:
405+
self._merge_includes=False
406+
# end
365407

366408
def_write(self,fp):
367409
"""Write an .ini-format representation of the configuration state in
@@ -379,6 +421,10 @@ def write_section(name, section_dict):
379421
forname,valueinself._sections.items():
380422
write_section(name,value)
381423

424+
defitems(self,section_name):
425+
""":return: list((option, value), ...) pairs of all items in the given section"""
426+
return [(k,v)fork,vinsuper(GitConfigParser,self).items(section_name)ifk!='__name__']
427+
382428
@needs_values
383429
defwrite(self):
384430
"""Write changes to our file, if there are changes at all
@@ -387,6 +433,17 @@ def write(self):
387433
a file lock"""
388434
self._assure_writable("write")
389435

436+
ifisinstance(self._file_or_files, (list,tuple)):
437+
raiseAssertionError("Cannot write back if there is not exactly a single file to write to, have %i files"
438+
%len(self._file_or_files))
439+
# end assert multiple files
440+
441+
ifself._has_includes():
442+
log.debug("Skipping write-back of confiuration file as include files were merged in."+
443+
"Set merge_includes=False to prevent this.")
444+
return
445+
# end
446+
390447
fp=self._file_or_files
391448
close_fp=False
392449

‎git/repo/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,11 @@ def _get_config_path(self, config_level):
359359
return"/etc/gitconfig"
360360
elifconfig_level=="user":
361361
config_home=os.environ.get("XDG_CONFIG_HOME")oros.path.join(os.environ.get("HOME",'~'),".config")
362-
returnos.path.expanduser(join(config_home,"git","config"))
362+
returnos.path.normpath(os.path.expanduser(join(config_home,"git","config")))
363363
elifconfig_level=="global":
364364
returnos.path.normpath(os.path.expanduser("~/.gitconfig"))
365365
elifconfig_level=="repository":
366-
returnjoin(self.git_dir,"config")
366+
returnos.path.normpath(join(self.git_dir,"config"))
367367

368368
raiseValueError("Invalid configuration level: %r"%config_level)
369369

‎git/test/fixtures/git_config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@
2727
[branch "mainline_performance"]
2828
remote = mainline
2929
merge = refs/heads/master
30+
[include]
31+
path = doesntexist.cfg
32+
abspath = /usr/bin/foodoesntexist.bar

‎git/test/test_config.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
fromgit.test.libimport (
88
TestCase,
99
fixture_path,
10-
assert_equal
10+
assert_equal,
1111
)
12+
fromgitdb.test.libimportwith_rw_directory
1213
fromgitimport (
1314
GitConfigParser
1415
)
1516
fromgit.compatimport (
1617
string_types,
1718
)
1819
importio
20+
importos
1921
fromcopyimportcopy
2022
fromgit.configimportcp
2123

@@ -127,3 +129,67 @@ def test_base(self):
127129

128130
# it raises if there is no default though
129131
self.failUnlessRaises(cp.NoSectionError,r_config.get_value,"doesnt","exist")
132+
133+
@with_rw_directory
134+
deftest_config_include(self,rw_dir):
135+
defwrite_test_value(cw,value):
136+
cw.set_value(value,'value',value)
137+
# end
138+
139+
defcheck_test_value(cr,value):
140+
assertcr.get_value(value,'value')==value
141+
# end
142+
143+
# PREPARE CONFIG FILE A
144+
fpa=os.path.join(rw_dir,'a')
145+
cw=GitConfigParser(fpa,read_only=False)
146+
write_test_value(cw,'a')
147+
148+
fpb=os.path.join(rw_dir,'b')
149+
fpc=os.path.join(rw_dir,'c')
150+
cw.set_value('include','relative_path_b','b')
151+
cw.set_value('include','doesntexist','foobar')
152+
cw.set_value('include','relative_cycle_a_a','a')
153+
cw.set_value('include','absolute_cycle_a_a',fpa)
154+
cw.release()
155+
assertos.path.exists(fpa)
156+
157+
# PREPARE CONFIG FILE B
158+
cw=GitConfigParser(fpb,read_only=False)
159+
write_test_value(cw,'b')
160+
cw.set_value('include','relative_cycle_b_a','a')
161+
cw.set_value('include','absolute_cycle_b_a',fpa)
162+
cw.set_value('include','relative_path_c','c')
163+
cw.set_value('include','absolute_path_c',fpc)
164+
cw.release()
165+
166+
# PREPARE CONFIG FILE C
167+
cw=GitConfigParser(fpc,read_only=False)
168+
write_test_value(cw,'c')
169+
cw.release()
170+
171+
cr=GitConfigParser(fpa,read_only=True)
172+
fortvin ('a','b','c'):
173+
check_test_value(cr,tv)
174+
# end for each test to verify
175+
assertlen(cr.items('include'))==8,"Expected all include sections to be merged"
176+
cr.release()
177+
178+
# test writable config writers - assure write-back doesn't involve includes
179+
cw=GitConfigParser(fpa,read_only=False,merge_includes=True)
180+
tv='x'
181+
write_test_value(cw,tv)
182+
cw.release()
183+
184+
cr=GitConfigParser(fpa,read_only=True)
185+
self.failUnlessRaises(cp.NoSectionError,check_test_value,cr,tv)
186+
cr.release()
187+
188+
# But can make it skip includes alltogether, and thus allow write-backs
189+
cw=GitConfigParser(fpa,read_only=False,merge_includes=False)
190+
write_test_value(cw,tv)
191+
cw.release()
192+
193+
cr=GitConfigParser(fpa,read_only=True)
194+
check_test_value(cr,tv)
195+
cr.release()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp