summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2009-10-20 17:32:52 +0200
committerSebastian Thiel <byronimo@gmail.com>2009-10-20 17:32:52 +0200
commit13ac6b6aa35f01eb50590998e1e5e9a41d186046 (patch)
tree34119f1d188fbbba640e10105c60e62717484ec7
parent4c39f9da792792d4e73fc3a5effde66576ae128c (diff)
parentf4874ca00b5f6bcba3a62d5776a4b2da899c8846 (diff)
downloadgitpython-13ac6b6aa35f01eb50590998e1e5e9a41d186046.tar.gz
Merge commit 'origin/improvements_for_mainline' into integration
* commit 'origin/improvements_for_mainline': Moved compatibility information of possible future release into right spot ( to the top of the release list ) repo_tests: fixed duplicate test-method name which would redefine the previous one which never ran Fixed Diff class which used Commits instead of Blobs - as Blobs contain the path ( in the 'name' member variable ), the a|b_path members of Diff have been removed. Tests were adjusted and run git.git.Git.__init__ takes None as default argument as the execute method handles this correctly Fixed git.blob.Blob.blame function which would return the text-per-commit as individual characters improved repo documentation Improved head and tag object documentation slightly Added docs for the error module Added missing information to docstrings of commit and stats module improved git.cmd documentation Improved documentation on Actor and Blob
-rw-r--r--CHANGES14
-rw-r--r--lib/git/actor.py3
-rw-r--r--lib/git/blob.py19
-rw-r--r--lib/git/cmd.py56
-rw-r--r--lib/git/commit.py81
-rw-r--r--lib/git/diff.py55
-rw-r--r--lib/git/errors.py14
-rw-r--r--lib/git/head.py21
-rw-r--r--lib/git/repo.py80
-rw-r--r--lib/git/stats.py32
-rw-r--r--lib/git/tag.py25
-rw-r--r--test/git/test_blob.py8
-rw-r--r--test/git/test_commit.py55
-rw-r--r--test/git/test_repo.py72
14 files changed, 340 insertions, 195 deletions
diff --git a/CHANGES b/CHANGES
index 8c67a7e5..839ee15e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,20 @@
=======
CHANGES
=======
+
+0.1.X
+=====
+( Future Release )
+General
+-------
+* See changes in Diff class as your client code needs adjustments to work with it
+
+Diff
+----
+* Members a a_commit and b_commit renamed to a_blob and b_blob - they are populated
+ with Blob objects if possible
+* Members a_path and b_path removed as this information is kept in the blobs
+
0.1.6
=====
diff --git a/lib/git/actor.py b/lib/git/actor.py
index cdcc02f8..bc1a4479 100644
--- a/lib/git/actor.py
+++ b/lib/git/actor.py
@@ -7,6 +7,9 @@
import re
class Actor(object):
+ """Actors hold information about a person acting on the repository. They
+ can be committers and authors or anything with a name and an email as
+ mentioned in the git log entries."""
def __init__(self, name, email):
self.name = name
self.email = email
diff --git a/lib/git/blob.py b/lib/git/blob.py
index 82f92ce3..82a41f73 100644
--- a/lib/git/blob.py
+++ b/lib/git/blob.py
@@ -12,6 +12,7 @@ from actor import Actor
from commit import Commit
class Blob(object):
+ """A Blob encapsulates a git blob object"""
DEFAULT_MIME_TYPE = "text/plain"
def __init__(self, repo, id, mode=None, name=None):
@@ -48,6 +49,9 @@ class Blob(object):
Returns
int
+
+ NOTE
+ The size will be cached after the first access
"""
if self._size is None:
self._size = int(self.repo.git.cat_file(self.id, s=True).rstrip())
@@ -60,6 +64,9 @@ class Blob(object):
Returns
str
+
+ NOTE
+ The data will be cached after the first access.
"""
self.data_stored = self.data_stored or self.repo.git.cat_file(self.id, p=True, with_raw_output=True)
return self.data_stored
@@ -71,6 +78,9 @@ class Blob(object):
Returns
str
+
+ NOTE
+ Defaults to 'text/plain' in case the actual file type is unknown.
"""
guesses = None
if self.name:
@@ -79,6 +89,10 @@ class Blob(object):
@property
def basename(self):
+ """
+ Returns
+ The basename of the Blobs file name
+ """
return os.path.basename(self.name)
@classmethod
@@ -88,6 +102,9 @@ class Blob(object):
Returns
list: [git.Commit, list: [<line>]]
+ A list of tuples associating a Commit object with a list of lines that
+ changed within the given commit. The Commit objects will be given in order
+ of appearance.
"""
data = repo.git.blame(commit, '--', file, p=True)
commits = {}
@@ -135,7 +152,7 @@ class Blob(object):
m = re.search(r'^\t(.*)$', line)
text, = m.groups()
blames[-1][0] = c
- blames[-1][1] += text
+ blames[-1][1].append( text )
info = None
return blames
diff --git a/lib/git/cmd.py b/lib/git/cmd.py
index 9cbad673..aef53350 100644
--- a/lib/git/cmd.py
+++ b/lib/git/cmd.py
@@ -22,19 +22,47 @@ if sys.platform == 'win32':
class Git(object):
"""
- The Git class manages communication with the Git binary
+ The Git class manages communication with the Git binary.
+
+ It provides a convenient interface to calling the Git binary, such as in::
+
+ g = Git( git_dir )
+ g.init() # calls 'git init' program
+ rval = g.ls_files() # calls 'git ls-files' program
+
+ ``Debugging``
+ Set the GIT_PYTHON_TRACE environment variable print each invocation
+ of the command to stdout.
+ Set its value to 'full' to see details about the returned values.
"""
- def __init__(self, git_dir):
+ def __init__(self, git_dir=None):
+ """
+ Initialize this instance with:
+
+ ``git_dir``
+ Git directory we should work in. If None, we always work in the current
+ directory as returned by os.getcwd()
+ """
super(Git, self).__init__()
self.git_dir = git_dir
def __getattr__(self, name):
+ """
+ A convenience method as it allows to call the command as if it was
+ an object.
+ Returns
+ Callable object that will execute call _call_process with your arguments.
+ """
if name[:1] == '_':
raise AttributeError(name)
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
@property
def get_dir(self):
+ """
+ Returns
+ Git directory we are working on
+ """
return self.git_dir
def execute(self, command,
@@ -49,7 +77,9 @@ class Git(object):
the returned information (stdout)
``command``
- The command argument list to execute
+ The command argument list to execute.
+ It should be a string, or a sequence of program arguments. The
+ program to execute is the first item in the args sequence or string.
``istream``
Standard input filehandle passed to subprocess.Popen.
@@ -68,11 +98,18 @@ class Git(object):
``with_raw_output``
Whether to avoid stripping off trailing whitespace.
- Returns
- str(output) # extended_output = False (Default)
- tuple(int(status), str(output)) # extended_output = True
+ Returns::
+
+ str(output) # extended_output = False (Default)
+ tuple(int(status), str(stdout), str(stderr)) # extended_output = True
+
+ Raise
+ GitCommandError
+
+ NOTE
+ If you add additional keyword arguments to the signature of this method,
+ you must update the execute_kwargs tuple housed in this module.
"""
-
if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full':
print ' '.join(command)
@@ -146,7 +183,8 @@ class Git(object):
the result as a String
``method``
- is the command
+ is the command. Contained "_" characters will be converted to dashes,
+ such as in 'ls_files' to call 'ls-files'.
``args``
is the list of arguments
@@ -156,7 +194,7 @@ class Git(object):
This function accepts the same optional keyword arguments
as execute().
- Examples
+ Examples::
git.rev_list('master', max_count=10, header=True)
Returns
diff --git a/lib/git/commit.py b/lib/git/commit.py
index 2b19ea42..1cb863ca 100644
--- a/lib/git/commit.py
+++ b/lib/git/commit.py
@@ -14,38 +14,44 @@ import diff
import stats
class Commit(LazyMixin):
+ """
+ Wraps a git Commit object.
+
+ This class will act lazily on some of its attributes and will query the
+ value on demand only if it involves calling the git binary.
+ """
def __init__(self, repo, id, tree=None, author=None, authored_date=None,
committer=None, committed_date=None, message=None, parents=None):
"""
- Instantiate a new Commit
+ Instantiate a new Commit. All keyword arguments taking None as default will
+ be implicitly set if id names a valid sha.
+
+ The parameter documentation indicates the type of the argument after a colon ':'.
``id``
- is the id of the commit
+ is the sha id of the commit
- ``parents``
- is a list of commit ids (will be converted into Commit instances)
+ ``parents`` : list( Commit, ... )
+ is a list of commit ids
- ``tree``
- is the correspdonding tree id (will be converted into a Tree object)
+ ``tree`` : Tree
+ is the corresponding tree id
- ``author``
- is the author string
+ ``author`` : Actor
+ is the author string ( will be implicitly converted into an Actor object )
- ``authored_date``
+ ``authored_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst )
is the authored DateTime
- ``committer``
+ ``committer`` : Actor
is the committer string
- ``committed_date``
+ ``committed_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
is the committed DateTime
- ``message``
+ ``message`` : string
is the commit message
- ``parents``
- is the list of the parents of the commit
-
Returns
git.Commit
"""
@@ -68,6 +74,10 @@ class Commit(LazyMixin):
self.tree = Tree(repo, id=tree)
def __bake__(self):
+ """
+ Called by LazyMixin superclass when the first uninitialized member needs
+ to be set as it is queried.
+ """
temp = Commit.find_all(self.repo, self.id, max_count=1)[0]
self.parents = temp.parents
self.tree = temp.tree
@@ -79,10 +89,18 @@ class Commit(LazyMixin):
@property
def id_abbrev(self):
+ """
+ Returns
+ First 7 bytes of the commit's sha id as an abbreviation of the full string.
+ """
return self.id[0:7]
@property
def summary(self):
+ """
+ Returns
+ First line of the commit message.
+ """
return self.message.split('\n', 1)[0]
@classmethod
@@ -115,10 +133,11 @@ class Commit(LazyMixin):
is the ref from which to begin (SHA1 or name)
``path``
- is an optinal path
+ is an optinal path, if set only Commits that include the path
+ will be considered
- ``options``
- is a Hash of optional arguments to git where
+ ``kwargs``
+ optional keyword arguments to git where
``max_count`` is the maximum number of commits to fetch
``skip`` is the number of commits to skip
@@ -140,7 +159,7 @@ class Commit(LazyMixin):
is the Repo
``text``
- is the text output from the git command (raw format)
+ is the text output from the git-rev-list command (raw format)
Returns
git.Commit[]
@@ -173,7 +192,7 @@ class Commit(LazyMixin):
@classmethod
def diff(cls, repo, a, b=None, paths=None):
"""
- Show diffs between two trees:
+ Creates diffs between a tree and the index or between two trees:
``repo``
is the Repo
@@ -187,10 +206,13 @@ class Commit(LazyMixin):
given paths.
``paths``
- is a list of paths to limit the diff.
+ is a list of paths to limit the diff to.
Returns
- git.Diff[]
+ git.Diff[]::
+
+ between tree and the index if only a is given
+ between two trees if a and b are given and are commits
"""
paths = paths or []
@@ -209,6 +231,12 @@ class Commit(LazyMixin):
@property
def diffs(self):
+ """
+ Returns
+ git.Diff[]
+ Diffs between this commit and its first parent or all changes if this
+ commit is the first commit and has no parent.
+ """
if not self.parents:
d = self.repo.git.show(self.id, '-M', full_index=True, pretty='raw')
if re.search(r'diff --git a', d):
@@ -223,6 +251,13 @@ class Commit(LazyMixin):
@property
def stats(self):
+ """
+ Create a git stat from changes between this commit and its first parent
+ or from all changes done if this is the very first commit.
+
+ Return
+ git.Stats
+ """
if not self.parents:
text = self.repo.git.diff_tree(self.id, '--', numstat=True, root=True)
text2 = ""
@@ -247,7 +282,7 @@ class Commit(LazyMixin):
Parse out the actor (author or committer) info
Returns
- [str (actor name and email), time (acted at time)]
+ [Actor, gmtime(acted at time)]
"""
m = re.search(r'^.+? (.*) (\d+) .*$', line)
actor, epoch = m.groups()
diff --git a/lib/git/diff.py b/lib/git/diff.py
index 0216e061..44f55602 100644
--- a/lib/git/diff.py
+++ b/lib/git/diff.py
@@ -5,28 +5,44 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import re
-import commit
+import blob
class Diff(object):
"""
A Diff contains diff information between two commits.
+
+ It contains two sides a and b of the diff, members are prefixed with
+ "a" and "b" respectively to inidcate that.
+
+ Diffs keep information about the changed blob objects, the file mode, renames,
+ deletions and new files.
+
+ There are a few cases where None has to be expected as member variable value:
+
+ ``New File``::
+
+ a_mode is None
+ a_blob is None
+
+ ``Deleted File``::
+
+ b_mode is None
+ b_blob is NOne
"""
- def __init__(self, repo, a_path, b_path, a_commit, b_commit, a_mode,
+ def __init__(self, repo, a_path, b_path, a_blob, b_blob, a_mode,
b_mode, new_file, deleted_file, rename_from,
rename_to, diff):
self.repo = repo
- self.a_path = a_path
- self.b_path = b_path
- if not a_commit or re.search(r'^0{40}$', a_commit):
- self.a_commit = None
+ if not a_blob or re.search(r'^0{40}$', a_blob):
+ self.a_blob = None
else:
- self.a_commit = commit.Commit(repo, id=a_commit)
- if not b_commit or re.search(r'^0{40}$', b_commit):
- self.b_commit = None
+ self.a_blob = blob.Blob(repo, id=a_blob, mode=a_mode, name=a_path)
+ if not b_blob or re.search(r'^0{40}$', b_blob):
+ self.b_blob = None
else:
- self.b_commit = commit.Commit(repo, id=b_commit)
+ self.b_blob = blob.Blob(repo, id=b_blob, mode=b_mode, name=b_path)
self.a_mode = a_mode
self.b_mode = b_mode
@@ -39,6 +55,17 @@ class Diff(object):
@classmethod
def list_from_string(cls, repo, text):
+ """
+ Create a new diff object from the given text
+ ``repo``
+ is the repository we are operating on - it is required
+
+ ``text``
+ result of 'git diff' between two commits or one commit and the index
+
+ Returns
+ git.Diff[]
+ """
diffs = []
diff_header = re.compile(r"""
@@ -51,8 +78,8 @@ class Diff(object):
^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
(?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
(?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
- (?:^index[ ](?P<a_commit>[0-9A-Fa-f]+)
- \.\.(?P<b_commit>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
+ (?:^index[ ](?P<a_blob>[0-9A-Fa-f]+)
+ \.\.(?P<b_blob>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
""", re.VERBOSE | re.MULTILINE).match
for diff in ('\n' + text).split('\ndiff --git')[1:]:
@@ -60,10 +87,10 @@ class Diff(object):
a_path, b_path, similarity_index, rename_from, rename_to, \
old_mode, new_mode, new_file_mode, deleted_file_mode, \
- a_commit, b_commit, b_mode = header.groups()
+ a_blob, b_blob, b_mode = header.groups()
new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode)
- diffs.append(Diff(repo, a_path, b_path, a_commit, b_commit,
+ diffs.append(Diff(repo, a_path, b_path, a_blob, b_blob,
old_mode or deleted_file_mode, new_mode or new_file_mode or b_mode,
new_file, deleted_file, rename_from, rename_to, diff[header.end():]))
diff --git a/lib/git/errors.py b/lib/git/errors.py
index bf882d33..2632d5f3 100644
--- a/lib/git/errors.py
+++ b/lib/git/errors.py
@@ -3,14 +3,24 @@
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+"""
+Module containing all exceptions thrown througout the git package,
+"""
class InvalidGitRepositoryError(Exception):
- pass
+ """
+ Thrown if the given repository appears to have an invalid format.
+ """
class NoSuchPathError(Exception):
- pass
+ """
+ Thrown if a path could not be access by the system.
+ """
class GitCommandError(Exception):
+ """
+ Thrown if execution of the git command fails with non-zero status code.
+ """
def __init__(self, command, status, stderr=None):
self.stderr = stderr
self.status = status
diff --git a/lib/git/head.py b/lib/git/head.py
index 86686f17..639cee40 100644
--- a/lib/git/head.py
+++ b/lib/git/head.py
@@ -28,16 +28,13 @@ class Head(object):
def __init__(self, name, commit):
"""
- Instantiate a new Head
+ Initialize a newly instanced Head
`name`
is the name of the head
`commit`
- is the Commit that the head points to
-
- Returns
- git.Head
+ is the Commit object that the head points to
"""
self.name = name
self.commit = commit
@@ -45,16 +42,19 @@ class Head(object):
@classmethod
def find_all(cls, repo, **kwargs):
"""
- Find all Heads
+ Find all Heads in the repository
`repo`
is the Repo
`kwargs`
- is a dict of options
+ Additional options given as keyword arguments, will be passed
+ to git-for-each-ref
Returns
git.Head[]
+
+ List is sorted by committerdate
"""
options = {'sort': "committerdate",
@@ -67,12 +67,12 @@ class Head(object):
@classmethod
def list_from_string(cls, repo, text):
"""
- Parse out head information into an array of baked head objects
+ Parse out head information into a list of head objects
``repo``
is the Repo
``text``
- is the text output from the git command
+ is the text output from the git-for-each-ref command
Returns
git.Head[]
@@ -95,7 +95,8 @@ class Head(object):
``line``
is the formatted head information
- Format
+ Format::
+
name: [a-zA-Z_/]+
<null byte>
id: [0-9A-Fa-f]{40}
diff --git a/lib/git/repo.py b/lib/git/repo.py
index b7ffcb61..1c4b4095 100644
--- a/lib/git/repo.py
+++ b/lib/git/repo.py
@@ -18,6 +18,11 @@ from commit import Commit
from tree import Tree
class Repo(object):
+ """
+ Represents a git repository and allows you to query references,
+ gather commit information, generate diffs, create and clone repositories query
+ the log.
+ """
DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
def __init__(self, path=None):
@@ -32,6 +37,9 @@ class Repo(object):
repo = Repo("/Users/mtrier/Development/git-python")
repo = Repo("/Users/mtrier/Development/git-python.git")
+ Raises
+ InvalidGitRepositoryError or NoSuchPathError
+
Returns
``git.Repo``
"""
@@ -110,13 +118,15 @@ class Repo(object):
is the branch/commit name (default 'master')
``path``
- is an optional path
+ is an optional path to limit the returned commits to
+ Commits that do not contain that path will not be returned.
``max_count``
is the maximum number of commits to return (default 10)
``skip``
- is the number of commits to skip (default 0)
+ is the number of commits to skip (default 0) which will effectively
+ move your commit-window by the given number.
Returns
``git.Commit[]``
@@ -126,7 +136,7 @@ class Repo(object):
return Commit.find_all(self, start, path, **options)
- def commits_between(self, frm, to, path = ''):
+ def commits_between(self, frm, to):
"""
The Commits objects that are reachable via ``to`` but not via ``frm``
Commits are returned in chronological order.
@@ -137,9 +147,6 @@ class Repo(object):
``to``
is the branch/commit name of the older item
- ``path``
- is an optional path
-
Returns
``git.Commit[]``
"""
@@ -154,7 +161,8 @@ class Repo(object):
is the branch/commit name (default 'master')
``path``
- is an optinal path
+ is an optinal path to limit the returned commits to.
+
``since``
is a string represeting a date/time
@@ -174,10 +182,11 @@ class Repo(object):
is the branch/commit name (default 'master')
``path``
- is an optinal path
+ is an optional path
+ Commits that do not contain the path will not contribute to the count.
Returns
- int
+ ``int``
"""
return Commit.count(self, start, path)
@@ -189,17 +198,17 @@ class Repo(object):
is the SHA1 identifier of the commit
``path``
- is an optinal path
+ is an optional path, if set the returned commit must contain the path.
Returns
- git.Commit
+ ``git.Commit``
"""
options = {'max_count': 1}
commits = Commit.find_all(self, id, path, **options)
if not commits:
- raise ValueError, 'Invalid identifier %s' % id
+ raise ValueError, "Invalid identifier %s, or given path '%s' too restrictive" % ( id, path )
return commits[0]
def commit_deltas_from(self, other_repo, ref='master', other_ref='master'):
@@ -207,7 +216,7 @@ class Repo(object):
Returns a list of commits that is in ``other_repo`` but not in self
Returns
- ``git.Commit[]``
+ git.Commit[]
"""
repo_refs = self.git.rev_list(ref, '--').strip().splitlines()
other_repo_refs = other_repo.git.rev_list(other_ref, '--').strip().splitlines()
@@ -246,7 +255,11 @@ class Repo(object):
def log(self, commit='master', path=None, **kwargs):
"""
- The commit log for a treeish
+ The Commit for a treeish, and all commits leading to it.
+
+ ``kwargs``
+ keyword arguments specifying flags to be used in git-log command,
+ i.e.: max_count=1 to limit the amount of commits returned
Returns
``git.Commit[]``
@@ -270,6 +283,9 @@ class Repo(object):
``paths``
is an optional list of file paths on which to restrict the diff
+
+ Returns
+ ``str``
"""
return self.git.diff(a, b, '--', *paths)
@@ -296,7 +312,7 @@ class Repo(object):
already exists. Creates the directory with a mode=0755.
``kwargs``
- is any additional options to the git init command
+ keyword arguments serving as additional options to the git init command
Examples::
@@ -321,8 +337,8 @@ class Repo(object):
``path``
is the full path of the new repo (traditionally ends with /<name>.git)
- ``options``
- is any additional options to the git clone command
+ ``kwargs``
+ keyword arguments to be given to the git clone command
Returns
``git.Repo`` (the newly forked repo)
@@ -340,7 +356,7 @@ class Repo(object):
is the treeish name/id (default 'master')
``prefix``
- is the optional prefix
+ is the optional prefix to prepend to each filename in the archive
Examples::
@@ -351,10 +367,10 @@ class Repo(object):
<String containing tar archive for commit a87ff14>
>>> repo.archive_tar('master', 'myproject/')
- <String containing tar archive and prefixed with 'myproject/'>
+ <String containing tar bytes archive, whose files are prefixed with 'myproject/'>
Returns
- str (containing tar archive)
+ str (containing bytes of tar archive)
"""
options = {}
if prefix:
@@ -369,7 +385,7 @@ class Repo(object):
is the treeish name/id (default 'master')
``prefix``
- is the optional prefix
+ is the optional prefix to prepend to each filename in the archive
Examples::
@@ -383,7 +399,7 @@ class Repo(object):
<String containing tar.gz archive and prefixed with 'myproject/'>
Returns
- str (containing tar.gz archive)
+ str (containing the bytes of tar.gz archive)
"""
kwargs = {}
if prefix:
@@ -408,16 +424,16 @@ class Repo(object):
os.unlink(filename)
daemon_export = property(_get_daemon_export, _set_daemon_export,
- doc="git-daemon export of this repository")
+ doc="If True, git-daemon may export this repository")
del _get_daemon_export
del _set_daemon_export
def _get_alternates(self):
"""
- The list of alternates for this repo
+ The list of alternates for this repo from which objects can be retrieved
Returns
- list[str] (pathnames of alternates)
+ list of strings being pathnames of alternates
"""
alternates_path = os.path.join(self.path, 'objects', 'info', 'alternates')
@@ -436,8 +452,12 @@ class Repo(object):
Sets the alternates
``alts``
- is the Array of String paths representing the alternates
+ is the array of string paths representing the alternates at which
+ git should look for objects, i.e. /home/user/repo/.git/objects
+ Raises
+ NoSuchPathError
+
Returns
None
"""
@@ -454,17 +474,19 @@ class Repo(object):
finally:
f.close()
- alternates = property(_get_alternates, _set_alternates)
+ alternates = property(_get_alternates, _set_alternates, doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")
@property
def is_dirty(self):
"""
- Return the status of the working directory.
+ Return the status of the index.
Returns
- ``True``, if the working directory has any uncommitted changes,
+ ``True``, if the index has any uncommitted changes,
otherwise ``False``
+ NOTE
+ Working tree changes that have not been staged will not be detected !
"""
if self.bare:
# Bare repositories with no associated working directory are
diff --git a/lib/git/stats.py b/lib/git/stats.py
index 74a23126..307e2f2f 100644
--- a/lib/git/stats.py
+++ b/lib/git/stats.py
@@ -5,6 +5,32 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
class Stats(object):
+ """
+ Represents stat information as presented by git at the end of a merge. It is
+ created from the output of a diff operation.
+
+ ``Example``::
+
+ c = Commit( sha1 )
+ s = c.stats
+ s.total # full-stat-dict
+ s.files # dict( filepath : stat-dict )
+
+ ``stat-dict``
+
+ A dictionary with the following keys and values::
+
+ deletions = number of deleted lines as int
+ insertions = number of inserted lines as int
+ lines = total number of lines changed as int, or deletions + insertions
+
+ ``full-stat-dict``
+
+ In addition to the items in the stat-dict, it features additional information::
+
+ files = number of changed files as int
+
+ """
def __init__(self, repo, total, files):
self.repo = repo
self.total = total
@@ -12,6 +38,12 @@ class Stats(object):
@classmethod
def list_from_string(cls, repo, text):
+ """
+ Create a Stat object from output retrieved by git-diff.
+
+ Returns
+ git.Stat
+ """
hsh = {'total': {'insertions': 0, 'deletions': 0, 'lines': 0, 'files': 0}, 'files': {}}
for line in text.splitlines():
(raw_insertions, raw_deletions, filename) = line.split("\t")
diff --git a/lib/git/tag.py b/lib/git/tag.py
index f7bc140e..8413ce73 100644
--- a/lib/git/tag.py
+++ b/lib/git/tag.py
@@ -9,16 +9,13 @@ from commit import Commit
class Tag(object):
def __init__(self, name, commit):
"""
- Instantiate a new Tag
+ Initialize a newly instantiated Tag
``name``
is the name of the head
``commit``
is the Commit that the head points to
-
- Returns
- ``git.Tag``
"""
self.name = name
self.commit = commit
@@ -26,16 +23,19 @@ class Tag(object):
@classmethod
def find_all(cls, repo, **kwargs):
"""
- Find all Tags
+ Find all Tags in the repository
``repo``
is the Repo
``kwargs``
- is a dict of options
+ Additional options given as keyword arguments, will be passed
+ to git-for-each-ref
Returns
``git.Tag[]``
+
+ List is sorted by committerdate
"""
options = {'sort': "committerdate",
'format': "%(refname)%00%(objectname)"}
@@ -47,16 +47,16 @@ class Tag(object):
@classmethod
def list_from_string(cls, repo, text):
"""
- Parse out tag information into an array of baked Tag objects
+ Parse out tag information into an array of Tag objects
``repo``
is the Repo
``text``
- is the text output from the git command
+ is the text output from the git-for-each command
Returns
- ``git.Tag[]``
+ git.Tag[]
"""
tags = []
for line in text.splitlines():
@@ -74,13 +74,14 @@ class Tag(object):
``line``
is the formatted tag information
- Format
+ Format::
+
name: [a-zA-Z_/]+
<null byte>
id: [0-9A-Fa-f]{40}
-
+
Returns
- ``git.Tag``
+ git.Tag
"""
full_name, ids = line.split("\x00")
name = full_name.split("/")[-1]
diff --git a/test/git/test_blob.py b/test/git/test_blob.py
index 8f83f391..5bd74ff7 100644
--- a/test/git/test_blob.py
+++ b/test/git/test_blob.py
@@ -69,6 +69,7 @@ class TestBlob(object):
git.return_value = fixture('blame')
b = Blob.blame(self.repo, 'master', 'lib/git.py')
assert_equal(13, len(b))
+ assert_equal( 2, len(b[0]) )
# assert_equal(25, reduce(lambda acc, x: acc + len(x[-1]), b))
assert_equal(hash(b[0][0]), hash(b[9][0]))
c = b[0][0]
@@ -83,6 +84,13 @@ class TestBlob(object):
assert_equal('tom@mojombo.com', c.committer.email)
assert_equal(time.gmtime(1191997100), c.committed_date)
assert_equal('initial grit setup', c.message)
+
+ # test the 'lines per commit' entries
+ tlist = b[0][1]
+ assert_true( tlist )
+ assert_true( isinstance( tlist[0], basestring ) )
+ assert_true( len( tlist ) < sum( len(t) for t in tlist ) ) # test for single-char bug
+
def test_should_return_appropriate_representation(self):
blob = Blob(self.repo, **{'id': 'abc'})
diff --git a/test/git/test_commit.py b/test/git/test_commit.py
index 3e37a7a4..c36d0c72 100644
--- a/test/git/test_commit.py
+++ b/test/git/test_commit.py
@@ -37,18 +37,19 @@ class TestCommit(object):
assert_equal(15, len(diffs))
- assert_equal('.gitignore', diffs[0].a_path)
- assert_equal('.gitignore', diffs[0].b_path)
- assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_commit.id)
- assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_commit.id)
- assert_equal('100644', diffs[0].b_mode)
+ assert_equal('.gitignore', diffs[0].a_blob.name)
+ assert_equal('.gitignore', diffs[0].b_blob.name)
+ assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_blob.id)
+ assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_blob.id)
+ assert_equal('100644', diffs[0].b_blob.mode)
assert_equal(False, diffs[0].new_file)
assert_equal(False, diffs[0].deleted_file)
assert_equal("--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1,2 @@\n coverage\n+pkg", diffs[0].diff)
- assert_equal('lib/grit/actor.rb', diffs[5].a_path)
- assert_equal(None, diffs[5].a_commit)
- assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_commit.id)
+ assert_equal('lib/grit/actor.rb', diffs[5].b_blob.name)
+ assert_equal(None, diffs[5].a_blob)
+ assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_blob.id)
+ assert_equal( None, diffs[5].a_mode )
assert_equal(True, diffs[5].new_file)
assert_true(git.called)
@@ -88,7 +89,7 @@ class TestCommit(object):
diffs = Commit.diff(self.repo, '59ddc32', ['lib'])
assert_equal(1, len(diffs))
- assert_equal('lib/grit/diff.rb', diffs[0].a_path)
+ assert_equal('lib/grit/diff.rb', diffs[0].a_blob.name)
assert_true(git.called)
assert_equal(git.call_args, (('diff', '-M', '59ddc32', '--', 'lib'), {'full_index': True}))
@@ -100,7 +101,7 @@ class TestCommit(object):
diffs = Commit.diff(self.repo, '59ddc32', '13d27d5', ['lib'])
assert_equal(1, len(diffs))
- assert_equal('lib/grit/commit.rb', diffs[0].a_path)
+ assert_equal('lib/grit/commit.rb', diffs[0].a_blob.name)
assert_true(git.called)
assert_equal(git.call_args, (('diff', '-M', '59ddc32', '13d27d5', '--', 'lib'), {'full_index': True}))
@@ -114,18 +115,18 @@ class TestCommit(object):
assert_equal(15, len(diffs))
- assert_equal('.gitignore', diffs[0].a_path)
- assert_equal('.gitignore', diffs[0].b_path)
- assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_commit.id)
- assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_commit.id)
- assert_equal('100644', diffs[0].b_mode)
+ assert_equal('.gitignore', diffs[0].a_blob.name)
+ assert_equal('.gitignore', diffs[0].b_blob.name)
+ assert_equal('4ebc8aea50e0a67e000ba29a30809d0a7b9b2666', diffs[0].a_blob.id)
+ assert_equal('2dd02534615434d88c51307beb0f0092f21fd103', diffs[0].b_blob.id)
+ assert_equal('100644', diffs[0].b_blob.mode)
assert_equal(False, diffs[0].new_file)
assert_equal(False, diffs[0].deleted_file)
assert_equal("--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1,2 @@\n coverage\n+pkg", diffs[0].diff)
- assert_equal('lib/grit/actor.rb', diffs[5].a_path)
- assert_equal(None, diffs[5].a_commit)
- assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_commit.id)
+ assert_equal('lib/grit/actor.rb', diffs[5].b_blob.name)
+ assert_equal(None, diffs[5].a_blob)
+ assert_equal('f733bce6b57c0e5e353206e692b0e3105c2527f4', diffs[5].b_blob.id)
assert_equal(True, diffs[5].new_file)
assert_true(git.called)
@@ -144,18 +145,17 @@ class TestCommit(object):
assert_equal(10, len(diffs))
- assert_equal('History.txt', diffs[0].a_path)
- assert_equal('History.txt', diffs[0].b_path)
- assert_equal(None, diffs[0].a_commit)
- assert_equal('100644', diffs[0].b_mode)
- assert_equal('81d2c27608b352814cbe979a6acd678d30219678', diffs[0].b_commit.id)
+ assert_equal('History.txt', diffs[0].b_blob.name)
+ assert_equal(None, diffs[0].a_blob)
+ assert_equal('100644', diffs[0].b_blob.mode)
+ assert_equal('81d2c27608b352814cbe979a6acd678d30219678', diffs[0].b_blob.id)
assert_equal(True, diffs[0].new_file)
assert_equal(False, diffs[0].deleted_file)
assert_equal("--- /dev/null\n+++ b/History.txt\n@@ -0,0 +1,5 @@\n+== 1.0.0 / 2007-10-09\n+\n+* 1 major enhancement\n+ * Birthday!\n+", diffs[0].diff)
- assert_equal('lib/grit.rb', diffs[5].a_path)
- assert_equal(None, diffs[5].a_commit)
- assert_equal('32cec87d1e78946a827ddf6a8776be4d81dcf1d1', diffs[5].b_commit.id)
+ assert_equal('lib/grit.rb', diffs[5].b_blob.name)
+ assert_equal(None, diffs[5].a_blob)
+ assert_equal('32cec87d1e78946a827ddf6a8776be4d81dcf1d1', diffs[5].b_blob.id)
assert_equal(True, diffs[5].new_file)
assert_true(git.called)
@@ -181,7 +181,10 @@ class TestCommit(object):
commit.__bake_it__()
diffs = commit.diffs
+ # in case of mode-only changes, there is no blob
assert_equal(23, len(diffs))
+ assert_equal(None, diffs[0].a_blob)
+ assert_equal(None, diffs[0].b_blob)
assert_equal('100644', diffs[0].a_mode)
assert_equal('100755', diffs[0].b_mode)
diff --git a/test/git/test_repo.py b/test/git/test_repo.py
index 6b82d029..82f27001 100644
--- a/test/git/test_repo.py
+++ b/test/git/test_repo.py
@@ -185,7 +185,7 @@ class TestRepo(object):
assert_equal(git.call_args, (('diff', 'master^', 'master', '--', 'foo/bar', 'foo/baz'), {}))
@patch_object(Git, '_call_process')
- def test_diff(self, git):
+ def test_diff_with_parents(self, git):
git.return_value = fixture('diff_p')
diffs = self.repo.commit_diff('master')
@@ -193,10 +193,10 @@ class TestRepo(object):
assert_true(git.called)
def test_archive_tar(self):
- self.repo.archive_tar
+ self.repo.archive_tar()
def test_archive_tar_gz(self):
- self.repo.archive_tar_gz
+ self.repo.archive_tar_gz()
@patch('git.utils.touch')
def test_enable_daemon_serve(self, touch):
@@ -207,19 +207,6 @@ class TestRepo(object):
self.repo.daemon_serve = True
assert_true(self.repo.daemon_serve)
- # @patch_object(os.path, 'exists')
- # @patch_object('__builtin__', 'open')
- # def test_alternates_with_two_alternates(self, exists, read):
- # # File.expects(:exist?).with("#{absolute_project_path}/.git/objects/info/alternates").returns(true)
- # # File.expects(:read).returns("/path/to/repo1/.git/objects\n/path/to/repo2.git/objects\n")
- # exists.return_value = True
- # read.return_value = ("/path/to/repo1/.git/objects\n/path/to/repo2.git/objects\n")
- #
- # assert_equal(["/path/to/repo1/.git/objects", "/path/to/repo2.git/objects"], self.repo.alternates)
- #
- # assert_true(exists.called)
- # assert_true(read.called)
- #
@patch_object(os.path, 'exists')
def test_alternates_no_file(self, os):
os.return_value = False
@@ -227,32 +214,6 @@ class TestRepo(object):
assert_true(os.called)
- # @patch_object(os.path, 'exists')
- # def test_alternates_setter_ok(self, os):
- # os.return_value = True
- # alts = ['/path/to/repo.git/objects', '/path/to/repo2.git/objects']
- #
- # # File.any_instance.expects(:write).with(alts.join("\n"))
- #
- # self.repo.alternates = alts
- #
- # assert_true(os.called)
- # # assert_equal(os.call_args, ((alts,), {}))
- # # for alt in alts:
- #
- # @patch_object(os.path, 'exists')
- # @raises(NoSuchPathError)
- # def test_alternates_setter_bad(self, os):
- # os.return_value = False
- #
- # alts = ['/path/to/repo.git/objects']
- # # File.any_instance.expects(:write).never
- # self.repo.alternates = alts
- #
- # for alt in alts:
- # assert_true(os.called)
- # assert_equal(os.call_args, (alt, {}))
-
@patch_object(os, 'remove')
def test_alternates_setter_empty(self, os):
self.repo.alternates = []
@@ -278,33 +239,6 @@ class TestRepo(object):
assert_true(git.called)
assert_equal(git.call_args, (('log', 'master', '--', 'file.rb'), {'pretty': 'raw', 'max_count': 1}))
- # @patch_object(Git, '_call_process')
- # @patch_object(Git, '_call_process')
- # def test_commit_deltas_from_nothing_new(self, gitb, gita):
- # gitb.return_value = fixture("rev_list_delta_b")
- # gita.return_value = fixture("rev_list_delta_a")
- # other_repo = Repo(GIT_REPO)
- # # self.repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_b"))
- # # other_repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_a"))
- #
- # delta_commits = self.repo.commit_deltas_from(other_repo)
- # assert_equal(0, len(delta_commits))
- # assert_true(gitb.called)
- # assert_equal(gitb.call_args, (('rev_list', 'master'), {}))
- # assert_true(gita.called)
- # assert_equal(gita.call_args, (('rev_list', 'master'), {}))
- #
- # def test_commit_deltas_from_when_other_has_new(self):
- # other_repo = Repo(GIT_REPO)
- # # self.repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_a"))
- # # other_repo.git.expects(:rev_list).with({}, "master").returns(fixture("rev_list_delta_b"))
- # # for ref in ['4c8124ffcf4039d292442eeccabdeca5af5c5017',
- # # '634396b2f541a9f2d58b00be1a07f0c358b999b3',
- # # 'ab25fd8483882c3bda8a458ad2965d2248654335']:
- # # Commit.expects(:find_all).with(other_repo, ref, :max_count => 1).returns([stub()])
- # delta_commits = self.repo.commit_deltas_from(other_repo)
- # assert_equal(3, len(delta_commits))
-
def test_is_dirty_with_bare_repository(self):
self.repo.bare = True
assert_false(self.repo.is_dirty)