Codebase list python-git / 15e0c64
New upstream version 2.1.13 TANIGUCHI Takaki 4 years ago
34 changed file(s) with 468 addition(s) and 93 deletion(s). Raw diff Collapse all Expand all
2626 -Charles Bouchard-Légaré <cblegare.atl _at_ ntis.ca>
2727 -Yaroslav Halchenko <debian _at_ onerussian.com>
2828 -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>
2935
3036 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
00 Metadata-Version: 1.2
11 Name: GitPython
2 Version: 2.1.11
2 Version: 2.1.13
33 Summary: Python Git Library
44 Home-page: https://github.com/gitpython-developers/GitPython
55 Author: Sebastian Thiel, Michael Trier
2222 Classifier: Programming Language :: Python :: 3.4
2323 Classifier: Programming Language :: Python :: 3.5
2424 Classifier: Programming Language :: Python :: 3.6
25 Requires: gitdb2 (>=2.0.0)
25 Classifier: Programming Language :: Python :: 3.7
2626 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
00 AUTHORS
11 CHANGES
2 CONTRIBUTING.md
23 LICENSE
34 MANIFEST.in
45 README.md
67 requirements.txt
78 setup.cfg
89 setup.py
10 test-requirements.txt
911 GitPython.egg-info/PKG-INFO
1012 GitPython.egg-info/SOURCES.txt
1113 GitPython.egg-info/dependency_links.txt
1416 GitPython.egg-info/top_level.txt
1517 doc/.gitignore
1618 doc/Makefile
19 doc/requirements.txt
1720 doc/source/changes.rst
1821 doc/source/conf.py
1922 doc/source/index.rst
115118 git/test/fixtures/git_config
116119 git/test/fixtures/git_config-inc.cfg
117120 git/test/fixtures/git_config_global
121 git/test/fixtures/git_config_multiple
118122 git/test/fixtures/git_config_with_comments
119123 git/test/fixtures/git_config_with_empty_value
120124 git/test/fixtures/git_file
11 include LICENSE
22 include CHANGES
33 include AUTHORS
4 include README
4 include CONTRIBUTING.md
5 include README.md
56 include requirements.txt
7 include test-requirements.txt
68
79 recursive-include doc *
810
00 Metadata-Version: 1.2
11 Name: GitPython
2 Version: 2.1.11
2 Version: 2.1.13
33 Summary: Python Git Library
44 Home-page: https://github.com/gitpython-developers/GitPython
55 Author: Sebastian Thiel, Michael Trier
2222 Classifier: Programming Language :: Python :: 3.4
2323 Classifier: Programming Language :: Python :: 3.5
2424 Classifier: Programming Language :: Python :: 3.6
25 Requires: gitdb2 (>=2.0.0)
25 Classifier: Programming Language :: Python :: 3.7
2626 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
1818 the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
1919
2020 * Git (1.7.x or newer)
21 * Python 2.7 to 3.6.
21 * Python 2.7 to 3.7.
2222
2323 The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
2424 The installer takes care of installing them for you.
177177 * [PyJFuzz](https://github.com/mseclab/PyJFuzz)
178178 * [Loki](https://github.com/Neo23x0/Loki)
179179 * [Omniwallet](https://github.com/OmniLayer/omniwallet)
180 * [GitViper](https://github.com/BeayemX/GitViper)
180181
181182 ### LICENSE
182183
190191 [![Code Climate](https://codeclimate.com/github/gitpython-developers/GitPython/badges/gpa.svg)](https://codeclimate.com/github/gitpython-developers/GitPython)
191192 [![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
192193 [![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)
193195 [![Throughput Graph](https://graphs.waffle.io/gitpython-developers/GitPython/throughput.svg)](https://waffle.io/gitpython-developers/GitPython/metrics/throughput)
196
194197
195198 Now that there seems to be a massive user base, this should be motivation enough to let git-python
196199 return to a proper state, which means
0 2.1.11
0 2.1.13
0 sphinx<2.0
1 sphinx_rtd_theme
00 =========
11 Changelog
22 =========
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
320
421 2.1.11 - Bugfixes
522 =================
2929 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest']
3030
3131 # Add any paths that contain templates here, relative to this directory.
32 templates_path = ['.templates']
32 templates_path = []
3333
3434 # The suffix of source filenames.
3535 source_suffix = '.rst'
9393 # Options for HTML output
9494 # -----------------------
9595
96 html_theme = 'sphinx_rtd_theme'
9697 html_theme_options = {
9798 }
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'
10399
104100 # The name for this set of Sphinx documents. If None, it defaults to
105101 # "<project> v<release> documentation".
120116 # Add any paths that contain custom static files (such as style sheets) here,
121117 # relative to this directory. They are copied after the builtin static files,
122118 # so a file named "default.css" will overwrite the builtin "default.css".
123 html_static_path = ['.static']
119 html_static_path = []
124120
125121 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126122 # using the given strftime format.
103103 $ cd git-python
104104 $ git submodule update --init --recursive
105105
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::
107107
108108 $ nosetests
109109
11
22 API Reference
33 =============
4
5 Version
6 -------
7
8 .. py:data:: git.__version__
9
10 Current GitPython version.
411
512 Objects.Base
613 ------------
1111 import os.path as osp
1212
1313
14 __version__ = '2.1.11'
14 __version__ = '2.1.13'
1515
1616
1717 #{ Initialization
1818 def _init_externals():
1919 """Initialize external projects by putting them into the path"""
20 if __version__ == '2.1.11':
20 if __version__ == '2.1.13':
2121 sys.path.insert(0, osp.join(osp.dirname(__file__), 'ext', 'gitdb'))
2222
2323 try:
4242 stream_copy,
4343 )
4444
45 try:
46 PermissionError
47 except NameError: # Python < 3.3
48 PermissionError = OSError
4549
4650 execute_kwargs = {'istream', 'with_extended_output',
4751 'with_exceptions', 'as_process', 'stdout_as_string',
7781 Set it to False if `universal_newline == True` (then streams are in text-mode)
7882 or if decoding must happen later (i.e. for Diffs).
7983 """
80 # Use 2 "pupm" threads and wait for both to finish.
84 # Use 2 "pump" threads and wait for both to finish.
8185 def pump_stream(cmdline, name, stream, is_decode, handler):
8286 try:
8387 for line in stream:
210214
211215 # test if the new git executable path is valid
212216
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
224221 has_git = False
225222 try:
226223 cls().version()
227224 has_git = True
228 except exceptions:
225 except (GitCommandNotFound, PermissionError):
229226 pass
230227
231228 # warn or raise exception if test failed
717714 stdout_sink = (PIPE
718715 if with_stdout
719716 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)
722722 try:
723723 proc = Popen(command,
724724 env=env,
793793 else:
794794 max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
795795 stream_copy(proc.stdout, output_stream, max_chunk_size)
796 stdout_value = output_stream
796 stdout_value = proc.stdout.read()
797797 stderr_value = proc.stderr.read()
798798 # strip trailing "\n"
799799 if stderr_value.endswith(b"\n"):
884884 if len(name) == 1:
885885 if value is True:
886886 return ["-%s" % name]
887 elif type(value) is not bool:
887 elif value not in (False, None):
888888 if split_single_char_options:
889889 return ["-%s" % name, "%s" % value]
890890 else:
892892 else:
893893 if value is True:
894894 return ["--%s" % dashify(name)]
895 elif type(value) is not bool:
895 elif value not in (False, None):
896896 return ["--%s=%s" % (dashify(name), value)]
897897 return []
898898
145145 self._config.__exit__(exception_type, exception_value, traceback)
146146
147147
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
148193 class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)):
149194
150195 """Implements specifics required to read git style configuration files.
199244 contents into ours. This makes it impossible to write back an individual configuration file.
200245 Thus, if you want to modify a single configuration file, turn this off to leave the original
201246 dataset unaltered when reading it."""
202 cp.RawConfigParser.__init__(self, dict_type=OrderedDict)
247 cp.RawConfigParser.__init__(self, dict_type=_OMD)
203248
204249 # Used in python 3, needs to stay in sync with sections for underlying implementation to work
205250 if not hasattr(self, '_proxies'):
347392 is_multi_line = True
348393 optval = string_decode(optval[1:])
349394 # end handle multi-line
350 cursect[optname] = optval
395 # preserves multiple values for duplicate optnames
396 cursect.add(optname, optval)
351397 else:
352398 # check if it's an option with no value - it's just ignored by git
353399 if not self.OPTVALUEONLY.match(line):
361407 is_multi_line = False
362408 line = line[:-1]
363409 # end handle quotations
364 cursect[optname] += string_decode(line)
410 optval = cursect.getlast(optname)
411 cursect.setlast(optname, optval + string_decode(line))
365412 # END parse section or option
366413 # END while reading
367414
441488 git compatible format"""
442489 def write_section(name, section_dict):
443490 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))
447497 # END if key is not __name__
448498 # END section writing
449499
455505 def items(self, section_name):
456506 """:return: list((option, value), ...) pairs of all items in the given section"""
457507 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()
458524
459525 @needs_values
460526 def write(self):
507573 return self._read_only
508574
509575 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
511581 :param default:
512582 If not None, the given default value will be returned in case
513583 the option did not exist
522592 return default
523593 raise
524594
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):
525620 types = (int, float)
526621 for numtype in types:
527622 try:
544639 return True
545640
546641 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)
548645
549646 return valuestr
550647
569666 if not self.has_section(section):
570667 self.add_section(section)
571668 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))
572688 return self
573689
574690 def rename_section(self, section, new_name):
583699 raise ValueError("Destination section '%s' already exists" % new_name)
584700
585701 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)
588705 # end for each value to copy
589706
590707 # This call writes back the changes, which is why we don't have the respective decorator
383383 @property
384384 def renamed_file(self):
385385 """:returns: True if the blob of our diff has been renamed
386 :note: This property is deprecated, please use ``renamed_file`` instead.
387386 """
388387 return self.rename_from != self.rename_to
389388
8080 :param tree: Tree
8181 Tree object
8282 :param author: Actor
83 is the author string ( will be implicitly converted into an Actor object )
83 is the author Actor object
8484 :param authored_date: int_seconds_since_epoch
8585 is the authored DateTime - use time.gmtime() to convert it into a
8686 different format
11591159 try:
11601160 parser = cls._config_parser(repo, pc, read_only=True)
11611161 except IOError:
1162 raise StopIteration
1162 return
11631163 # END handle empty iterator
11641164
11651165 rt = pc.tree # root tree
11831183 entry = index.entries[index.entry_key(p, 0)]
11841184 sm = Submodule(repo, entry.binsha, entry.mode, entry.path)
11851185 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
11881189 # END handle keyerror
11891190 # END handle critical error
11901191
5858
5959 self.tag = lines[2][4:] # tag <tag name>
6060
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)
6364
6465 # line 4 empty - it could mark the beginning of the next header
6566 # in case there really is no message, it would not exist. Otherwise
543543 except GitCommandError as ex:
544544 if any(msg in str(ex) for msg in ['correct access rights', 'cannot run ssh']):
545545 # 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
550549 else:
551550 raise ex
552551 else:
694693 msg += "Will ignore extra progress lines or fetch head lines."
695694 msg %= (l_fil, l_fhi)
696695 log.debug(msg)
696 log.debug("info lines: " + str(fetch_info_lines))
697 log.debug("head info : " + str(fetch_head_info))
697698 if l_fil < l_fhi:
698699 fetch_head_info = fetch_head_info[:l_fil]
699700 else:
9696 repo = Repo("/Users/mtrier/Development/git-python.git")
9797 repo = Repo("~/Development/git-python.git")
9898 repo = Repo("$REPOSITORIES/Development/git-python.git")
99 repo = Repo("C:\\Users\\mtrier\\Development\\git-python\\.git")
99100
100101 - In *Cygwin*, path may be a `'cygdrive/...'` prefixed path.
101102 - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also evals to false,
139140 # removed. It's just cleaner.
140141 if is_git_dir(curpath):
141142 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')
143158 break
144159
145160 dotgit = osp.join(curpath, '.git')
415430 elif config_level == "global":
416431 return osp.normpath(osp.expanduser("~/.gitconfig"))
417432 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"))
419434
420435 raise ValueError("Invalid configuration level: %r" % config_level)
421436
543558 return res
544559
545560 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
547562
548563 :param ancestor_rev: Rev which should be an ancestor
549564 :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.
551566 """
552567 try:
553568 self.git.merge_base(ancestor_rev, rev, is_ancestor=True)
713728
714729 stream = (line for line in data.split(b'\n') if line)
715730 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
717735 hexsha, orig_lineno, lineno, num_lines = line.split()
718736 lineno = int(lineno)
719737 num_lines = int(num_lines)
723741 # for this commit
724742 props = {}
725743 while True:
726 line = next(stream)
744 try:
745 line = next(stream)
746 except StopIteration:
747 return
727748 if line == b'boundary':
728749 # "boundary" indicates a root commit and occurs
729750 # instead of the "previous" tag
748769 # Discard all lines until we find "filename" which is
749770 # guaranteed to be the last line
750771 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
752776 tag, value = line.split(b' ', 1)
753777 if tag == b'filename':
754778 orig_filename = value
906930 return cls(path, odbt=odbt)
907931
908932 @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):
910934 if progress is not None:
911935 progress = to_progress_instance(progress)
912936
928952 sep_dir = kwargs.get('separate_git_dir')
929953 if sep_dir:
930954 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,
932959 v=True, universal_newlines=True, **add_progress(kwargs, git, progress))
933960 if progress:
934961 handle_process_output(proc, None, progress.new_message_handler(), finalize_process, decode_streams=False)
958985 # END handle remote repo
959986 return repo
960987
961 def clone(self, path, progress=None, **kwargs):
988 def clone(self, path, progress=None, multi_options=None, **kwargs):
962989 """Create a clone from this repository.
963990
964991 :param path: is the full path of the new repo (traditionally ends with ./<name>.git).
965992 :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']
966997 :param kwargs:
967998 * odbt = ObjectDatabase Type, allowing to determine the object database
968999 implementation used by the returned Repo instance
9691000 * All remaining keyword arguments are given to the git-clone command
9701001
9711002 :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)
9731004
9741005 @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):
9761007 """Create a clone from the given URL
9771008
9781009 :param url: valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS
9791010 :param to_path: Path to which the repository should be cloned to
9801011 :param progress: See 'git.remote.Remote.push'.
9811012 :param env: Optional dictionary containing the desired environment variables.
1013 :param mutli_options: See ``clone`` method
9821014 :param kwargs: see the ``clone`` method
9831015 :return: Repo instance pointing to the cloned directory"""
9841016 git = Git(os.getcwd())
9851017 if env is not None:
9861018 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)
9881020
9891021 def archive(self, ostream, treeish=None, prefix=None, **kwargs):
9901022 """Archive the tree at the given revision.
8585 ## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes.
8686 path = decygpath(path)
8787 if not osp.isabs(path):
88 path = osp.join(osp.dirname(d), path)
88 path = osp.normpath(osp.join(osp.dirname(d), path))
8989 return find_submodule_git_dir(path)
9090 # end handle exception
9191 return None
0 [section0]
1 option0 = value0
2
3 [section1]
4 option1 = value1a
5 option1 = value1b
6 other_option1 = other_value1
1010 GitConfigParser
1111 )
1212 from git.compat import string_types
13 from git.config import cp
13 from git.config import _OMD, cp
1414 from git.test.lib import (
1515 TestCase,
1616 fixture_path,
264264
265265 with self.assertRaises(cp.NoOptionError):
266266 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'])
66 import os
77 import subprocess
88 import sys
9 from tempfile import TemporaryFile
910
1011 from git import (
1112 Git,
8081 def test_it_transforms_kwargs_into_git_command_arguments(self):
8182 assert_equal(["-s"], self.git.transform_kwargs(**{'s': True}))
8283 assert_equal(["-s", "5"], self.git.transform_kwargs(**{'s': 5}))
84 assert_equal([], self.git.transform_kwargs(**{'s': None}))
8385
8486 assert_equal(["--max-count"], self.git.transform_kwargs(**{'max_count': True}))
8587 assert_equal(["--max-count=5"], self.git.transform_kwargs(**{'max_count': 5}))
88 assert_equal([], self.git.transform_kwargs(**{'max_count': None}))
8689
8790 # Multiple args are supported by using lists/tuples
8891 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]}))
9093
9194 # order is undefined
9295 res = self.git.transform_kwargs(**{'s': True, 't': True})
106109 # this_should_not_be_ignored=False implies it *should* be ignored
107110 self.git.version(pass_this_kwarg=False)
108111 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)
109117
110118 def test_it_accepts_environment_variables(self):
111119 filename = fixture_path("ls_tree_empty")
252252 self.assertEqual(tinfo.ref.commit, rtag.commit)
253253 self.assertTrue(tinfo.flags & tinfo.NEW_TAG)
254254
255 # adjust tag commit
255 # adjust the local tag commit
256256 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)
258264 tinfo = res[str(rtag)]
259265 self.assertEqual(tinfo.commit, rtag.commit)
260266 self.assertTrue(tinfo.flags & tinfo.TAG_UPDATE)
631637
632638 def test_fetch_error(self):
633639 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__"):
635641 rem.fetch('__BAD_REF__')
636642
637643 @with_rw_repo('0.1.6', bare=False)
228228
229229 Repo.clone_from(original_repo.git_dir, pathlib.Path(rw_dir) / "clone_pathlib")
230230
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
231247 @with_rw_repo('HEAD')
232248 def test_max_chunk_size(self, repo):
233249 class TestOutputStream(object):
973989 commit = repo.head.commit
974990 self.assertIsInstance(commit, Object)
975991
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
976997 self.assertIsInstance(repo.heads['aaaaaaaa'], Head)
977998
978999 @with_rw_directory
11 # This module is part of GitPython and is released under
22 # the BSD License: http://www.opensource.org/licenses/bsd-license.php
33 import os
4 import shutil
45 import sys
56 from unittest import skipIf
67
478479 with sm.config_writer() as writer:
479480 writer.set_value('path', fp) # change path to something with prefix AFTER url change
480481
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)
484484
485485 # move it properly - doesn't work as it its path currently points to an indexentry
486486 # which doesn't exist ( move it to some path, it doesn't matter here )
659659 url=empty_repo_dir, no_checkout=checkout_mode and True or False)
660660 # end for each checkout mode
661661
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
662680 @skipIf(HIDE_WINDOWS_KNOWN_ERRORS,
663681 """FIXME on cygwin: File "C:\\projects\\gitpython\\git\\cmd.py", line 671, in execute
664682 raise GitCommandError(command, status, stderr_value, stdout_value)
211211 self.assertIsInstance(Actor.author(cr), Actor)
212212 # END assure config reader is handled
213213
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
214220 @ddt.data(('name', ''), ('name', 'prefix_'))
215221 def test_iterable_list(self, case):
216222 name, prefix = case
385385 # handle
386386 # Counting objects: 4, done.
387387 # 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
389389 if len(self.error_lines) > 0 or self._cur_line.startswith(('error:', 'fatal:')):
390390 self.error_lines.append(self._cur_line)
391391 return []
533533 can be committers and authors or anything with a name and an email as
534534 mentioned in the git log entries."""
535535 # 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'(.*) <(.*?)>')
538538
539539 # ENVIRONMENT VARIABLES
540540 # read when creating new commits
863863
864864 def __contains__(self, attr):
865865 # 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
869872 # END handle match
870873
871874 # otherwise make a full name search
0 gitdb>=0.6.4
1 ddt>=1.1.1
0 gitdb2 (>=2.0.0)
1717
1818 with open('requirements.txt') as reqs_file:
1919 requirements = reqs_file.read().splitlines()
20
21 with open('test-requirements.txt') as reqs_file:
22 test_requirements = reqs_file.read().splitlines()
2023
2124
2225 class build_py(_build_py):
6265 print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr)
6366
6467
65 install_requires = ['gitdb2 >= 2.0.0']
66 test_requires = ['ddt>=1.1.1']
67 # end
68
6968 setup(
7069 name="GitPython",
7170 cmdclass={'build_py': build_py, 'sdist': sdist},
8079 package_dir={'git': 'git'},
8180 license="BSD License",
8281 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,
8684 zip_safe=False,
8785 long_description="""GitPython is a python library used to interact with Git repositories""",
8886 classifiers=[
109107 "Programming Language :: Python :: 3.4",
110108 "Programming Language :: Python :: 3.5",
111109 "Programming Language :: Python :: 3.6",
110 "Programming Language :: Python :: 3.7"
112111 ]
113112 )
0 ddt>=1.1.1
1 coverage
2 flake8
3 nose
4 tox
5 mock; python_version=='2.7'