New upstream version 2.1.13
TANIGUCHI Takaki
4 years ago
26 | 26 | -Charles Bouchard-Légaré <cblegare.atl _at_ ntis.ca> |
27 | 27 | -Yaroslav Halchenko <debian _at_ onerussian.com> |
28 | 28 | -Tim Swast <swast _at_ google.com> |
29 | -William Luc Ritchie | |
30 | -David Host <hostdm _at_ outlook.com> | |
31 | -A. Jesse Jiryu Davis <jesse _at_ emptysquare.net> | |
32 | -Steven Whitman <ninloot _at_ gmail.com> | |
33 | -Stefan Stancu <stefan.stancu _at_ gmail.com> | |
34 | -César Izurieta <cesar _at_ caih.org> | |
29 | 35 | |
30 | 36 | Portions derived from other open source works and are clearly marked. |
0 | ### How to contribute | |
1 | ||
2 | * [fork this project](https://github.com/gitpython-developers/GitPython/fork) on GitHub | |
3 | * For setting up the environment to run the self tests, look at `.travis.yml`. | |
4 | * Add yourself to AUTHORS.md and write your patch. **Write a test that fails unless your patch is present.** | |
5 | * Initiate a pull request | |
6 |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: GitPython |
2 | Version: 2.1.11 | |
2 | Version: 2.1.13 | |
3 | 3 | Summary: Python Git Library |
4 | 4 | Home-page: https://github.com/gitpython-developers/GitPython |
5 | 5 | Author: Sebastian Thiel, Michael Trier |
22 | 22 | Classifier: Programming Language :: Python :: 3.4 |
23 | 23 | Classifier: Programming Language :: Python :: 3.5 |
24 | 24 | Classifier: Programming Language :: Python :: 3.6 |
25 | Requires: gitdb2 (>=2.0.0) | |
25 | Classifier: Programming Language :: Python :: 3.7 | |
26 | 26 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* |
0 | 0 | AUTHORS |
1 | 1 | CHANGES |
2 | CONTRIBUTING.md | |
2 | 3 | LICENSE |
3 | 4 | MANIFEST.in |
4 | 5 | README.md |
6 | 7 | requirements.txt |
7 | 8 | setup.cfg |
8 | 9 | setup.py |
10 | test-requirements.txt | |
9 | 11 | GitPython.egg-info/PKG-INFO |
10 | 12 | GitPython.egg-info/SOURCES.txt |
11 | 13 | GitPython.egg-info/dependency_links.txt |
14 | 16 | GitPython.egg-info/top_level.txt |
15 | 17 | doc/.gitignore |
16 | 18 | doc/Makefile |
19 | doc/requirements.txt | |
17 | 20 | doc/source/changes.rst |
18 | 21 | doc/source/conf.py |
19 | 22 | doc/source/index.rst |
115 | 118 | git/test/fixtures/git_config |
116 | 119 | git/test/fixtures/git_config-inc.cfg |
117 | 120 | git/test/fixtures/git_config_global |
121 | git/test/fixtures/git_config_multiple | |
118 | 122 | git/test/fixtures/git_config_with_comments |
119 | 123 | git/test/fixtures/git_config_with_empty_value |
120 | 124 | git/test/fixtures/git_file |
1 | 1 | include LICENSE |
2 | 2 | include CHANGES |
3 | 3 | include AUTHORS |
4 | include README | |
4 | include CONTRIBUTING.md | |
5 | include README.md | |
5 | 6 | include requirements.txt |
7 | include test-requirements.txt | |
6 | 8 | |
7 | 9 | recursive-include doc * |
8 | 10 |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: GitPython |
2 | Version: 2.1.11 | |
2 | Version: 2.1.13 | |
3 | 3 | Summary: Python Git Library |
4 | 4 | Home-page: https://github.com/gitpython-developers/GitPython |
5 | 5 | Author: Sebastian Thiel, Michael Trier |
22 | 22 | Classifier: Programming Language :: Python :: 3.4 |
23 | 23 | Classifier: Programming Language :: Python :: 3.5 |
24 | 24 | Classifier: Programming Language :: Python :: 3.6 |
25 | Requires: gitdb2 (>=2.0.0) | |
25 | Classifier: Programming Language :: Python :: 3.7 | |
26 | 26 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* |
18 | 18 | the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable. |
19 | 19 | |
20 | 20 | * Git (1.7.x or newer) |
21 | * Python 2.7 to 3.6. | |
21 | * Python 2.7 to 3.7. | |
22 | 22 | |
23 | 23 | The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`. |
24 | 24 | The installer takes care of installing them for you. |
177 | 177 | * [PyJFuzz](https://github.com/mseclab/PyJFuzz) |
178 | 178 | * [Loki](https://github.com/Neo23x0/Loki) |
179 | 179 | * [Omniwallet](https://github.com/OmniLayer/omniwallet) |
180 | * [GitViper](https://github.com/BeayemX/GitViper) | |
180 | 181 | |
181 | 182 | ### LICENSE |
182 | 183 | |
190 | 191 | [![Code Climate](https://codeclimate.com/github/gitpython-developers/GitPython/badges/gpa.svg)](https://codeclimate.com/github/gitpython-developers/GitPython) |
191 | 192 | [![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable) |
192 | 193 | [![Stories in Ready](https://badge.waffle.io/gitpython-developers/GitPython.png?label=ready&title=Ready)](https://waffle.io/gitpython-developers/GitPython) |
194 | [![Packaging status](https://repology.org/badge/tiny-repos/python:gitpython.svg)](https://repology.org/metapackage/python:gitpython/versions) | |
193 | 195 | [![Throughput Graph](https://graphs.waffle.io/gitpython-developers/GitPython/throughput.svg)](https://waffle.io/gitpython-developers/GitPython/metrics/throughput) |
196 | ||
194 | 197 | |
195 | 198 | Now that there seems to be a massive user base, this should be motivation enough to let git-python |
196 | 199 | return to a proper state, which means |
0 | 0 | ========= |
1 | 1 | Changelog |
2 | 2 | ========= |
3 | ||
4 | 2.1.13 - Bring back Python 2.7 support | |
5 | ====================================== | |
6 | ||
7 | My apologies for any inconvenience this may have caused. Following semver, backward incompatible changes | |
8 | will be introduced in a minor version. | |
9 | ||
10 | 2.1.12 - Bugfixes and Features | |
11 | ============================== | |
12 | ||
13 | * Multi-value support and interface improvements for Git configuration. Thanks to A. Jesse Jiryu Davis. | |
14 | ||
15 | see the following for (most) details: | |
16 | https://github.com/gitpython-developers/gitpython/milestone/27?closed=1 | |
17 | ||
18 | or run have a look at the difference between tags v2.1.11 and v2.1.12: | |
19 | https://github.com/gitpython-developers/GitPython/compare/2.1.11...2.1.12 | |
3 | 20 | |
4 | 21 | 2.1.11 - Bugfixes |
5 | 22 | ================= |
29 | 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] |
30 | 30 | |
31 | 31 | # Add any paths that contain templates here, relative to this directory. |
32 | templates_path = ['.templates'] | |
32 | templates_path = [] | |
33 | 33 | |
34 | 34 | # The suffix of source filenames. |
35 | 35 | source_suffix = '.rst' |
93 | 93 | # Options for HTML output |
94 | 94 | # ----------------------- |
95 | 95 | |
96 | html_theme = 'sphinx_rtd_theme' | |
96 | 97 | html_theme_options = { |
97 | 98 | } |
98 | ||
99 | # The style sheet to use for HTML and HTML Help pages. A file of that name | |
100 | # must exist either in Sphinx' static/ path, or in one of the custom paths | |
101 | # given in html_static_path. | |
102 | html_style = 'default.css' | |
103 | 99 | |
104 | 100 | # The name for this set of Sphinx documents. If None, it defaults to |
105 | 101 | # "<project> v<release> documentation". |
120 | 116 | # Add any paths that contain custom static files (such as style sheets) here, |
121 | 117 | # relative to this directory. They are copied after the builtin static files, |
122 | 118 | # so a file named "default.css" will overwrite the builtin "default.css". |
123 | html_static_path = ['.static'] | |
119 | html_static_path = [] | |
124 | 120 | |
125 | 121 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |
126 | 122 | # using the given strftime format. |
103 | 103 | $ cd git-python |
104 | 104 | $ git submodule update --init --recursive |
105 | 105 | |
106 | Finally verify the installation by running the `nose powered <http://code.google.com/p/python-nose/>`_ unit tests:: | |
106 | Finally verify the installation by running the `nose powered <https://github.com/nose-devs/nose>`_ unit tests:: | |
107 | 107 | |
108 | 108 | $ nosetests |
109 | 109 |
1 | 1 | |
2 | 2 | API Reference |
3 | 3 | ============= |
4 | ||
5 | Version | |
6 | ------- | |
7 | ||
8 | .. py:data:: git.__version__ | |
9 | ||
10 | Current GitPython version. | |
4 | 11 | |
5 | 12 | Objects.Base |
6 | 13 | ------------ |
11 | 11 | import os.path as osp |
12 | 12 | |
13 | 13 | |
14 | __version__ = '2.1.11' | |
14 | __version__ = '2.1.13' | |
15 | 15 | |
16 | 16 | |
17 | 17 | #{ Initialization |
18 | 18 | def _init_externals(): |
19 | 19 | """Initialize external projects by putting them into the path""" |
20 | if __version__ == '2.1.11': | |
20 | if __version__ == '2.1.13': | |
21 | 21 | sys.path.insert(0, osp.join(osp.dirname(__file__), 'ext', 'gitdb')) |
22 | 22 | |
23 | 23 | try: |
42 | 42 | stream_copy, |
43 | 43 | ) |
44 | 44 | |
45 | try: | |
46 | PermissionError | |
47 | except NameError: # Python < 3.3 | |
48 | PermissionError = OSError | |
45 | 49 | |
46 | 50 | execute_kwargs = {'istream', 'with_extended_output', |
47 | 51 | 'with_exceptions', 'as_process', 'stdout_as_string', |
77 | 81 | Set it to False if `universal_newline == True` (then streams are in text-mode) |
78 | 82 | or if decoding must happen later (i.e. for Diffs). |
79 | 83 | """ |
80 | # Use 2 "pupm" threads and wait for both to finish. | |
84 | # Use 2 "pump" threads and wait for both to finish. | |
81 | 85 | def pump_stream(cmdline, name, stream, is_decode, handler): |
82 | 86 | try: |
83 | 87 | for line in stream: |
210 | 214 | |
211 | 215 | # test if the new git executable path is valid |
212 | 216 | |
213 | if sys.version_info < (3,): | |
214 | # - a GitCommandNotFound error is spawned by ourselves | |
215 | # - a OSError is spawned if the git executable provided | |
216 | # cannot be executed for whatever reason | |
217 | exceptions = (GitCommandNotFound, OSError) | |
218 | else: | |
219 | # - a GitCommandNotFound error is spawned by ourselves | |
220 | # - a PermissionError is spawned if the git executable provided | |
221 | # cannot be executed for whatever reason | |
222 | exceptions = (GitCommandNotFound, PermissionError) | |
223 | ||
217 | # - a GitCommandNotFound error is spawned by ourselves | |
218 | # - a PermissionError is spawned if the git executable provided | |
219 | # cannot be executed for whatever reason | |
220 | ||
224 | 221 | has_git = False |
225 | 222 | try: |
226 | 223 | cls().version() |
227 | 224 | has_git = True |
228 | except exceptions: | |
225 | except (GitCommandNotFound, PermissionError): | |
229 | 226 | pass |
230 | 227 | |
231 | 228 | # warn or raise exception if test failed |
717 | 714 | stdout_sink = (PIPE |
718 | 715 | if with_stdout |
719 | 716 | else getattr(subprocess, 'DEVNULL', None) or open(os.devnull, 'wb')) |
720 | log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s)", | |
721 | command, cwd, universal_newlines, shell) | |
717 | istream_ok = "None" | |
718 | if istream: | |
719 | istream_ok = "<valid stream>" | |
720 | log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)", | |
721 | command, cwd, universal_newlines, shell, istream_ok) | |
722 | 722 | try: |
723 | 723 | proc = Popen(command, |
724 | 724 | env=env, |
793 | 793 | else: |
794 | 794 | max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE |
795 | 795 | stream_copy(proc.stdout, output_stream, max_chunk_size) |
796 | stdout_value = output_stream | |
796 | stdout_value = proc.stdout.read() | |
797 | 797 | stderr_value = proc.stderr.read() |
798 | 798 | # strip trailing "\n" |
799 | 799 | if stderr_value.endswith(b"\n"): |
884 | 884 | if len(name) == 1: |
885 | 885 | if value is True: |
886 | 886 | return ["-%s" % name] |
887 | elif type(value) is not bool: | |
887 | elif value not in (False, None): | |
888 | 888 | if split_single_char_options: |
889 | 889 | return ["-%s" % name, "%s" % value] |
890 | 890 | else: |
892 | 892 | else: |
893 | 893 | if value is True: |
894 | 894 | return ["--%s" % dashify(name)] |
895 | elif type(value) is not bool: | |
895 | elif value not in (False, None): | |
896 | 896 | return ["--%s=%s" % (dashify(name), value)] |
897 | 897 | return [] |
898 | 898 |
145 | 145 | self._config.__exit__(exception_type, exception_value, traceback) |
146 | 146 | |
147 | 147 | |
148 | class _OMD(OrderedDict): | |
149 | """Ordered multi-dict.""" | |
150 | ||
151 | def __setitem__(self, key, value): | |
152 | super(_OMD, self).__setitem__(key, [value]) | |
153 | ||
154 | def add(self, key, value): | |
155 | if key not in self: | |
156 | super(_OMD, self).__setitem__(key, [value]) | |
157 | return | |
158 | ||
159 | super(_OMD, self).__getitem__(key).append(value) | |
160 | ||
161 | def setall(self, key, values): | |
162 | super(_OMD, self).__setitem__(key, values) | |
163 | ||
164 | def __getitem__(self, key): | |
165 | return super(_OMD, self).__getitem__(key)[-1] | |
166 | ||
167 | def getlast(self, key): | |
168 | return super(_OMD, self).__getitem__(key)[-1] | |
169 | ||
170 | def setlast(self, key, value): | |
171 | if key not in self: | |
172 | super(_OMD, self).__setitem__(key, [value]) | |
173 | return | |
174 | ||
175 | prior = super(_OMD, self).__getitem__(key) | |
176 | prior[-1] = value | |
177 | ||
178 | def get(self, key, default=None): | |
179 | return super(_OMD, self).get(key, [default])[-1] | |
180 | ||
181 | def getall(self, key): | |
182 | return super(_OMD, self).__getitem__(key) | |
183 | ||
184 | def items(self): | |
185 | """List of (key, last value for key).""" | |
186 | return [(k, self[k]) for k in self] | |
187 | ||
188 | def items_all(self): | |
189 | """List of (key, list of values for key).""" | |
190 | return [(k, self.getall(k)) for k in self] | |
191 | ||
192 | ||
148 | 193 | class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)): |
149 | 194 | |
150 | 195 | """Implements specifics required to read git style configuration files. |
199 | 244 | contents into ours. This makes it impossible to write back an individual configuration file. |
200 | 245 | Thus, if you want to modify a single configuration file, turn this off to leave the original |
201 | 246 | dataset unaltered when reading it.""" |
202 | cp.RawConfigParser.__init__(self, dict_type=OrderedDict) | |
247 | cp.RawConfigParser.__init__(self, dict_type=_OMD) | |
203 | 248 | |
204 | 249 | # Used in python 3, needs to stay in sync with sections for underlying implementation to work |
205 | 250 | if not hasattr(self, '_proxies'): |
347 | 392 | is_multi_line = True |
348 | 393 | optval = string_decode(optval[1:]) |
349 | 394 | # end handle multi-line |
350 | cursect[optname] = optval | |
395 | # preserves multiple values for duplicate optnames | |
396 | cursect.add(optname, optval) | |
351 | 397 | else: |
352 | 398 | # check if it's an option with no value - it's just ignored by git |
353 | 399 | if not self.OPTVALUEONLY.match(line): |
361 | 407 | is_multi_line = False |
362 | 408 | line = line[:-1] |
363 | 409 | # end handle quotations |
364 | cursect[optname] += string_decode(line) | |
410 | optval = cursect.getlast(optname) | |
411 | cursect.setlast(optname, optval + string_decode(line)) | |
365 | 412 | # END parse section or option |
366 | 413 | # END while reading |
367 | 414 | |
441 | 488 | git compatible format""" |
442 | 489 | def write_section(name, section_dict): |
443 | 490 | fp.write(("[%s]\n" % name).encode(defenc)) |
444 | for (key, value) in section_dict.items(): | |
445 | if key != "__name__": | |
446 | fp.write(("\t%s = %s\n" % (key, self._value_to_string(value).replace('\n', '\n\t'))).encode(defenc)) | |
491 | for (key, values) in section_dict.items_all(): | |
492 | if key == "__name__": | |
493 | continue | |
494 | ||
495 | for v in values: | |
496 | fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace('\n', '\n\t'))).encode(defenc)) | |
447 | 497 | # END if key is not __name__ |
448 | 498 | # END section writing |
449 | 499 | |
455 | 505 | def items(self, section_name): |
456 | 506 | """:return: list((option, value), ...) pairs of all items in the given section""" |
457 | 507 | return [(k, v) for k, v in super(GitConfigParser, self).items(section_name) if k != '__name__'] |
508 | ||
509 | def items_all(self, section_name): | |
510 | """:return: list((option, [values...]), ...) pairs of all items in the given section""" | |
511 | rv = _OMD(self._defaults) | |
512 | ||
513 | for k, vs in self._sections[section_name].items_all(): | |
514 | if k == '__name__': | |
515 | continue | |
516 | ||
517 | if k in rv and rv.getall(k) == vs: | |
518 | continue | |
519 | ||
520 | for v in vs: | |
521 | rv.add(k, v) | |
522 | ||
523 | return rv.items_all() | |
458 | 524 | |
459 | 525 | @needs_values |
460 | 526 | def write(self): |
507 | 573 | return self._read_only |
508 | 574 | |
509 | 575 | def get_value(self, section, option, default=None): |
510 | """ | |
576 | """Get an option's value. | |
577 | ||
578 | If multiple values are specified for this option in the section, the | |
579 | last one specified is returned. | |
580 | ||
511 | 581 | :param default: |
512 | 582 | If not None, the given default value will be returned in case |
513 | 583 | the option did not exist |
522 | 592 | return default |
523 | 593 | raise |
524 | 594 | |
595 | return self._string_to_value(valuestr) | |
596 | ||
597 | def get_values(self, section, option, default=None): | |
598 | """Get an option's values. | |
599 | ||
600 | If multiple values are specified for this option in the section, all are | |
601 | returned. | |
602 | ||
603 | :param default: | |
604 | If not None, a list containing the given default value will be | |
605 | returned in case the option did not exist | |
606 | :return: a list of properly typed values, either int, float or string | |
607 | ||
608 | :raise TypeError: in case the value could not be understood | |
609 | Otherwise the exceptions known to the ConfigParser will be raised.""" | |
610 | try: | |
611 | lst = self._sections[section].getall(option) | |
612 | except Exception: | |
613 | if default is not None: | |
614 | return [default] | |
615 | raise | |
616 | ||
617 | return [self._string_to_value(valuestr) for valuestr in lst] | |
618 | ||
619 | def _string_to_value(self, valuestr): | |
525 | 620 | types = (int, float) |
526 | 621 | for numtype in types: |
527 | 622 | try: |
544 | 639 | return True |
545 | 640 | |
546 | 641 | if not isinstance(valuestr, string_types): |
547 | raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr) | |
642 | raise TypeError( | |
643 | "Invalid value type: only int, long, float and str are allowed", | |
644 | valuestr) | |
548 | 645 | |
549 | 646 | return valuestr |
550 | 647 | |
569 | 666 | if not self.has_section(section): |
570 | 667 | self.add_section(section) |
571 | 668 | self.set(section, option, self._value_to_string(value)) |
669 | return self | |
670 | ||
671 | @needs_values | |
672 | @set_dirty_and_flush_changes | |
673 | def add_value(self, section, option, value): | |
674 | """Adds a value for the given option in section. | |
675 | It will create the section if required, and will not throw as opposed to the default | |
676 | ConfigParser 'set' method. The value becomes the new value of the option as returned | |
677 | by 'get_value', and appends to the list of values returned by 'get_values`'. | |
678 | ||
679 | :param section: Name of the section in which the option resides or should reside | |
680 | :param option: Name of the option | |
681 | ||
682 | :param value: Value to add to option. It must be a string or convertible | |
683 | to a string | |
684 | :return: this instance""" | |
685 | if not self.has_section(section): | |
686 | self.add_section(section) | |
687 | self._sections[section].add(option, self._value_to_string(value)) | |
572 | 688 | return self |
573 | 689 | |
574 | 690 | def rename_section(self, section, new_name): |
583 | 699 | raise ValueError("Destination section '%s' already exists" % new_name) |
584 | 700 | |
585 | 701 | super(GitConfigParser, self).add_section(new_name) |
586 | for k, v in self.items(section): | |
587 | self.set(new_name, k, self._value_to_string(v)) | |
702 | new_section = self._sections[new_name] | |
703 | for k, vs in self.items_all(section): | |
704 | new_section.setall(k, vs) | |
588 | 705 | # end for each value to copy |
589 | 706 | |
590 | 707 | # This call writes back the changes, which is why we don't have the respective decorator |
383 | 383 | @property |
384 | 384 | def renamed_file(self): |
385 | 385 | """:returns: True if the blob of our diff has been renamed |
386 | :note: This property is deprecated, please use ``renamed_file`` instead. | |
387 | 386 | """ |
388 | 387 | return self.rename_from != self.rename_to |
389 | 388 |
80 | 80 | :param tree: Tree |
81 | 81 | Tree object |
82 | 82 | :param author: Actor |
83 | is the author string ( will be implicitly converted into an Actor object ) | |
83 | is the author Actor object | |
84 | 84 | :param authored_date: int_seconds_since_epoch |
85 | 85 | is the authored DateTime - use time.gmtime() to convert it into a |
86 | 86 | different format |
1159 | 1159 | try: |
1160 | 1160 | parser = cls._config_parser(repo, pc, read_only=True) |
1161 | 1161 | except IOError: |
1162 | raise StopIteration | |
1162 | return | |
1163 | 1163 | # END handle empty iterator |
1164 | 1164 | |
1165 | 1165 | rt = pc.tree # root tree |
1183 | 1183 | entry = index.entries[index.entry_key(p, 0)] |
1184 | 1184 | sm = Submodule(repo, entry.binsha, entry.mode, entry.path) |
1185 | 1185 | except KeyError: |
1186 | raise InvalidGitRepositoryError( | |
1187 | "Gitmodule path %r did not exist in revision of parent commit %s" % (p, parent_commit)) | |
1186 | # The submodule doesn't exist, probably it wasn't | |
1187 | # removed from the .gitmodules file. | |
1188 | continue | |
1188 | 1189 | # END handle keyerror |
1189 | 1190 | # END handle critical error |
1190 | 1191 |
58 | 58 | |
59 | 59 | self.tag = lines[2][4:] # tag <tag name> |
60 | 60 | |
61 | tagger_info = lines[3] # tagger <actor> <date> | |
62 | self.tagger, self.tagged_date, self.tagger_tz_offset = parse_actor_and_date(tagger_info) | |
61 | if len(lines) > 3: | |
62 | tagger_info = lines[3] # tagger <actor> <date> | |
63 | self.tagger, self.tagged_date, self.tagger_tz_offset = parse_actor_and_date(tagger_info) | |
63 | 64 | |
64 | 65 | # line 4 empty - it could mark the beginning of the next header |
65 | 66 | # in case there really is no message, it would not exist. Otherwise |
543 | 543 | except GitCommandError as ex: |
544 | 544 | if any(msg in str(ex) for msg in ['correct access rights', 'cannot run ssh']): |
545 | 545 | # If ssh is not setup to access this repository, see issue 694 |
546 | result = Git().execute( | |
547 | ['git', 'config', '--get', 'remote.%s.url' % self.name] | |
548 | ) | |
549 | yield result | |
546 | remote_details = self.repo.git.config('--get-all', 'remote.%s.url' % self.name) | |
547 | for line in remote_details.split('\n'): | |
548 | yield line | |
550 | 549 | else: |
551 | 550 | raise ex |
552 | 551 | else: |
694 | 693 | msg += "Will ignore extra progress lines or fetch head lines." |
695 | 694 | msg %= (l_fil, l_fhi) |
696 | 695 | log.debug(msg) |
696 | log.debug("info lines: " + str(fetch_info_lines)) | |
697 | log.debug("head info : " + str(fetch_head_info)) | |
697 | 698 | if l_fil < l_fhi: |
698 | 699 | fetch_head_info = fetch_head_info[:l_fil] |
699 | 700 | else: |
96 | 96 | repo = Repo("/Users/mtrier/Development/git-python.git") |
97 | 97 | repo = Repo("~/Development/git-python.git") |
98 | 98 | repo = Repo("$REPOSITORIES/Development/git-python.git") |
99 | repo = Repo("C:\\Users\\mtrier\\Development\\git-python\\.git") | |
99 | 100 | |
100 | 101 | - In *Cygwin*, path may be a `'cygdrive/...'` prefixed path. |
101 | 102 | - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also evals to false, |
139 | 140 | # removed. It's just cleaner. |
140 | 141 | if is_git_dir(curpath): |
141 | 142 | self.git_dir = curpath |
142 | self._working_tree_dir = os.getenv('GIT_WORK_TREE', os.path.dirname(self.git_dir)) | |
143 | # from man git-config : core.worktree | |
144 | # Set the path to the root of the working tree. If GIT_COMMON_DIR environment | |
145 | # variable is set, core.worktree is ignored and not used for determining the | |
146 | # root of working tree. This can be overridden by the GIT_WORK_TREE environment | |
147 | # variable. The value can be an absolute path or relative to the path to the .git | |
148 | # directory, which is either specified by GIT_DIR, or automatically discovered. | |
149 | # If GIT_DIR is specified but none of GIT_WORK_TREE and core.worktree is specified, | |
150 | # the current working directory is regarded as the top level of your working tree. | |
151 | self._working_tree_dir = os.path.dirname(self.git_dir) | |
152 | if os.environ.get('GIT_COMMON_DIR') is None: | |
153 | gitconf = self.config_reader("repository") | |
154 | if gitconf.has_option('core', 'worktree'): | |
155 | self._working_tree_dir = gitconf.get('core', 'worktree') | |
156 | if 'GIT_WORK_TREE' in os.environ: | |
157 | self._working_tree_dir = os.getenv('GIT_WORK_TREE') | |
143 | 158 | break |
144 | 159 | |
145 | 160 | dotgit = osp.join(curpath, '.git') |
415 | 430 | elif config_level == "global": |
416 | 431 | return osp.normpath(osp.expanduser("~/.gitconfig")) |
417 | 432 | elif config_level == "repository": |
418 | return osp.normpath(osp.join(self.git_dir, "config")) | |
433 | return osp.normpath(osp.join(self._common_dir or self.git_dir, "config")) | |
419 | 434 | |
420 | 435 | raise ValueError("Invalid configuration level: %r" % config_level) |
421 | 436 | |
543 | 558 | return res |
544 | 559 | |
545 | 560 | def is_ancestor(self, ancestor_rev, rev): |
546 | """Check if a commit is an ancestor of another | |
561 | """Check if a commit is an ancestor of another | |
547 | 562 | |
548 | 563 | :param ancestor_rev: Rev which should be an ancestor |
549 | 564 | :param rev: Rev to test against ancestor_rev |
550 | :return: ``True``, ancestor_rev is an accestor to rev. | |
565 | :return: ``True``, ancestor_rev is an ancestor to rev. | |
551 | 566 | """ |
552 | 567 | try: |
553 | 568 | self.git.merge_base(ancestor_rev, rev, is_ancestor=True) |
713 | 728 | |
714 | 729 | stream = (line for line in data.split(b'\n') if line) |
715 | 730 | while True: |
716 | line = next(stream) # when exhausted, causes a StopIteration, terminating this function | |
731 | try: | |
732 | line = next(stream) # when exhausted, causes a StopIteration, terminating this function | |
733 | except StopIteration: | |
734 | return | |
717 | 735 | hexsha, orig_lineno, lineno, num_lines = line.split() |
718 | 736 | lineno = int(lineno) |
719 | 737 | num_lines = int(num_lines) |
723 | 741 | # for this commit |
724 | 742 | props = {} |
725 | 743 | while True: |
726 | line = next(stream) | |
744 | try: | |
745 | line = next(stream) | |
746 | except StopIteration: | |
747 | return | |
727 | 748 | if line == b'boundary': |
728 | 749 | # "boundary" indicates a root commit and occurs |
729 | 750 | # instead of the "previous" tag |
748 | 769 | # Discard all lines until we find "filename" which is |
749 | 770 | # guaranteed to be the last line |
750 | 771 | while True: |
751 | line = next(stream) # will fail if we reach the EOF unexpectedly | |
772 | try: | |
773 | line = next(stream) # will fail if we reach the EOF unexpectedly | |
774 | except StopIteration: | |
775 | return | |
752 | 776 | tag, value = line.split(b' ', 1) |
753 | 777 | if tag == b'filename': |
754 | 778 | orig_filename = value |
906 | 930 | return cls(path, odbt=odbt) |
907 | 931 | |
908 | 932 | @classmethod |
909 | def _clone(cls, git, url, path, odb_default_type, progress, **kwargs): | |
933 | def _clone(cls, git, url, path, odb_default_type, progress, multi_options=None, **kwargs): | |
910 | 934 | if progress is not None: |
911 | 935 | progress = to_progress_instance(progress) |
912 | 936 | |
928 | 952 | sep_dir = kwargs.get('separate_git_dir') |
929 | 953 | if sep_dir: |
930 | 954 | kwargs['separate_git_dir'] = Git.polish_url(sep_dir) |
931 | proc = git.clone(Git.polish_url(url), clone_path, with_extended_output=True, as_process=True, | |
955 | multi = None | |
956 | if multi_options: | |
957 | multi = ' '.join(multi_options).split(' ') | |
958 | proc = git.clone(multi, Git.polish_url(url), clone_path, with_extended_output=True, as_process=True, | |
932 | 959 | v=True, universal_newlines=True, **add_progress(kwargs, git, progress)) |
933 | 960 | if progress: |
934 | 961 | handle_process_output(proc, None, progress.new_message_handler(), finalize_process, decode_streams=False) |
958 | 985 | # END handle remote repo |
959 | 986 | return repo |
960 | 987 | |
961 | def clone(self, path, progress=None, **kwargs): | |
988 | def clone(self, path, progress=None, multi_options=None, **kwargs): | |
962 | 989 | """Create a clone from this repository. |
963 | 990 | |
964 | 991 | :param path: is the full path of the new repo (traditionally ends with ./<name>.git). |
965 | 992 | :param progress: See 'git.remote.Remote.push'. |
993 | :param multi_options: A list of Clone options that can be provided multiple times. One | |
994 | option per list item which is passed exactly as specified to clone. | |
995 | For example ['--config core.filemode=false', '--config core.ignorecase', | |
996 | '--recurse-submodule=repo1_path', '--recurse-submodule=repo2_path'] | |
966 | 997 | :param kwargs: |
967 | 998 | * odbt = ObjectDatabase Type, allowing to determine the object database |
968 | 999 | implementation used by the returned Repo instance |
969 | 1000 | * All remaining keyword arguments are given to the git-clone command |
970 | 1001 | |
971 | 1002 | :return: ``git.Repo`` (the newly cloned repo)""" |
972 | return self._clone(self.git, self.common_dir, path, type(self.odb), progress, **kwargs) | |
1003 | return self._clone(self.git, self.common_dir, path, type(self.odb), progress, multi_options, **kwargs) | |
973 | 1004 | |
974 | 1005 | @classmethod |
975 | def clone_from(cls, url, to_path, progress=None, env=None, **kwargs): | |
1006 | def clone_from(cls, url, to_path, progress=None, env=None, multi_options=None, **kwargs): | |
976 | 1007 | """Create a clone from the given URL |
977 | 1008 | |
978 | 1009 | :param url: valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS |
979 | 1010 | :param to_path: Path to which the repository should be cloned to |
980 | 1011 | :param progress: See 'git.remote.Remote.push'. |
981 | 1012 | :param env: Optional dictionary containing the desired environment variables. |
1013 | :param mutli_options: See ``clone`` method | |
982 | 1014 | :param kwargs: see the ``clone`` method |
983 | 1015 | :return: Repo instance pointing to the cloned directory""" |
984 | 1016 | git = Git(os.getcwd()) |
985 | 1017 | if env is not None: |
986 | 1018 | git.update_environment(**env) |
987 | return cls._clone(git, url, to_path, GitCmdObjectDB, progress, **kwargs) | |
1019 | return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) | |
988 | 1020 | |
989 | 1021 | def archive(self, ostream, treeish=None, prefix=None, **kwargs): |
990 | 1022 | """Archive the tree at the given revision. |
85 | 85 | ## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes. |
86 | 86 | path = decygpath(path) |
87 | 87 | if not osp.isabs(path): |
88 | path = osp.join(osp.dirname(d), path) | |
88 | path = osp.normpath(osp.join(osp.dirname(d), path)) | |
89 | 89 | return find_submodule_git_dir(path) |
90 | 90 | # end handle exception |
91 | 91 | return None |
0 | [section0] | |
1 | option0 = value0 | |
2 | ||
3 | [section1] | |
4 | option1 = value1a | |
5 | option1 = value1b | |
6 | other_option1 = other_value1 |
10 | 10 | GitConfigParser |
11 | 11 | ) |
12 | 12 | from git.compat import string_types |
13 | from git.config import cp | |
13 | from git.config import _OMD, cp | |
14 | 14 | from git.test.lib import ( |
15 | 15 | TestCase, |
16 | 16 | fixture_path, |
264 | 264 | |
265 | 265 | with self.assertRaises(cp.NoOptionError): |
266 | 266 | cr.get_value('color', 'ui') |
267 | ||
268 | def test_multiple_values(self): | |
269 | file_obj = self._to_memcache(fixture_path('git_config_multiple')) | |
270 | with GitConfigParser(file_obj, read_only=False) as cw: | |
271 | self.assertEqual(cw.get('section0', 'option0'), 'value0') | |
272 | self.assertEqual(cw.get_values('section0', 'option0'), ['value0']) | |
273 | self.assertEqual(cw.items('section0'), [('option0', 'value0')]) | |
274 | ||
275 | # Where there are multiple values, "get" returns the last. | |
276 | self.assertEqual(cw.get('section1', 'option1'), 'value1b') | |
277 | self.assertEqual(cw.get_values('section1', 'option1'), | |
278 | ['value1a', 'value1b']) | |
279 | self.assertEqual(cw.items('section1'), | |
280 | [('option1', 'value1b'), | |
281 | ('other_option1', 'other_value1')]) | |
282 | self.assertEqual(cw.items_all('section1'), | |
283 | [('option1', ['value1a', 'value1b']), | |
284 | ('other_option1', ['other_value1'])]) | |
285 | with self.assertRaises(KeyError): | |
286 | cw.get_values('section1', 'missing') | |
287 | ||
288 | self.assertEqual(cw.get_values('section1', 'missing', 1), [1]) | |
289 | self.assertEqual(cw.get_values('section1', 'missing', 's'), ['s']) | |
290 | ||
291 | def test_multiple_values_rename(self): | |
292 | file_obj = self._to_memcache(fixture_path('git_config_multiple')) | |
293 | with GitConfigParser(file_obj, read_only=False) as cw: | |
294 | cw.rename_section('section1', 'section2') | |
295 | cw.write() | |
296 | file_obj.seek(0) | |
297 | cr = GitConfigParser(file_obj, read_only=True) | |
298 | self.assertEqual(cr.get_value('section2', 'option1'), 'value1b') | |
299 | self.assertEqual(cr.get_values('section2', 'option1'), | |
300 | ['value1a', 'value1b']) | |
301 | self.assertEqual(cr.items('section2'), | |
302 | [('option1', 'value1b'), | |
303 | ('other_option1', 'other_value1')]) | |
304 | self.assertEqual(cr.items_all('section2'), | |
305 | [('option1', ['value1a', 'value1b']), | |
306 | ('other_option1', ['other_value1'])]) | |
307 | ||
308 | def test_multiple_to_single(self): | |
309 | file_obj = self._to_memcache(fixture_path('git_config_multiple')) | |
310 | with GitConfigParser(file_obj, read_only=False) as cw: | |
311 | cw.set_value('section1', 'option1', 'value1c') | |
312 | ||
313 | cw.write() | |
314 | file_obj.seek(0) | |
315 | cr = GitConfigParser(file_obj, read_only=True) | |
316 | self.assertEqual(cr.get_value('section1', 'option1'), 'value1c') | |
317 | self.assertEqual(cr.get_values('section1', 'option1'), ['value1c']) | |
318 | self.assertEqual(cr.items('section1'), | |
319 | [('option1', 'value1c'), | |
320 | ('other_option1', 'other_value1')]) | |
321 | self.assertEqual(cr.items_all('section1'), | |
322 | [('option1', ['value1c']), | |
323 | ('other_option1', ['other_value1'])]) | |
324 | ||
325 | def test_single_to_multiple(self): | |
326 | file_obj = self._to_memcache(fixture_path('git_config_multiple')) | |
327 | with GitConfigParser(file_obj, read_only=False) as cw: | |
328 | cw.add_value('section1', 'other_option1', 'other_value1a') | |
329 | ||
330 | cw.write() | |
331 | file_obj.seek(0) | |
332 | cr = GitConfigParser(file_obj, read_only=True) | |
333 | self.assertEqual(cr.get_value('section1', 'option1'), 'value1b') | |
334 | self.assertEqual(cr.get_values('section1', 'option1'), | |
335 | ['value1a', 'value1b']) | |
336 | self.assertEqual(cr.get_value('section1', 'other_option1'), | |
337 | 'other_value1a') | |
338 | self.assertEqual(cr.get_values('section1', 'other_option1'), | |
339 | ['other_value1', 'other_value1a']) | |
340 | self.assertEqual(cr.items('section1'), | |
341 | [('option1', 'value1b'), | |
342 | ('other_option1', 'other_value1a')]) | |
343 | self.assertEqual( | |
344 | cr.items_all('section1'), | |
345 | [('option1', ['value1a', 'value1b']), | |
346 | ('other_option1', ['other_value1', 'other_value1a'])]) | |
347 | ||
348 | def test_add_to_multiple(self): | |
349 | file_obj = self._to_memcache(fixture_path('git_config_multiple')) | |
350 | with GitConfigParser(file_obj, read_only=False) as cw: | |
351 | cw.add_value('section1', 'option1', 'value1c') | |
352 | cw.write() | |
353 | file_obj.seek(0) | |
354 | cr = GitConfigParser(file_obj, read_only=True) | |
355 | self.assertEqual(cr.get_value('section1', 'option1'), 'value1c') | |
356 | self.assertEqual(cr.get_values('section1', 'option1'), | |
357 | ['value1a', 'value1b', 'value1c']) | |
358 | self.assertEqual(cr.items('section1'), | |
359 | [('option1', 'value1c'), | |
360 | ('other_option1', 'other_value1')]) | |
361 | self.assertEqual(cr.items_all('section1'), | |
362 | [('option1', ['value1a', 'value1b', 'value1c']), | |
363 | ('other_option1', ['other_value1'])]) | |
364 | ||
365 | def test_setlast(self): | |
366 | # Test directly, not covered by higher-level tests. | |
367 | omd = _OMD() | |
368 | omd.setlast('key', 'value1') | |
369 | self.assertEqual(omd['key'], 'value1') | |
370 | self.assertEqual(omd.getall('key'), ['value1']) | |
371 | omd.setlast('key', 'value2') | |
372 | self.assertEqual(omd['key'], 'value2') | |
373 | self.assertEqual(omd.getall('key'), ['value2']) |
6 | 6 | import os |
7 | 7 | import subprocess |
8 | 8 | import sys |
9 | from tempfile import TemporaryFile | |
9 | 10 | |
10 | 11 | from git import ( |
11 | 12 | Git, |
80 | 81 | def test_it_transforms_kwargs_into_git_command_arguments(self): |
81 | 82 | assert_equal(["-s"], self.git.transform_kwargs(**{'s': True})) |
82 | 83 | assert_equal(["-s", "5"], self.git.transform_kwargs(**{'s': 5})) |
84 | assert_equal([], self.git.transform_kwargs(**{'s': None})) | |
83 | 85 | |
84 | 86 | assert_equal(["--max-count"], self.git.transform_kwargs(**{'max_count': True})) |
85 | 87 | assert_equal(["--max-count=5"], self.git.transform_kwargs(**{'max_count': 5})) |
88 | assert_equal([], self.git.transform_kwargs(**{'max_count': None})) | |
86 | 89 | |
87 | 90 | # Multiple args are supported by using lists/tuples |
88 | 91 | assert_equal(["-L", "1-3", "-L", "12-18"], self.git.transform_kwargs(**{'L': ('1-3', '12-18')})) |
89 | assert_equal(["-C", "-C"], self.git.transform_kwargs(**{'C': [True, True]})) | |
92 | assert_equal(["-C", "-C"], self.git.transform_kwargs(**{'C': [True, True, None, False]})) | |
90 | 93 | |
91 | 94 | # order is undefined |
92 | 95 | res = self.git.transform_kwargs(**{'s': True, 't': True}) |
106 | 109 | # this_should_not_be_ignored=False implies it *should* be ignored |
107 | 110 | self.git.version(pass_this_kwarg=False) |
108 | 111 | assert_true("pass_this_kwarg" not in git.call_args[1]) |
112 | ||
113 | @raises(GitCommandError) | |
114 | def test_it_raises_proper_exception_with_output_stream(self): | |
115 | tmp_file = TemporaryFile() | |
116 | self.git.checkout('non-existent-branch', output_stream=tmp_file) | |
109 | 117 | |
110 | 118 | def test_it_accepts_environment_variables(self): |
111 | 119 | filename = fixture_path("ls_tree_empty") |
252 | 252 | self.assertEqual(tinfo.ref.commit, rtag.commit) |
253 | 253 | self.assertTrue(tinfo.flags & tinfo.NEW_TAG) |
254 | 254 | |
255 | # adjust tag commit | |
255 | # adjust the local tag commit | |
256 | 256 | Reference.set_object(rtag, rhead.commit.parents[0].parents[0]) |
257 | res = fetch_and_test(remote, tags=True) | |
257 | ||
258 | # as of git 2.20 one cannot clobber local tags that have changed without | |
259 | # specifying --force, and the test assumes you can clobber, so... | |
260 | force = None | |
261 | if rw_repo.git.version_info[:2] >= (2, 20): | |
262 | force = True | |
263 | res = fetch_and_test(remote, tags=True, force=force) | |
258 | 264 | tinfo = res[str(rtag)] |
259 | 265 | self.assertEqual(tinfo.commit, rtag.commit) |
260 | 266 | self.assertTrue(tinfo.flags & tinfo.TAG_UPDATE) |
631 | 637 | |
632 | 638 | def test_fetch_error(self): |
633 | 639 | rem = self.rorepo.remote('origin') |
634 | with self.assertRaisesRegex(GitCommandError, "Couldn't find remote ref __BAD_REF__"): | |
640 | with self.assertRaisesRegex(GitCommandError, "[Cc]ouldn't find remote ref __BAD_REF__"): | |
635 | 641 | rem.fetch('__BAD_REF__') |
636 | 642 | |
637 | 643 | @with_rw_repo('0.1.6', bare=False) |
228 | 228 | |
229 | 229 | Repo.clone_from(original_repo.git_dir, pathlib.Path(rw_dir) / "clone_pathlib") |
230 | 230 | |
231 | @with_rw_directory | |
232 | def test_clone_from_pathlib_withConfig(self, rw_dir): | |
233 | if pathlib is None: # pythons bellow 3.4 don't have pathlib | |
234 | raise SkipTest("pathlib was introduced in 3.4") | |
235 | ||
236 | original_repo = Repo.init(osp.join(rw_dir, "repo")) | |
237 | ||
238 | cloned = Repo.clone_from(original_repo.git_dir, pathlib.Path(rw_dir) / "clone_pathlib_withConfig", | |
239 | multi_options=["--recurse-submodules=repo", | |
240 | "--config core.filemode=false", | |
241 | "--config submodule.repo.update=checkout"]) | |
242 | ||
243 | assert_equal(cloned.config_reader().get_value('submodule', 'active'), 'repo') | |
244 | assert_equal(cloned.config_reader().get_value('core', 'filemode'), False) | |
245 | assert_equal(cloned.config_reader().get_value('submodule "repo"', 'update'), 'checkout') | |
246 | ||
231 | 247 | @with_rw_repo('HEAD') |
232 | 248 | def test_max_chunk_size(self, repo): |
233 | 249 | class TestOutputStream(object): |
973 | 989 | commit = repo.head.commit |
974 | 990 | self.assertIsInstance(commit, Object) |
975 | 991 | |
992 | # this ensures we can read the remotes, which confirms we're reading | |
993 | # the config correctly. | |
994 | origin = repo.remotes.origin | |
995 | self.assertIsInstance(origin, Remote) | |
996 | ||
976 | 997 | self.assertIsInstance(repo.heads['aaaaaaaa'], Head) |
977 | 998 | |
978 | 999 | @with_rw_directory |
1 | 1 | # This module is part of GitPython and is released under |
2 | 2 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php |
3 | 3 | import os |
4 | import shutil | |
4 | 5 | import sys |
5 | 6 | from unittest import skipIf |
6 | 7 | |
478 | 479 | with sm.config_writer() as writer: |
479 | 480 | writer.set_value('path', fp) # change path to something with prefix AFTER url change |
480 | 481 | |
481 | # update fails as list_items in such a situations cannot work, as it cannot | |
482 | # find the entry at the changed path | |
483 | self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False) | |
482 | # update doesn't fail, because list_items ignores the wrong path in such situations. | |
483 | rm.update(recursive=False) | |
484 | 484 | |
485 | 485 | # move it properly - doesn't work as it its path currently points to an indexentry |
486 | 486 | # which doesn't exist ( move it to some path, it doesn't matter here ) |
659 | 659 | url=empty_repo_dir, no_checkout=checkout_mode and True or False) |
660 | 660 | # end for each checkout mode |
661 | 661 | |
662 | @with_rw_directory | |
663 | def test_list_only_valid_submodules(self, rwdir): | |
664 | repo_path = osp.join(rwdir, 'parent') | |
665 | repo = git.Repo.init(repo_path) | |
666 | repo.git.submodule('add', self._small_repo_url(), 'module') | |
667 | repo.index.commit("add submodule") | |
668 | ||
669 | assert len(repo.submodules) == 1 | |
670 | ||
671 | # Delete the directory from submodule | |
672 | submodule_path = osp.join(repo_path, 'module') | |
673 | shutil.rmtree(submodule_path) | |
674 | repo.git.add([submodule_path]) | |
675 | repo.index.commit("remove submodule") | |
676 | ||
677 | repo = git.Repo(repo_path) | |
678 | assert len(repo.submodules) == 0 | |
679 | ||
662 | 680 | @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, |
663 | 681 | """FIXME on cygwin: File "C:\\projects\\gitpython\\git\\cmd.py", line 671, in execute |
664 | 682 | raise GitCommandError(command, status, stderr_value, stdout_value) |
211 | 211 | self.assertIsInstance(Actor.author(cr), Actor) |
212 | 212 | # END assure config reader is handled |
213 | 213 | |
214 | def test_actor_from_string(self): | |
215 | self.assertEqual(Actor._from_string("name"), Actor("name", None)) | |
216 | self.assertEqual(Actor._from_string("name <>"), Actor("name", "")) | |
217 | self.assertEqual(Actor._from_string("name last another <some-very-long-email@example.com>"), | |
218 | Actor("name last another", "some-very-long-email@example.com")) | |
219 | ||
214 | 220 | @ddt.data(('name', ''), ('name', 'prefix_')) |
215 | 221 | def test_iterable_list(self, case): |
216 | 222 | name, prefix = case |
385 | 385 | # handle |
386 | 386 | # Counting objects: 4, done. |
387 | 387 | # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done. |
388 | self._cur_line = line | |
388 | self._cur_line = line = line.decode('utf-8') if isinstance(line, bytes) else line | |
389 | 389 | if len(self.error_lines) > 0 or self._cur_line.startswith(('error:', 'fatal:')): |
390 | 390 | self.error_lines.append(self._cur_line) |
391 | 391 | return [] |
533 | 533 | can be committers and authors or anything with a name and an email as |
534 | 534 | mentioned in the git log entries.""" |
535 | 535 | # PRECOMPILED REGEX |
536 | name_only_regex = re.compile(r'<(.+)>') | |
537 | name_email_regex = re.compile(r'(.*) <(.+?)>') | |
536 | name_only_regex = re.compile(r'<(.*)>') | |
537 | name_email_regex = re.compile(r'(.*) <(.*?)>') | |
538 | 538 | |
539 | 539 | # ENVIRONMENT VARIABLES |
540 | 540 | # read when creating new commits |
863 | 863 | |
864 | 864 | def __contains__(self, attr): |
865 | 865 | # first try identity match for performance |
866 | rval = list.__contains__(self, attr) | |
867 | if rval: | |
868 | return rval | |
866 | try: | |
867 | rval = list.__contains__(self, attr) | |
868 | if rval: | |
869 | return rval | |
870 | except (AttributeError, TypeError): | |
871 | pass | |
869 | 872 | # END handle match |
870 | 873 | |
871 | 874 | # otherwise make a full name search |
17 | 17 | |
18 | 18 | with open('requirements.txt') as reqs_file: |
19 | 19 | requirements = reqs_file.read().splitlines() |
20 | ||
21 | with open('test-requirements.txt') as reqs_file: | |
22 | test_requirements = reqs_file.read().splitlines() | |
20 | 23 | |
21 | 24 | |
22 | 25 | class build_py(_build_py): |
62 | 65 | print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr) |
63 | 66 | |
64 | 67 | |
65 | install_requires = ['gitdb2 >= 2.0.0'] | |
66 | test_requires = ['ddt>=1.1.1'] | |
67 | # end | |
68 | ||
69 | 68 | setup( |
70 | 69 | name="GitPython", |
71 | 70 | cmdclass={'build_py': build_py, 'sdist': sdist}, |
80 | 79 | package_dir={'git': 'git'}, |
81 | 80 | license="BSD License", |
82 | 81 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', |
83 | requires=['gitdb2 (>=2.0.0)'], | |
84 | install_requires=install_requires, | |
85 | test_requirements=test_requires + install_requires, | |
82 | install_requires=requirements, | |
83 | tests_require=requirements + test_requirements, | |
86 | 84 | zip_safe=False, |
87 | 85 | long_description="""GitPython is a python library used to interact with Git repositories""", |
88 | 86 | classifiers=[ |
109 | 107 | "Programming Language :: Python :: 3.4", |
110 | 108 | "Programming Language :: Python :: 3.5", |
111 | 109 | "Programming Language :: Python :: 3.6", |
110 | "Programming Language :: Python :: 3.7" | |
112 | 111 | ] |
113 | 112 | ) |