Skip to content

Commit a0146a7

Browse files
Andrew VitkoAndrew Vitko
Andrew Vitko
authored and
Andrew Vitko
committed
My local changes to make it work with sparta
1 parent 6971a93 commit a0146a7

File tree

18 files changed

+534
-65
lines changed

18 files changed

+534
-65
lines changed

git/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
import os.path as osp
1313

1414

15-
__version__ = 'git'
15+
__version__ = 'vitko'
1616

1717

1818
#{ Initialization
1919
def _init_externals():
2020
"""Initialize external projects by putting them into the path"""
21-
if __version__ == 'git':
21+
if __version__ == '2.1.11':
2222
sys.path.insert(0, osp.join(osp.dirname(__file__), 'ext', 'gitdb'))
2323

2424
try:

git/cmd.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@
99
import logging
1010
import os
1111
import signal
12+
from path import Path
1213
from subprocess import (
1314
call,
1415
Popen,
15-
PIPE
16+
PIPE,
17+
STDOUT
1618
)
1719
import subprocess
1820
import sys
1921
import threading
2022
from collections import OrderedDict
2123
from textwrap import dedent
22-
23-
from git.compat import (
24+
from .compat import (
2425
string_types,
2526
defenc,
2627
force_bytes,
@@ -31,16 +32,17 @@
3132
is_posix,
3233
is_win,
3334
)
34-
from git.exc import CommandError
35-
from git.util import is_cygwin_git, cygpath, expand_path
36-
3735
from .exc import (
3836
GitCommandError,
37+
InvalidGitRepositoryError,
3938
GitCommandNotFound
4039
)
4140
from .util import (
4241
LazyMixin,
4342
stream_copy,
43+
is_cygwin_git,
44+
cygpath,
45+
expand_path
4446
)
4547

4648
try:
@@ -82,17 +84,20 @@ def handle_process_output(process, stdout_handler, stderr_handler,
8284
Set it to False if `universal_newline == True` (then streams are in text-mode)
8385
or if decoding must happen later (i.e. for Diffs).
8486
"""
85-
# Use 2 "pump" threads and wait for both to finish.
87+
pumped_lines = {'stdout': [], 'stderr': []}
88+
# Use 2 "pupm" threads and wait for both to finish.
89+
# BF: Collect all pumped output to possibly provide it within thrown exception
8690
def pump_stream(cmdline, name, stream, is_decode, handler):
8791
try:
8892
for line in stream:
93+
if is_decode:
94+
line = line.decode(defenc)
95+
pumped_lines[name].append(line)
8996
if handler:
90-
if is_decode:
91-
line = line.decode(defenc)
9297
handler(line)
9398
except Exception as ex:
9499
log.error("Pumping %r of cmd(%s) failed due to: %r", name, cmdline, ex)
95-
raise CommandError(['<%s-pump>' % name] + cmdline, ex)
100+
raise CommandError(['<%s-pump>' % name] + cmdline, ex, **pumped_lines)
96101
finally:
97102
stream.close()
98103

@@ -108,20 +113,29 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
108113

109114
threads = []
110115

111-
for name, stream, handler in pumps:
112-
t = threading.Thread(target=pump_stream,
113-
args=(cmdline, name, stream, decode_streams, handler))
114-
t.setDaemon(True)
115-
t.start()
116-
threads.append(t)
116+
try:
117+
for name, stream, handler in pumps:
118+
t = threading.Thread(target=pump_stream,
119+
args=(cmdline, name, stream, decode_streams, handler))
120+
t.setDaemon(True)
121+
t.start()
122+
threads.append(t)
123+
124+
## FIXME: Why Join?? Will block if `stdin` needs feeding...
125+
#
126+
for t in threads:
127+
t.join()
117128

118-
## FIXME: Why Join?? Will block if `stdin` needs feeding...
119-
#
120-
for t in threads:
121-
t.join()
129+
if finalizer:
130+
return finalizer(process)
131+
except Exception as e:
132+
for outname, out in pumped_lines.items():
133+
if out and hasattr(e, outname):
134+
eout = getattr(e, outname)
135+
if not eout:
136+
setattr(e, outname, safe_decode(os.linesep.join(out)))
137+
raise
122138

123-
if finalizer:
124-
return finalizer(process)
125139

126140

127141
def dashify(string):
@@ -218,7 +232,7 @@ def refresh(cls, path=None):
218232
# - a GitCommandNotFound error is spawned by ourselves
219233
# - a PermissionError is spawned if the git executable provided
220234
# cannot be executed for whatever reason
221-
235+
222236
has_git = False
223237
try:
224238
cls().version()
@@ -312,6 +326,11 @@ def refresh(cls, path=None):
312326

313327
return has_git
314328

329+
def is_git_dir(self):
330+
if call(['git', 'rev-parse', '--is-inside-work-tree'], stderr=STDOUT, stdout=open(os.devnull, 'w'), cwd=self._working_dir) != 0:
331+
return False
332+
return True
333+
315334
@classmethod
316335
def is_cygwin(cls):
317336
return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE)
@@ -525,6 +544,8 @@ def __init__(self, working_dir=None):
525544
It is meant to be the working tree directory if available, or the
526545
.git directory in case of bare repositories."""
527546
super(Git, self).__init__()
547+
if working_dir == None:
548+
working_dir = '.'
528549
self._working_dir = expand_path(working_dir)
529550
self._git_options = ()
530551
self._persistent_git_options = []
@@ -638,7 +659,7 @@ def execute(self, command,
638659
639660
:param env:
640661
A dictionary of environment variables to be passed to `subprocess.Popen`.
641-
662+
642663
:param max_chunk_size:
643664
Maximum number of bytes in one chunk of data passed to the output_stream in
644665
one invocation of write() method. If the given number is not positive then

git/config.py

Lines changed: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,51 @@ def __exit__(self, exception_type, exception_value, traceback):
146146
self._config.__exit__(exception_type, exception_value, traceback)
147147

148148

149+
class _OMD(OrderedDict):
150+
"""Ordered multi-dict."""
151+
152+
def __setitem__(self, key, value):
153+
super(_OMD, self).__setitem__(key, [value])
154+
155+
def add(self, key, value):
156+
if key not in self:
157+
super(_OMD, self).__setitem__(key, [value])
158+
return
159+
160+
super(_OMD, self).__getitem__(key).append(value)
161+
162+
def setall(self, key, values):
163+
super(_OMD, self).__setitem__(key, values)
164+
165+
def __getitem__(self, key):
166+
return super(_OMD, self).__getitem__(key)[-1]
167+
168+
def getlast(self, key):
169+
return super(_OMD, self).__getitem__(key)[-1]
170+
171+
def setlast(self, key, value):
172+
if key not in self:
173+
super(_OMD, self).__setitem__(key, [value])
174+
return
175+
176+
prior = super(_OMD, self).__getitem__(key)
177+
prior[-1] = value
178+
179+
def get(self, key, default=None):
180+
return super(_OMD, self).get(key, [default])[-1]
181+
182+
def getall(self, key):
183+
return super(_OMD, self).__getitem__(key)
184+
185+
def items(self):
186+
"""List of (key, last value for key)."""
187+
return [(k, self[k]) for k in self]
188+
189+
def items_all(self):
190+
"""List of (key, list of values for key)."""
191+
return [(k, self.getall(k)) for k in self]
192+
193+
149194
class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)):
150195

151196
"""Implements specifics required to read git style configuration files.
@@ -200,7 +245,7 @@ def __init__(self, file_or_files, read_only=True, merge_includes=True):
200245
contents into ours. This makes it impossible to write back an individual configuration file.
201246
Thus, if you want to modify a single configuration file, turn this off to leave the original
202247
dataset unaltered when reading it."""
203-
cp.RawConfigParser.__init__(self, dict_type=OrderedDict)
248+
cp.RawConfigParser.__init__(self, dict_type=_OMD)
204249

205250
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
206251
if not hasattr(self, '_proxies'):
@@ -348,7 +393,8 @@ def string_decode(v):
348393
is_multi_line = True
349394
optval = string_decode(optval[1:])
350395
# end handle multi-line
351-
cursect[optname] = optval
396+
# preserves multiple values for duplicate optnames
397+
cursect.add(optname, optval)
352398
else:
353399
# check if it's an option with no value - it's just ignored by git
354400
if not self.OPTVALUEONLY.match(line):
@@ -362,7 +408,8 @@ def string_decode(v):
362408
is_multi_line = False
363409
line = line[:-1]
364410
# end handle quotations
365-
cursect[optname] += string_decode(line)
411+
optval = cursect.getlast(optname)
412+
cursect.setlast(optname, optval + string_decode(line))
366413
# END parse section or option
367414
# END while reading
368415

@@ -442,9 +489,12 @@ def _write(self, fp):
442489
git compatible format"""
443490
def write_section(name, section_dict):
444491
fp.write(("[%s]\n" % name).encode(defenc))
445-
for (key, value) in section_dict.items():
446-
if key != "__name__":
447-
fp.write(("\t%s = %s\n" % (key, self._value_to_string(value).replace('\n', '\n\t'))).encode(defenc))
492+
for (key, values) in section_dict.items_all():
493+
if key == "__name__":
494+
continue
495+
496+
for v in values:
497+
fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace('\n', '\n\t'))).encode(defenc))
448498
# END if key is not __name__
449499
# END section writing
450500

@@ -457,6 +507,22 @@ def items(self, section_name):
457507
""":return: list((option, value), ...) pairs of all items in the given section"""
458508
return [(k, v) for k, v in super(GitConfigParser, self).items(section_name) if k != '__name__']
459509

510+
def items_all(self, section_name):
511+
""":return: list((option, [values...]), ...) pairs of all items in the given section"""
512+
rv = _OMD(self._defaults)
513+
514+
for k, vs in self._sections[section_name].items_all():
515+
if k == '__name__':
516+
continue
517+
518+
if k in rv and rv.getall(k) == vs:
519+
continue
520+
521+
for v in vs:
522+
rv.add(k, v)
523+
524+
return rv.items_all()
525+
460526
@needs_values
461527
def write(self):
462528
"""Write changes to our file, if there are changes at all
@@ -508,7 +574,11 @@ def read_only(self):
508574
return self._read_only
509575

510576
def get_value(self, section, option, default=None):
511-
"""
577+
"""Get an option's value.
578+
579+
If multiple values are specified for this option in the section, the
580+
last one specified is returned.
581+
512582
:param default:
513583
If not None, the given default value will be returned in case
514584
the option did not exist
@@ -523,6 +593,31 @@ def get_value(self, section, option, default=None):
523593
return default
524594
raise
525595

596+
return self._string_to_value(valuestr)
597+
598+
def get_values(self, section, option, default=None):
599+
"""Get an option's values.
600+
601+
If multiple values are specified for this option in the section, all are
602+
returned.
603+
604+
:param default:
605+
If not None, a list containing the given default value will be
606+
returned in case the option did not exist
607+
:return: a list of properly typed values, either int, float or string
608+
609+
:raise TypeError: in case the value could not be understood
610+
Otherwise the exceptions known to the ConfigParser will be raised."""
611+
try:
612+
lst = self._sections[section].getall(option)
613+
except Exception:
614+
if default is not None:
615+
return [default]
616+
raise
617+
618+
return [self._string_to_value(valuestr) for valuestr in lst]
619+
620+
def _string_to_value(self, valuestr):
526621
types = (int, float)
527622
for numtype in types:
528623
try:
@@ -545,7 +640,9 @@ def get_value(self, section, option, default=None):
545640
return True
546641

547642
if not isinstance(valuestr, string_types):
548-
raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr)
643+
raise TypeError(
644+
"Invalid value type: only int, long, float and str are allowed",
645+
valuestr)
549646

550647
return valuestr
551648

@@ -572,6 +669,25 @@ def set_value(self, section, option, value):
572669
self.set(section, option, self._value_to_string(value))
573670
return self
574671

672+
@needs_values
673+
@set_dirty_and_flush_changes
674+
def add_value(self, section, option, value):
675+
"""Adds a value for the given option in section.
676+
It will create the section if required, and will not throw as opposed to the default
677+
ConfigParser 'set' method. The value becomes the new value of the option as returned
678+
by 'get_value', and appends to the list of values returned by 'get_values`'.
679+
680+
:param section: Name of the section in which the option resides or should reside
681+
:param option: Name of the option
682+
683+
:param value: Value to add to option. It must be a string or convertible
684+
to a string
685+
:return: this instance"""
686+
if not self.has_section(section):
687+
self.add_section(section)
688+
self._sections[section].add(option, self._value_to_string(value))
689+
return self
690+
575691
def rename_section(self, section, new_name):
576692
"""rename the given section to new_name
577693
:raise ValueError: if section doesn't exit
@@ -584,8 +700,9 @@ def rename_section(self, section, new_name):
584700
raise ValueError("Destination section '%s' already exists" % new_name)
585701

586702
super(GitConfigParser, self).add_section(new_name)
587-
for k, v in self.items(section):
588-
self.set(new_name, k, self._value_to_string(v))
703+
new_section = self._sections[new_name]
704+
for k, vs in self.items_all(section):
705+
new_section.setall(k, vs)
589706
# end for each value to copy
590707

591708
# This call writes back the changes, which is why we don't have the respective decorator

0 commit comments

Comments
 (0)