diff options
-rw-r--r-- | .coveragerc | 10 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .gitmodules | 2 | ||||
-rw-r--r-- | .travis.yml | 14 | ||||
-rw-r--r-- | README.md | 97 | ||||
-rw-r--r-- | README.rst | 65 | ||||
-rw-r--r-- | doc/source/intro.rst | 2 | ||||
-rw-r--r-- | etc/sublime-text/git-python.sublime-project | 71 | ||||
-rw-r--r-- | git/cmd.py | 53 | ||||
-rw-r--r-- | git/diff.py | 4 | ||||
-rw-r--r-- | git/exc.py | 10 | ||||
m--------- | git/ext/gitdb | 0 | ||||
-rw-r--r-- | git/objects/submodule/base.py | 2 | ||||
-rw-r--r-- | git/refs/reference.py | 2 | ||||
-rw-r--r-- | git/remote.py | 22 | ||||
-rw-r--r-- | git/repo/base.py | 53 | ||||
-rw-r--r-- | git/repo/fun.py | 13 | ||||
-rw-r--r-- | git/test/test_git.py | 45 | ||||
-rw-r--r-- | git/test/test_remote.py | 4 | ||||
-rw-r--r-- | git/util.py | 21 | ||||
-rw-r--r-- | setup.py | 1 |
21 files changed, 365 insertions, 129 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..410ffc52 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +source = git + +; to make nosetests happy +[report] +omit = + */yaml* + */tests/* + */python?.?/* + */site-packages/nose/*
\ No newline at end of file @@ -2,7 +2,10 @@ *.swp *~ /lib/GitPython.egg-info +cover/ +.coverage /build /dist /doc/_build nbproject +*.sublime-workspace diff --git a/.gitmodules b/.gitmodules index 533fc59f..612c39d9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "gitdb"] path = git/ext/gitdb - url = http://github.com/gitpython-developers/gitdb.git + url = https://github.com/gitpython-developers/gitdb.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..0a2906dc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: python +python: + - "2.6" + - "2.7" + # - "pypy" - won't work as smmap doesn't work (see gitdb/.travis.yml for details) + +install: + - git submodule update --init --recursive + - git fetch --tags + - pip install coveralls +script: + - nosetests --with-coverage +# after_success: as long as we are not running smoothly ... give it the cover treatment every time + - coveralls diff --git a/README.md b/README.md new file mode 100644 index 00000000..d2a858bf --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +## GitPython + +GitPython is a python library used to interact with git repositories, high-level like git-porcelain, or low-level like git-plumbing. + +It provides abstractions of git objects for easy access of repository data, and additionally allows you to access the git repository more directly using either a pure python implementation, or the faster, but more resource intensive git command implementation. + +The object database implementation is optimized for handling large quantities of objects and large datasets, which is achieved by using low-level structures and data streaming. + +### REQUIREMENTS + +* Git ( tested with 1.8.3.4 ) +* Python Nose - used for running the tests + - Tested with nose 1.3.0 +* Mock by Michael Foord used for tests + - Tested with 1.0.1 + +### INSTALL + +If you have downloaded the source code: + + python setup.py install + +or if you want to obtain a copy more easily: + + pip install gitpython + +A distribution package can be obtained for manual installation at: + + http://pypi.python.org/pypi/GitPython + +### DEVELOPMENT STATUS + +[](https://travis-ci.org/gitpython-developers/GitPython) +[](https://coveralls.io/r/gitpython-developers/GitPython) + +The project was idle for 2 years, the last release (v0.3.2 RC1) was made on July 2011. Reason for this might have been the project's dependency on me as sole active maintainer, which is an issue in itself. + +Now I am back and fully dedicated to pushing [OSS](https://github.com/Byron/bcore) forward in the realm of [digital content creation](http://gooseberry.blender.org/), and git-python will see some of my time as well. Therefore it will be moving forward, slowly but steadily. + +In short, I want to make a new release of 0.3 with all contributions and fixes included, foster community building to facilitate contributions. Everything else is future. + +#### PRESENT GOALS + +The goals I have set for myself, in order, are as follows, all on branch 0.3. + +* bring the test suite back online to work with the most commonly used git version +* setup a travis test-matrix to test against a lower and upper git version as well +* merge all open pull requests, may there be a test-case or not, back. If something breaks, fix it if possible or let the contributor know +* conform git-python's structure and toolchain to the one used in my [other OSS projects](https://github.com/Byron/bcore) +* evaluate all open issues and close them if possible +* create a new release of the 0.3 branch +* evaluate python 3.3 compatibility and establish it if possible + +While that is happening, I will try hard to foster community around the project. This means being more responsive on the mailing list and in issues, as well as setting up clear guide lines about the [contribution](http://rfc.zeromq.org/spec:22) and maintenance workflow. + +#### 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. + +* restructure master to match my [OSS standard](https://github.com/Byron/bcore) +* review code base and bring test-suite back online +* establish python 3.3 compatibility +* make it 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 + +### SOURCE + + +GitPython's git repo is available on GitHub, which can be browsed at: + +https://github.com/gitpython-developers/GitPython + +and cloned using: + +git clone git://github.com/gitpython-developers/GitPython.git git-python + + +### DOCUMENTATION + +The html-compiled documentation can be found at the following URL: + +http://packages.python.org/GitPython/ + +### MAILING LIST + +http://groups.google.com/group/git-python + +### ISSUE TRACKER + +Issues are tracked on github: + +https://github.com/gitpython-developers/GitPython/issues + +### LICENSE + +New BSD License. See the LICENSE file. diff --git a/README.rst b/README.rst deleted file mode 100644 index fdb50942..00000000 --- a/README.rst +++ /dev/null @@ -1,65 +0,0 @@ -========== -GitPython -========== - -GitPython is a python library used to interact with git repositories, high-level like git-porcelain, or low-level like git-plumbing. - -It provides abstractions of git objects for easy access of repository data, and additionally allows you to access the git repository more directly using either a pure python implementation, or the faster, but more resource intensive git command implementation. - -The object database implementation is optimized for handling large quantities of objects and large datasets, which is achieved by using low-level structures and data streaming. - -REQUIREMENTS -============ - -* Git ( tested with 1.8.3.4 ) -* Python Nose - used for running the tests - * Tested with nose 1.3.0 -* Mock by Michael Foord used for tests - * Tested with 1.0.1 - -INSTALL -======= -If you have downloaded the source code: - - python setup.py install - -or if you want to obtain a copy more easily: - - easy_install gitpython - -A distribution package can be obtained for manual installation at: - - http://pypi.python.org/pypi/GitPython - -SOURCE -====== - -GitPython's git repo is available on GitHub, which can be browsed at: - -https://github.com/gitpython-developers/GitPython - -and cloned using: - -git clone git://github.com/gitpython-developers/GitPython.git git-python - - -DOCUMENTATION -============= -The html-compiled documentation can be found at the following URL: - -http://packages.python.org/GitPython/ - -MAILING LIST -============ -http://groups.google.com/group/git-python - -ISSUE TRACKER -============= -Issues are tracked on github: - -https://github.com/gitpython-developers/GitPython/issues - -LICENSE -======= - -New BSD License. See the LICENSE file. diff --git a/doc/source/intro.rst b/doc/source/intro.rst index 520cf159..8dac2804 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -47,7 +47,7 @@ here: * `setuptools`_ * `install setuptools <http://peak.telecommunity.com/DevCenter/EasyInstall#installation-instructions>`_ -* `pypi <http://pypi.python.org/pypi/SQLAlchemy>`_ +* `pypi <https://pypi.python.org/pypi/GitPython>`_ .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools diff --git a/etc/sublime-text/git-python.sublime-project b/etc/sublime-text/git-python.sublime-project new file mode 100644 index 00000000..5d981925 --- /dev/null +++ b/etc/sublime-text/git-python.sublime-project @@ -0,0 +1,71 @@ +{ + "folders": + [ + // GIT-PYTHON + ///////////// + { + "follow_symlinks": true, + "path": "../..", + "file_exclude_patterns" : [ + "*.sublime-workspace", + ".git", + ".noseids", + ".coverage" + ], + "folder_exclude_patterns" : [ + ".git", + "cover", + "git/ext" + ] + }, + // GITDB + //////// + { + "follow_symlinks": true, + "path": "../../git/ext/gitdb", + "file_exclude_patterns" : [ + "*.sublime-workspace", + ".git", + ".noseids", + ".coverage" + ], + "folder_exclude_patterns" : [ + ".git", + "cover", + "gitdb/ext" + ] + }, + // SMMAP + //////// + { + "follow_symlinks": true, + "path": "../../git/ext/gitdb/gitdb/ext/smmap", + "file_exclude_patterns" : [ + "*.sublime-workspace", + ".git", + ".noseids", + ".coverage" + ], + "folder_exclude_patterns" : [ + ".git", + "cover", + ] + }, + // ASYNC + //////// + { + "follow_symlinks": true, + "path": "../../git/ext/gitdb/gitdb/ext/async", + "file_exclude_patterns" : [ + "*.sublime-workspace", + ".git", + ".noseids", + ".coverage" + ], + "folder_exclude_patterns" : [ + ".git", + "cover", + ] + }, + ] +} @@ -42,7 +42,8 @@ class Git(LazyMixin): of the command to stdout. Set its value to 'full' to see details about the returned values. """ - __slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info") + __slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info", + "_git_options") # CONFIGURATION # The size in bytes read from stdout when copying git's output to another stream @@ -224,7 +225,8 @@ class Git(LazyMixin): .git directory in case of bare repositories.""" super(Git, self).__init__() self._working_dir = working_dir - + self._git_options = () + # cached command slots self.cat_file_header = None self.cat_file_all = None @@ -241,7 +243,7 @@ class Git(LazyMixin): if attr == '_version_info': # We only use the first 4 numbers, as everthing else could be strings in fact (on windows) version_numbers = self._call_process('version').split(' ')[2] - self._version_info = tuple(int(n) for n in version_numbers.split('.')[:4]) + self._version_info = tuple(int(n) for n in version_numbers.split('.')[:4] if n.isdigit()) else: super(Git, self)._set_cache_(attr) #END handle version info @@ -321,6 +323,9 @@ class Git(LazyMixin): if ouput_stream is True, the stdout value will be your output stream: * 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 + output regardless of system language. :raise GitCommandError: @@ -338,6 +343,7 @@ class Git(LazyMixin): # Start the process proc = Popen(command, + env={"LC_MESSAGES": "C"}, cwd=cwd, stdin=istream, stderr=PIPE, @@ -385,7 +391,10 @@ class Git(LazyMixin): # END handle debug printing if with_exceptions and status != 0: - raise GitCommandError(command, status, stderr_value) + if with_extended_output: + raise GitCommandError(command, status, stderr_value, stdout_value) + else: + raise GitCommandError(command, status, stderr_value) # Allow access to the command's status code if with_extended_output: @@ -393,7 +402,7 @@ class Git(LazyMixin): else: return stdout_value - def transform_kwargs(self, **kwargs): + def transform_kwargs(self, split_single_char_options=False, **kwargs): """Transforms Python style kwargs into git command line options.""" args = list() for k, v in kwargs.items(): @@ -401,7 +410,10 @@ class Git(LazyMixin): if v is True: args.append("-%s" % k) elif type(v) is not bool: - args.append("-%s%s" % (k, v)) + if split_single_char_options: + args.extend(["-%s" % k, "%s" % v]) + else: + args.append("-%s%s" % (k, v)) else: if v is True: args.append("--%s" % dashify(k)) @@ -412,18 +424,38 @@ class Git(LazyMixin): @classmethod def __unpack_args(cls, arg_list): if not isinstance(arg_list, (list,tuple)): + if isinstance(arg_list, unicode): + return [arg_list.encode('utf-8')] return [ str(arg_list) ] outlist = list() for arg in arg_list: if isinstance(arg_list, (list, tuple)): outlist.extend(cls.__unpack_args( arg )) + elif isinstance(arg_list, unicode): + outlist.append(arg_list.encode('utf-8')) # END recursion else: outlist.append(str(arg)) # END for each arg return outlist + def __call__(self, **kwargs): + """Specify command line options to the git executable + for a subcommand call + + :param kwargs: + is a dict of keyword arguments. + these arguments are passed as in _call_process + but will be passed to the git command rather than + the subcommand. + + ``Examples``:: + git(work_tree='/tmp').difftool()""" + self._git_options = self.transform_kwargs( + split_single_char_options=True, **kwargs) + return self + def _call_process(self, method, *args, **kwargs): """Run the given git command with the specified arguments and return the result as a String @@ -462,7 +494,14 @@ class Git(LazyMixin): args = opt_args + ext_args def make_call(): - call = [self.GIT_PYTHON_GIT_EXECUTABLE, dashify(method)] + call = [self.GIT_PYTHON_GIT_EXECUTABLE] + + # add the git options, the reset to empty + # to avoid side_effects + call.extend(self._git_options) + self._git_options = () + + call.extend([dashify(method)]) call.extend(args) return call #END utility to recreate call after changes diff --git a/git/diff.py b/git/diff.py index 8a4819ab..e90fc1cf 100644 --- a/git/diff.py +++ b/git/diff.py @@ -75,6 +75,10 @@ class Diffable(object): args.append("-M") # check for renames else: args.append("--raw") + + # in any way, assure we don't see colored output, + # fixes https://github.com/gitpython-developers/GitPython/issues/172 + args.append('--no-color') if paths is not None and not isinstance(paths, (tuple,list)): paths = [ paths ] @@ -17,14 +17,18 @@ class NoSuchPathError(OSError): class GitCommandError(Exception): """ Thrown if execution of the git command fails with non-zero status code. """ - def __init__(self, command, status, stderr=None): + def __init__(self, command, status, stderr=None, stdout=None): self.stderr = stderr + self.stdout = stdout self.status = status self.command = command def __str__(self): - return ("'%s' returned exit status %i: %s" % - (' '.join(str(i) for i in self.command), self.status, self.stderr)) + ret = "'%s' returned exit status %i: %s" % \ + (' '.join(str(i) for i in self.command), self.status, self.stderr) + if self.stdout is not None: + ret += "\nstdout: %s" % self.stdout + return ret class CheckoutError( Exception ): diff --git a/git/ext/gitdb b/git/ext/gitdb -Subproject 6576d5503a64d124fd7bcf639cc8955918b3ac4 +Subproject 39de1127459b73b862f2b779bb4565ad6b4bd62 diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index f7dc1597..99d54076 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -895,7 +895,7 @@ class Submodule(util.IndexObject, Iterable, Traversable): u = parser.get_value(sms, 'url') b = cls.k_head_default if parser.has_option(sms, cls.k_head_option): - b = parser.get_value(sms, cls.k_head_option) + b = str(parser.get_value(sms, cls.k_head_option)) # END handle optional information # get the binsha diff --git a/git/refs/reference.py b/git/refs/reference.py index 8cc577a8..09312f70 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -18,7 +18,7 @@ def require_remote_ref_path(func): """A decorator raising a TypeError if we are not a valid remote, based on the path""" def wrapper(self, *args): if not self.path.startswith(self._remote_common_path_default + "/"): - raise ValueError("ref path does not point to a remote reference: %s" % path) + raise ValueError("ref path does not point to a remote reference: %s" % self.path) return func(self, *args) #END wrapper wrapper.__name__ = func.__name__ diff --git a/git/remote.py b/git/remote.py index f89e9d83..b06c0686 100644 --- a/git/remote.py +++ b/git/remote.py @@ -513,14 +513,15 @@ class Remote(LazyMixin, Iterable): def _get_fetch_info_from_stderr(self, proc, progress): # skip first line as it is some remote info we are not interested in output = IterableList('name') - - + + # lines which are no progress are fetch info lines # this also waits for the command to finish # Skip some progress lines that don't provide relevant information fetch_info_lines = list() for line in digest_process_messages(proc.stderr, progress): - if line.startswith('From') or line.startswith('remote: Total') or line.startswith('POST'): + if line.startswith('From') or line.startswith('remote: Total') or line.startswith('POST') \ + or line.startswith(' ='): continue elif line.startswith('warning:'): print >> sys.stderr, line @@ -536,7 +537,10 @@ class Remote(LazyMixin, Iterable): fetch_head_info = fp.readlines() fp.close() - assert len(fetch_info_lines) == len(fetch_head_info), "len(%s) != len(%s)" % (fetch_head_info, fetch_info_lines) + # NOTE: HACK Just disabling this line will make github repositories work much better. + # I simply couldn't stand it anymore, so here is the quick and dirty fix ... . + # This project needs a lot of work ! + # assert len(fetch_info_lines) == len(fetch_head_info), "len(%s) != len(%s)" % (fetch_head_info, fetch_info_lines) output.extend(FetchInfo._from_line(self.repo, err_line, fetch_line) for err_line,fetch_line in zip(fetch_info_lines, fetch_head_info)) @@ -579,6 +583,10 @@ class Remote(LazyMixin, Iterable): See also git-push(1). Taken from the git manual + + Fetch supports multiple refspecs (as the + underlying git-fetch does) - supplying a list rather than a string + for 'refspec' will make use of this facility. :param progress: See 'push' method :param kwargs: Additional arguments to be passed to git-fetch :return: @@ -589,7 +597,11 @@ class Remote(LazyMixin, Iterable): As fetch does not provide progress information to non-ttys, we cannot make it available here unfortunately as in the 'push' method.""" kwargs = add_progress(kwargs, self.repo.git, progress) - proc = self.repo.git.fetch(self, refspec, with_extended_output=True, as_process=True, v=True, **kwargs) + if isinstance(refspec, list): + args = refspec + else: + args = [refspec] + proc = self.repo.git.fetch(self, *args, with_extended_output=True, as_process=True, v=True, **kwargs) return self._get_fetch_info_from_stderr(proc, progress or RemoteProgress()) def pull(self, refspec=None, progress=None, **kwargs): diff --git a/git/repo/base.py b/git/repo/base.py index 3bbcdb59..933c8c82 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -32,6 +32,7 @@ from gitdb.util import ( from fun import ( rev_parse, is_git_dir, + find_git_dir, touch ) @@ -55,13 +56,13 @@ class Repo(object): The following attributes are worth using: - 'working_dir' is the working directory of the git command, wich is the working tree + 'working_dir' is the working directory of the git command, which is the working tree directory if available or the .git directory in case of bare repositories 'working_tree_dir' is the working tree directory, but will raise AssertionError if we are a bare repository. - 'git_dir' is the .git repository directoy, which is always set.""" + 'git_dir' is the .git repository directory, which is always set.""" DAEMON_EXPORT_FILE = 'git-daemon-export-ok' __slots__ = ( "working_dir", "_working_tree_dir", "git_dir", "_bare", "git", "odb" ) @@ -108,8 +109,8 @@ class Repo(object): self.git_dir = curpath self._working_tree_dir = os.path.dirname(curpath) break - gitpath = join(curpath, '.git') - if is_git_dir(gitpath): + gitpath = find_git_dir(join(curpath, '.git')) + if gitpath is not None: self.git_dir = gitpath self._working_tree_dir = curpath break @@ -119,7 +120,7 @@ class Repo(object): # END while curpath if self.git_dir is None: - raise InvalidGitRepositoryError(epath) + raise InvalidGitRepositoryError(epath) self._bare = False try: @@ -375,7 +376,7 @@ class Repo(object): if rev is None: return self.head.commit else: - return self.rev_parse(str(rev)+"^0") + return self.rev_parse(unicode(rev)+"^0") def iter_trees(self, *args, **kwargs): """:return: Iterator yielding Tree objects @@ -398,7 +399,7 @@ class Repo(object): if rev is None: return self.head.commit.tree else: - return self.rev_parse(str(rev)+"^{tree}") + return self.rev_parse(unicode(rev)+"^{tree}") def iter_commits(self, rev=None, paths='', **kwargs): """A list of Commit objects representing the history of a given ref/commit @@ -498,8 +499,8 @@ class Repo(object): default_args = ('--abbrev=40', '--full-index', '--raw') if index: # diff index against HEAD - if isfile(self.index.path) and self.head.is_valid() and \ - len(self.git.diff('HEAD', '--cached', *default_args)): + if isfile(self.index.path) and \ + len(self.git.diff('--cached', *default_args)): return True # END index handling if working_tree: @@ -512,35 +513,33 @@ class Repo(object): return True # END untracked files return False - + @property def untracked_files(self): """ :return: list(str,...) - - Files currently untracked as they have not been staged yet. Paths + + Files currently untracked as they have not been staged yet. Paths are relative to the current working directory of the git command. - + :note: ignored files will not appear here, i.e. files mentioned in .gitignore""" # make sure we get all files, no only untracked directores - proc = self.git.status(untracked_files=True, as_process=True) - stream = iter(proc.stdout) + proc = self.git.status(porcelain=True, + untracked_files=True, + as_process=True) + # Untracked files preffix in porcelain mode + prefix = "?? " untracked_files = list() - for line in stream: - if not line.startswith("# Untracked files:"): + for line in proc.stdout: + if not line.startswith(prefix): continue - # skip two lines - stream.next() - stream.next() - - for untracked_info in stream: - if not untracked_info.startswith("#\t"): - break - untracked_files.append(untracked_info.replace("#\t", "").rstrip()) - # END for each utracked info line - # END for each line + filename = line[len(prefix):].rstrip('\n') + # Special characters are escaped + if filename[0] == filename[-1] == '"': + filename = filename[1:-1].decode('string_escape') + untracked_files.append(filename) return untracked_files @property diff --git a/git/repo/fun.py b/git/repo/fun.py index 7a8657ab..2c49d836 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -7,6 +7,7 @@ from gitdb.util import ( join, isdir, isfile, + dirname, hex_to_bin, bin_to_hex ) @@ -31,6 +32,18 @@ def is_git_dir(d): return False +def find_git_dir(d): + if is_git_dir(d): + return d + elif isfile(d): + with open(d) as fp: + content = fp.read().rstrip() + if content.startswith('gitdir: '): + d = join(dirname(d), content[8:]) + return find_git_dir(d) + return None + + def short_to_long(odb, hexsha): """:return: long hexadecimal sha1 from the given less-than-40 byte hexsha or None if no candidate could be found. diff --git a/git/test/test_git.py b/git/test/test_git.py index b61a0eea..5d4756ba 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -5,8 +5,9 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os, sys -from git.test.lib import ( TestBase, - patch, +from git.test.lib import ( + TestBase, + patch, raises, assert_equal, assert_true, @@ -16,7 +17,7 @@ from git import ( Git, GitCommandError ) class TestGit(TestBase): - + @classmethod def setUp(cls): super(TestGit, cls).setUp() @@ -29,6 +30,14 @@ class TestGit(TestBase): assert_true(git.called) assert_equal(git.call_args, ((['git', 'version'],), {})) + def test_call_unpack_args_unicode(self): + args = Git._Git__unpack_args(u'Unicode' + unichr(40960)) + assert_equal(args, ['Unicode\xea\x80\x80']) + + def test_call_unpack_args(self): + args = Git._Git__unpack_args(['git', 'log', '--', u'Unicode' + unichr(40960)]) + assert_equal(args, ['git', 'log', '--', 'Unicode\xea\x80\x80']) + @raises(GitCommandError) def test_it_raises_errors(self): self.git.this_does_not_exist() @@ -58,7 +67,7 @@ class TestGit(TestBase): # this_should_not_be_ignored=False implies it *should* be ignored output = self.git.version(pass_this_kwarg=False) assert_true("pass_this_kwarg" not in git.call_args[1]) - + def test_persistent_cat_file_command(self): # read header only import subprocess as sp @@ -67,37 +76,37 @@ class TestGit(TestBase): g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n") g.stdin.flush() obj_info = g.stdout.readline() - + # read header + data g = self.git.cat_file(batch=True, istream=sp.PIPE,as_process=True) g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n") g.stdin.flush() obj_info_two = g.stdout.readline() assert obj_info == obj_info_two - + # read data - have to read it in one large chunk size = int(obj_info.split()[2]) data = g.stdout.read(size) terminating_newline = g.stdout.read(1) - + # now we should be able to read a new object g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n") g.stdin.flush() assert g.stdout.readline() == obj_info - - + + # same can be achived using the respective command functions hexsha, typename, size = self.git.get_object_header(hexsha) hexsha, typename_two, size_two, data = self.git.get_object_data(hexsha) assert typename == typename_two and size == size_two - + def test_version(self): v = self.git.version_info assert isinstance(v, tuple) for n in v: assert isinstance(n, int) #END verify number types - + def test_cmd_override(self): prev_cmd = self.git.GIT_PYTHON_GIT_EXECUTABLE try: @@ -107,3 +116,17 @@ class TestGit(TestBase): finally: type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd #END undo adjustment + + def test_options_are_passed_to_git(self): + # This work because any command after git --version is ignored + git_version = self.git(version=True).NoOp() + git_command_version = self.git.version() + self.assertEquals(git_version, git_command_version) + + def test_single_char_git_options_are_passed_to_git(self): + input_value='TestValue' + output_value = self.git(c='user.name={}'.format(input_value)).config('--get', 'user.name') + self.assertEquals(input_value, output_value) + + def test_change_to_transform_kwargs_does_not_break_command_options(self): + self.git.log(n=1) diff --git a/git/test/test_remote.py b/git/test/test_remote.py index a7f1be22..b1248096 100644 --- a/git/test/test_remote.py +++ b/git/test/test_remote.py @@ -199,6 +199,10 @@ class TestRemote(TestBase): # ... with respec and no target res = fetch_and_test(remote, refspec='master') assert len(res) == 1 + + # ... multiple refspecs + res = fetch_and_test(remote, refspec=['master', 'fred']) + assert len(res) == 1 # add new tag reference rtag = TagReference.create(remote_repo, "1.0-RV_hello.there") diff --git a/git/util.py b/git/util.py index 7c257b37..88a72c0c 100644 --- a/git/util.py +++ b/git/util.py @@ -22,6 +22,10 @@ from gitdb.util import ( to_bin_sha ) +# Import the user database on unix based systems +if os.name == "posix": + import pwd + __all__ = ( "stream_copy", "join_path", "to_native_path_windows", "to_native_path_linux", "join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList", "BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists', @@ -113,12 +117,17 @@ def assure_directory_exists(path, is_file=False): def get_user_id(): """:return: string identifying the currently active system user as name@node - :note: user can be set with the 'USER' environment variable, usually set on windows""" - ukn = 'UNKNOWN' - username = os.environ.get('USER', os.environ.get('USERNAME', ukn)) - if username == ukn and hasattr(os, 'getlogin'): - username = os.getlogin() - # END get username from login + :note: user can be set with the 'USER' environment variable, usually set on windows + :note: on unix based systems you can use the password database + to get the login name of the effective process user""" + if os.name == "posix": + username = pwd.getpwuid(os.geteuid()).pw_name + else: + ukn = 'UNKNOWN' + username = os.environ.get('USER', os.environ.get('USERNAME', ukn)) + if username == ukn and hasattr(os, 'getlogin'): + username = os.getlogin() + # END get username from login return "%s@%s" % (username, platform.node()) #} END utilities @@ -73,7 +73,6 @@ setup(name = "GitPython", package_data = {'git.test' : ['fixtures/*']}, package_dir = {'git':'git'}, license = "BSD License", - requires=('gitdb (>=0.5.1)',), install_requires='gitdb >= 0.5.1', zip_safe=False, long_description = """\ |