diff options
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | doc/source/changes.rst | 14 | ||||
-rw-r--r-- | doc/source/intro.rst | 8 | ||||
-rw-r--r-- | doc/source/tutorial.rst | 8 | ||||
-rw-r--r-- | git/cmd.py | 9 | ||||
-rw-r--r-- | git/config.py | 6 | ||||
-rw-r--r-- | git/objects/commit.py | 2 | ||||
-rw-r--r-- | git/test/fixtures/.gitconfig | 3 | ||||
-rw-r--r-- | git/test/lib/helper.py | 10 | ||||
-rw-r--r-- | git/test/test_config.py | 15 | ||||
-rw-r--r-- | git/test/test_docs.py | 2 | ||||
-rw-r--r-- | git/test/test_remote.py | 5 | ||||
-rw-r--r-- | git/util.py | 7 |
15 files changed, 72 insertions, 32 deletions
diff --git a/.travis.yml b/.travis.yml index b53228ca..7aaf9f94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,9 @@ install: # as commits are performed with the default user, it needs to be set for travis too - git config --global user.email "travis@ci.com" - git config --global user.name "Travis Runner" + # If we rewrite the user's config by accident, we will mess it up + # and cause subsequent tests to fail + - cat git/test/fixtures/.gitconfig >> ~/.gitconfig script: # Make sure we limit open handles to see if we are leaking them - ulimit -n 96 @@ -77,6 +77,7 @@ New BSD License. See the LICENSE file. ### DEVELOPMENT STATUS [](https://travis-ci.org/gitpython-developers/GitPython) +[](https://codeclimate.com/github/gitpython-developers/GitPython) [](https://coveralls.io/r/gitpython-developers/GitPython?branch=master) [](https://readthedocs.org/projects/gitpython/?badge=stable) [](http://www.issuestats.com/github/gitpython-developers/GitPython) @@ -87,14 +88,5 @@ Now that there seems to be a massive user base, this should be motivation enough * no open pull requests * no open issues describing bugs -#### FUTURE GOALS - -There has been a lot of work in the master branch, which is the direction I want git-python to go. Namely, it should be able to freely mix and match the back-end used, depending on your requirements and environment. - -* make new master work similarly to 0.3, but with the option to swap for at least one additional backend -* make a 1.0 release -* add backends as required - - [twitch-channel]: http://www.twitch.tv/byronimo/profile [youtube-playlist]: https://www.youtube.com/playlist?list=PLMHbQxe1e9MnoEcLhn6Yhv5KAvpWkJbL0
\ No newline at end of file @@ -1 +1 @@ -0.3.7 +1.0.1 diff --git a/doc/source/changes.rst b/doc/source/changes.rst index d9f44a86..e6d7b09b 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,18 @@ Changelog ========= +1.0.1 - Fixes +============= + +* A list of all issues can be found `on github <https://github.com/gitpython-developers/GitPython/issues?q=milestone%3A%22v1.0.1+-+Fixes%22+is%3Aclosed>`_ + +1.0.0 - Notes +============= + +This version is equivalent to v0.3.7, but finally acknowledges that GitPython is stable and production ready. + +It follows the `semantic version scheme <http://semver.org>`_, and thus will not break its existing API unless it goes 2.0. + 0.3.7 - Fixes ============= * `IndexFile.add()` will now write the index without any extension data by default. However, you may override this behaviour with the new `write_extension_data` keyword argument. @@ -353,7 +365,7 @@ General a treeish git cowardly refuses to pick one and asks for the command to use the unambiguous syntax where '--' seperates the treeish from the paths. -* ``Repo.commits``, ``Repo.commits_between``, ``Reop.commits_since``, +* ``Repo.commits``, ``Repo.commits_between``, ``Repo.commits_since``, ``Repo.commit_count``, ``Repo.commit``, ``Commit.count`` and ``Commit.find_all`` all now optionally take a path argument which constrains the lookup by path. This changes the order of the positional diff --git a/doc/source/intro.rst b/doc/source/intro.rst index b767ccd7..78d40344 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -90,9 +90,11 @@ Finally verify the installation by running the `nose powered <http://code.google $ nosetests -Mailing List -============ -http://groups.google.com/group/git-python +Questions and Answers +===================== +Please use stackoverflow for questions, and don't forget to tag it with `gitpython` to assure the right people see the question in a timely manner. + +http://stackoverflow.com/questions/tagged/gitpython Issue Tracker ============= diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 632d2d0c..7cc296d8 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -343,6 +343,14 @@ This one sets a custom script to be executed in place of `ssh`, and can be used with repo.git.custom_environment(GIT_SSH=ssh_executable): repo.remotes.origin.fetch() +Here's an example executable that can be used in place of the `ssh_executable` above:: + + #!/bin/sh + ID_RSA=/var/lib/openshift/5562b947ecdd5ce939000038/app-deployments/id_rsa + exec /usr/bin/ssh -o StrictHostKeyChecking=no -i $ID_RSA "$@" + +Please note that the script must be executable (i.e. `chomd +x script.sh`). `StrictHostKeyChecking=no` is used to avoid prompts asking to save the hosts key to `~/.ssh/known_hosts`, which happens in case you run this as daemon. + You might also have a look at `Git.update_environment(...)` in case you want to setup a changed environment more permanently. Submodule Handling @@ -530,7 +530,7 @@ class Git(LazyMixin): * output_stream if extended_output = False * tuple(int(status), output_stream, str(stderr)) if extended_output = True - Note git is executed with LC_MESSAGES="C" to ensure consitent + Note git is executed with LC_MESSAGES="C" to ensure consistent output regardless of system language. :raise GitCommandError: @@ -549,7 +549,12 @@ class Git(LazyMixin): # Start the process env = os.environ.copy() - env["LC_MESSAGES"] = "C" + # Attempt to force all output to plain ascii english, which is what some parsing code + # may expect. + # According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well + # just to be sure. + env["LANGUAGE"] = "C" + env["LC_ALL"] = "C" env.update(self._environment) if sys.platform == 'win32': diff --git a/git/config.py b/git/config.py index 38dd1b44..a6a25c7b 100644 --- a/git/config.py +++ b/git/config.py @@ -81,6 +81,7 @@ def set_dirty_and_flush_changes(non_const_func): def flush_changes(self, *args, **kwargs): rval = non_const_func(self, *args, **kwargs) + self._dirty = True self.write() return rval # END wrapper method @@ -190,6 +191,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje self._file_or_files = file_or_files self._read_only = read_only + self._dirty = False self._is_initialized = False self._merge_includes = merge_includes self._lock = None @@ -304,7 +306,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje if mo: # We might just have handled the last line, which could contain a quotation we want to remove optname, vi, optval = mo.group('option', 'vi', 'value') - if vi in ('=', ':') and ';' in optval: + if vi in ('=', ':') and ';' in optval and not optval.strip().startswith('"'): pos = optval.find(';') if pos != -1 and optval[pos - 1].isspace(): optval = optval[:pos] @@ -433,6 +435,8 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje :raise IOError: if this is a read-only writer instance or if we could not obtain a file lock""" self._assure_writable("write") + if not self._dirty: + return if isinstance(self._file_or_files, (list, tuple)): raise AssertionError("Cannot write back if there is not exactly a single file to write to, have %i files" diff --git a/git/objects/commit.py b/git/objects/commit.py index f13760fd..ac381cd9 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -445,7 +445,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): next_line = readline() while next_line.startswith(b'mergetag '): next_line = readline() - while next_line.startswith(' '): + while next_line.startswith(b' '): next_line = readline() # end skip mergetags diff --git a/git/test/fixtures/.gitconfig b/git/test/fixtures/.gitconfig new file mode 100644 index 00000000..6a0459f6 --- /dev/null +++ b/git/test/fixtures/.gitconfig @@ -0,0 +1,3 @@ +[alias] + rbi = "!g() { git rebase -i origin/${1:-master} ; } ; g" + expush = "!f() { git branch -f tmp ; { git rbi $1 && git push ; } ; git reset --hard tmp ; git rebase origin/${1:-master}; } ; f"
\ No newline at end of file diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index 541b972d..8be2881c 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -18,10 +18,11 @@ from git.compat import string_types osp = os.path.dirname GIT_REPO = os.environ.get("GIT_PYTHON_TEST_GIT_REPO_BASE", osp(osp(osp(osp(__file__))))) +GIT_DAEMON_PORT = os.environ.get("GIT_PYTHON_TEST_GIT_DAEMON_PORT", "9418") __all__ = ( 'fixture_path', 'fixture', 'absolute_project_path', 'StringProcessAdapter', - 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'GIT_REPO' + 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'GIT_REPO', 'GIT_DAEMON_PORT' ) #{ Routines @@ -193,14 +194,15 @@ def with_rw_and_rw_remote_repo(working_tree_ref): # by the user, not by us d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir) d_remote.fetch() - remote_repo_url = "git://localhost%s" % remote_repo_dir + remote_repo_url = "git://localhost:%s%s" % (GIT_DAEMON_PORT, remote_repo_dir) d_remote.config_writer.set('url', remote_repo_url) temp_dir = osp(_mktemp()) # On windows, this will fail ... we deal with failures anyway and default to telling the user to do it try: - gd = Git().daemon(temp_dir, enable='receive-pack', as_process=True) + gd = Git().daemon(temp_dir, enable='receive-pack', listen='127.0.0.1', port=GIT_DAEMON_PORT, + as_process=True) # yes, I know ... fortunately, this is always going to work if sleep time is just large enough time.sleep(0.5) except Exception: @@ -223,6 +225,8 @@ def with_rw_and_rw_remote_repo(working_tree_ref): raise AssertionError(msg) else: msg = 'Please start a git-daemon to run this test, execute: git daemon --enable=receive-pack "%s"' + msg += 'You can also run the daemon on a different port by passing --port=<port>' + msg += 'and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>' msg %= temp_dir raise AssertionError(msg) # END make assertion diff --git a/git/test/test_config.py b/git/test/test_config.py index fc2b87b6..7758a094 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -18,7 +18,6 @@ from git.compat import ( ) import io import os -from copy import copy from git.config import cp @@ -30,21 +29,18 @@ class TestBase(TestCase): sio.name = file_path return sio - def _parsers_equal_or_raise(self, lhs, rhs): - pass - def test_read_write(self): # writer must create the exact same file as the one read before for filename in ("git_config", "git_config_global"): file_obj = self._to_memcache(fixture_path(filename)) - file_obj_orig = copy(file_obj) w_config = GitConfigParser(file_obj, read_only=False) w_config.read() # enforce reading assert w_config._sections w_config.write() # enforce writing # we stripped lines when reading, so the results differ - assert file_obj.getvalue() and file_obj.getvalue() != file_obj_orig.getvalue() + assert file_obj.getvalue() + self.assertEqual(file_obj.getvalue(), self._to_memcache(fixture_path(filename)).getvalue()) # creating an additional config writer must fail due to exclusive access self.failUnlessRaises(IOError, GitConfigParser, file_obj, read_only=False) @@ -207,3 +203,10 @@ class TestBase(TestCase): assert not cw.has_section('core') assert len(cw.items(nn)) == 4 cw.release() + + def test_complex_aliases(self): + file_obj = self._to_memcache(fixture_path('.gitconfig')) + w_config = GitConfigParser(file_obj, read_only=False) + self.assertEqual(w_config.get('alias', 'rbi'), '"!g() { git rebase -i origin/${1:-master} ; } ; g"') + w_config.release() + self.assertEqual(file_obj.getvalue(), self._to_memcache(fixture_path('.gitconfig')).getvalue()) diff --git a/git/test/test_docs.py b/git/test/test_docs.py index 586f0ce4..5b8aa817 100644 --- a/git/test/test_docs.py +++ b/git/test/test_docs.py @@ -94,7 +94,7 @@ class Tutorials(TestBase): # [11-test_init_repo_object] assert now.commit.message != past.commit.message # You can read objects directly through binary streams, no working tree required - assert (now.commit.tree / 'VERSION').data_stream.read().decode('ascii').startswith('0') + assert (now.commit.tree / 'VERSION').data_stream.read().decode('ascii').startswith('1') # You can traverse trees as well to handle all contained files of a particular commit file_count = 0 diff --git a/git/test/test_remote.py b/git/test/test_remote.py index c419ecee..af854988 100644 --- a/git/test/test_remote.py +++ b/git/test/test_remote.py @@ -8,7 +8,8 @@ from git.test.lib import ( TestBase, with_rw_repo, with_rw_and_rw_remote_repo, - fixture + fixture, + GIT_DAEMON_PORT ) from git import ( RemoteProgress, @@ -250,7 +251,7 @@ class TestRemote(TestBase): # must clone with a local path for the repo implementation not to freak out # as it wants local paths only ( which I can understand ) other_repo = remote_repo.clone(other_repo_dir, shared=False) - remote_repo_url = "git://localhost%s" % remote_repo.git_dir + remote_repo_url = "git://localhost:%s%s" % (GIT_DAEMON_PORT, remote_repo.git_dir) # put origin to git-url other_origin = other_repo.remotes.origin diff --git a/git/util.py b/git/util.py index 1147cb53..fb459da1 100644 --- a/git/util.py +++ b/git/util.py @@ -164,8 +164,9 @@ class RemoteProgress(object): Handler providing an interface to parse progress information emitted by git-push and git-fetch and to dispatch callbacks allowing subclasses to react to the progress. """ - _num_op_codes = 7 - BEGIN, END, COUNTING, COMPRESSING, WRITING, RECEIVING, RESOLVING = [1 << x for x in range(_num_op_codes)] + _num_op_codes = 8 + BEGIN, END, COUNTING, COMPRESSING, WRITING, RECEIVING, RESOLVING, FINDING_SOURCES = \ + [1 << x for x in range(_num_op_codes)] STAGE_MASK = BEGIN | END OP_MASK = ~STAGE_MASK @@ -227,6 +228,8 @@ class RemoteProgress(object): op_code |= self.RECEIVING elif op_name == 'Resolving deltas': op_code |= self.RESOLVING + elif op_name == 'Finding sources': + op_code |= self.FINDING_SOURCES else: # Note: On windows it can happen that partial lines are sent # Hence we get something like "CompreReceiving objects", which is |