def __init__(self, name, email):
self.name = name
self.email = email
-
+
def __str__(self):
return self.name
-
+
def __repr__(self):
return '<GitPython.Actor "%s <%s>">' % (self.name, self.email)
-
+
@classmethod
def from_string(cls, string):
- """
+ """
Create an Actor from a string.
-
+
``str``
is the string, which is expected to be in regular git format
-
+
Format
John Doe <jdoe@example.com>
- Returns
+ Returns
Actor
"""
if re.search(r'<.+>', string):
class Blob(object):
DEFAULT_MIME_TYPE = "text/plain"
-
+
def __init__(self, repo, **kwargs):
"""
Create an unbaked Blob containing just the specified attributes
-
- ``repo``
+
+ ``repo``
is the Repo
-
+
``atts``
is a dict of instance variable data
self.name = None
self._size = None
self.data_stored = None
-
+
self.repo = repo
for k, v in kwargs.items():
setattr(self, k, v)
-
+
@property
def size(self):
"""
if self._size is None:
self._size = int(self.repo.git.cat_file(self.id, **{'s': True}).rstrip())
return self._size
-
+
@property
def data(self):
"""
if self.name:
guesses = mimetypes.guess_type(self.name)
return guesses and guesses[0] or self.DEFAULT_MIME_TYPE
-
+
@property
def basename(self):
return os.path.basename(self.name)
commits = {}
blames = []
info = None
-
+
for line in data.splitlines():
parts = re.split(r'\s+', line, 1)
if re.search(r'^[0-9A-Fa-f]{40}$', parts[0]):
'committed_date': info['committer_date'],
'message': info['summary']})
commits[info['id']] = c
-
+
m = re.search(r'^\t(.*)$', line)
text, = m.groups()
blames[-1][0] = c
blames[-1][1] += text
info = None
-
+
return blames
-
+
def __repr__(self):
return '<GitPython.Blob "%s">' % self.id
def __init__(self, repo, **kwargs):
"""
Instantiate a new Commit
-
+
``id``
is the id of the commit
-
+
``parents``
is a list of commit ids (will be converted into Commit instances)
-
+
``tree``
is the correspdonding tree id (will be converted into a Tree object)
-
+
``author``
is the author string
-
+
``authored_date``
is the authored DateTime
-
+
``committer``
is the committer string
-
- ``committed_date``
+
+ ``committed_date``
is the committed DateTime
-
+
``message``
is the first line of the commit message
-
- Returns
+
+ Returns
GitPython.Commit
"""
LazyMixin.__init__(self)
-
+
self.repo = repo
self.id = None
self.tree = None
self.committed_date = None
self.message = None
self.parents = None
-
+
for k, v in kwargs.items():
setattr(self, k, v)
-
+
if self.id:
if 'parents' in kwargs:
self.parents = map(lambda p: Commit(repo, **{'id': p}), kwargs['parents'])
@property
def id_abbrev(self):
return self.id[0:7]
-
+
@classmethod
def count(cls, repo, ref):
"""
Count the number of commits reachable from this ref
-
+
``repo``
is the Repo
-
+
``ref``
is the ref from which to begin (SHA1 or name)
-
+
Returns
int
"""
Find all commits matching the given criteria.
``repo``
is the Repo
-
+
``ref``
is the ref from which to begin (SHA1 or name)
-
+
``options``
is a Hash of optional arguments to git where
``max_count`` is the maximum number of commits to fetch
``skip`` is the number of commits to skip
-
+
Returns
GitPython.Commit[]
"""
options = {'pretty': 'raw'}
options.update(kwargs)
-
+
output = repo.git.rev_list(ref, **options)
return cls.list_from_string(repo, output)
def list_from_string(cls, repo, text):
"""
Parse out commit information into a list of Commit objects
-
+
``repo``
is the Repo
-
+
``text``
is the text output from the git command (raw format)
GitPython.Commit[]
"""
lines = [l for l in text.splitlines() if l.strip()]
-
+
commits = []
-
+
while lines:
id = lines.pop(0).split()[-1]
tree = lines.pop(0).split()[-1]
-
+
parents = []
while lines and re.search(r'^parent', lines[0]):
parents.append(lines.pop(0).split()[-1])
author, authored_date = cls.actor(lines.pop(0))
committer, committed_date = cls.actor(lines.pop(0))
-
+
messages = []
while lines and re.search(r'^ {4}', lines[0]):
messages.append(lines.pop(0).strip())
-
+
message = messages and messages[0] or ''
-
+
commits.append(Commit(repo, id=id, parents=parents, tree=tree, author=author, authored_date=authored_date,
committer=committer, committed_date=committed_date, message=message))
-
+
return commits
-
+
@classmethod
def diff(cls, repo, a, b = None, paths = []):
"""
Show diffs between two trees:
-
+
``repo``
is the Repo
-
+
``a``
is a named commit
-
+
``b``
- is an optional named commit. Passing a list assumes you
- wish to omit the second named commit and limit the diff to the
+ is an optional named commit. Passing a list assumes you
+ wish to omit the second named commit and limit the diff to the
given paths.
-
+
``paths``
is a list of paths to limit the diff.
if isinstance(b, list):
paths = b
b = None
-
+
if paths:
paths.insert(0, "--")
-
+
if b:
paths.insert(0, b)
paths.insert(0, a)
d = ''
return diff.Diff.list_from_string(self.repo, d)
else:
- return self.diff(self.repo, self.parents[0].id, self.id)
-
+ return self.diff(self.repo, self.parents[0].id, self.id)
+
@property
def stats(self):
if not self.parents:
else:
text = self.repo.git.diff(self.parents[0].id, self.id, **{'numstat': True})
return stats.Stats.list_from_string(self.repo, text)
-
+
def __str__(self):
""" Convert commit to string which is SHA1 """
return self.id
-
+
def __repr__(self):
return '<GitPython.Commit "%s">' % self.id
-
+
@classmethod
def actor(cls, line):
"""
"""
A Diff contains diff information between two commits.
"""
-
+
def __init__(self, repo, a_path, b_path, a_commit, b_commit, a_mode, b_mode, new_file, deleted_file, 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
else:
self.b_commit = None
else:
self.b_commit = commit.Commit(repo, **{'id': b_commit})
-
+
self.a_mode = a_mode
self.b_mode = b_mode
self.new_file = new_file
self.deleted_file = deleted_file
self.diff = diff
-
+
@classmethod
def list_from_string(cls, repo, text):
lines = text.splitlines()
if re.search(r'^diff --git', lines[0]):
diffs.append(Diff(repo, a_path, b_path, None, None, a_mode, b_mode, False, False, None))
continue
-
+
new_file = False
deleted_file = False
-
+
if re.search(r'^new file', lines[0]):
m = re.search(r'^new file mode (.+)', lines.pop(0))
if m:
a_mode, = m.groups()
b_mode = None
deleted_file = True
-
+
m = re.search(r'^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$', lines.pop(0))
if m:
a_commit, b_commit, b_mode = m.groups()
if b_mode:
b_mode = b_mode.strip()
-
+
diff_lines = []
while lines and not re.search(r'^diff', lines[0]):
diff_lines.append(lines.pop(0))
-
+
diff = "\n".join(diff_lines)
diffs.append(Diff(repo, a_path, b_path, a_commit, b_commit, a_mode, b_mode, new_file, deleted_file, diff))
-
- return diffs
\ No newline at end of file
+
+ return diffs
"""
A Head is a named reference to a Commit. Every Head instance contains a name
and a Commit object.
-
+
Examples::
-
+
>>> repo = Repo("/path/to/repo")
>>> head = repo.heads[0]
-
+
>>> head.name
'master'
-
+
>>> head.commit
<GitPython.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
-
+
>>> head.commit.id
'1c09f116cbc2cb4100fb6935bb162daa4723f455'
"""
-
+
def __init__(self, name, commit):
"""
Instantiate a new Head
-
+
`name`
is the name of the head
-
+
`commit`
is the Commit that the head points to
-
+
Returns
GitPython.Head
"""
self.name = name
self.commit = commit
-
+
@classmethod
def find_all(cls, repo, **kwargs):
"""
Find all Heads
-
+
`repo`
is the Repo
-
+
`kwargs`
is a dict of options
-
+
Returns
GitPython.Head[]
"""
options = {'sort': "committerdate",
'format': "%(refname)%00%(objectname)"}
options.update(kwargs)
-
- output = repo.git.for_each_ref("refs/heads", **options)
+
+ output = repo.git.for_each_ref("refs/heads", **options)
return cls.list_from_string(repo, output)
@classmethod
def list_from_string(cls, repo, text):
"""
Parse out head information into an array of baked head objects
-
+
``repo``
is the Repo
``text``
GitPython.Head[]
"""
heads = []
-
+
for line in text.splitlines():
heads.append(cls.from_string(repo, line))
-
+
return heads
@classmethod
def from_string(cls, repo, line):
"""
Create a new Head instance from the given string.
-
+
``repo``
is the Repo
-
+
``line``
is the formatted head information
name: [a-zA-Z_/]+
<null byte>
id: [0-9A-Fa-f]{40}
-
+
Returns
GitPython.Head
"""
name = full_name.split("/")[-1]
c = commit.Commit(repo, **{'id': ids})
return Head(name, c)
-
+
def __repr__(self):
return '<GitPython.Head "%s">' % self.name
class LazyMixin(object):
lazy_properties = []
-
+
def __init__(self):
self.__baked__ = False
return
self.__bake__()
self.__baked__ = True
-
+
def __bake_it__(self):
self.__baked__ = True
class MethodMissingMixin(object):
- """
- A Mixin' to implement the 'method_missing' Ruby-like protocol.
-
+ """
+ A Mixin' to implement the 'method_missing' Ruby-like protocol.
+
This was `taken from a blog post <http://blog.iffy.us/?p=43>`_
- """
+ """
def __getattribute__(self, attr):
try:
return object.__getattribute__(self, attr)
def __call__(self, *args, **kwargs):
return self.__wrapped__.method_missing(self.__method__, *args, **kwargs)
return MethodMissing(self, attr)
-
+
def method_missing(self, *args, **kwargs):
""" This method should be overridden in the derived class. """
raise NotImplementedError(str(self.__wrapped__) + " 'method_missing' method has not been implemented.")
class Repo(object):
DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
-
+
def __init__(self, path):
"""
Create a new Repo instance
-
+
``path``
is the path to either the root git directory or the bare git repo
-
+
Examples::
-
+
repo = Repo("/Users/mtrier/Development/git-python")
repo = Repo("/Users/mtrier/Development/git-python.git")
-
+
Returns
``GitPython.Repo``
"""
epath = os.path.abspath(path)
-
+
if os.path.exists(os.path.join(epath, '.git')):
self.path = os.path.join(epath, '.git')
self.bare = False
else:
raise NoSuchPathError(epath)
self.git = Git(self.path)
-
+
@property
def description(self):
"""
return result.rstrip()
finally:
f.close()
-
+
@property
def heads(self):
"""
A list of ``Head`` objects representing the branch heads in
this repo
-
+
Returns
``GitPython.Head[]``
"""
# alias heads
branches = heads
-
+
@property
def tags(self):
"""
``GitPython.Tag[]``
"""
return Tag.find_all(self)
-
+
def commits(self, start = 'master', max_count = 10, skip = 0):
"""
A list of Commit objects representing the history of a given ref/commit
-
+
``start``
is the branch/commit name (default 'master')
-
+
``max_count``
is the maximum number of commits to return (default 10)
-
+
``skip``
is the number of commits to skip (default 0)
"""
options = {'max_count': max_count,
'skip': skip}
-
+
return Commit.find_all(self, start, **options)
-
+
def commits_between(self, frm, to):
"""
The Commits objects that are reachable via ``to`` but not via ``frm``
Commits are returned in chronological order.
-
+
``from``
is the branch/commit name of the younger item
-
+
``to``
is the branch/commit name of the older item
``GitPython.Commit[]``
"""
return Commit.find_all(self, "%s..%s" % (frm, to)).reverse()
-
+
def commits_since(self, start = 'master', since = '1970-01-01'):
"""
The Commits objects that are newer than the specified date.
Commits are returned in chronological order.
-
+
``start``
is the branch/commit name (default 'master')
-
+
``since``
is a string represeting a date/time
-
+
Returns
``GitPython.Commit[]``
"""
options = {'since': since}
-
+
return Commit.find_all(self, start, **options)
def commit_count(self, start = 'master'):
"""
The number of commits reachable by the given branch/commit
-
+
``start``
is the branch/commit name (default 'master')
int
"""
return Commit.count(self, start)
-
+
def commit(self, id):
"""
The Commit object for the specified id
-
+
``id``
is the SHA1 identifier of the commit
-
+
Returns
GitPython.Commit
"""
options = {'max_count': 1}
-
+
commits = Commit.find_all(self, id, **options)
-
+
if not commits:
raise ValueError, 'Invalid identifier %s' % id
return commits[0]
-
+
def commit_deltas_from(self, other_repo, ref = 'master', other_ref = 'master'):
"""
Returns a list of commits that is in ``other_repo`` but not in self
-
+
Returns
``GitPython.Commit[]``
"""
repo_refs = self.git.rev_list(ref).strip().splitlines()
other_repo_refs = other_repo.git.rev_list(other_ref).strip().splitlines()
-
+
diff_refs = list(set(other_repo_refs) - set(repo_refs))
return map(lambda ref: Commit.find_all(other_repo, ref, **{'max_count': 1}[0]), diff_refs)
-
+
def tree(self, treeish = 'master', paths = []):
"""
The Tree object for the given treeish reference
-
+
``treeish``
is the reference (default 'master')
``paths``
is an optional Array of directory paths to restrict the tree (deafult [])
-
+
Examples::
-
+
repo.tree('master', ['lib/'])
-
-
+
+
Returns
``GitPython.Tree``
"""
def blob(self, id):
"""
The Blob object for the given id
-
+
``id``
is the SHA1 id of the blob
-
+
Returns
``GitPython.Blob``
"""
def log(self, commit = 'master', path = None, **kwargs):
"""
The commit log for a treeish
-
+
Returns
``GitPython.Commit[]``
"""
arg = [commit]
commits = self.git.log(*arg, **options)
return Commit.list_from_string(self, commits)
-
+
def diff(self, a, b, *paths):
"""
The diff from commit ``a`` to commit ``b``, optionally restricted to the given file(s)
-
+
``a``
is the base commit
``b``
is the other commit
-
+
``paths``
is an optional list of file paths on which to restrict the diff
"""
"""
The commit diff for the given commit
``commit`` is the commit name/id
-
+
Returns
``GitPython.Diff[]``
"""
return Commit.diff(self, commit)
-
+
@classmethod
def init_bare(self, path, mkdir=True, **kwargs):
"""
Initialize a bare git repository at the given path
-
+
``path``
is the full path to the repo (traditionally ends with /<name>.git)
-
+
``mkdir``
if specified will create the repository directory if it doesn't
already exists. Creates the directory with a mode=0755.
-
+
``kwargs``
is any additional options to the git init command
-
+
Examples::
-
+
GitPython.Repo.init_bare('/var/git/myrepo.git')
-
+
Returns
``GitPython.Repo`` (the newly created repo)
"""
gitpath = path
else:
gitpath = os.path.join(path, '.git')
-
+
if mkdir and not os.path.exists(gitpath):
os.makedirs(gitpath, 0755)
-
+
git = Git(gitpath)
- output = git.init(**kwargs)
+ output = git.init(**kwargs)
return Repo(path)
create = init_bare
-
+
def fork_bare(self, path, **kwargs):
"""
Fork a bare git repository from this repo
-
+
``path``
is the full path of the new repo (traditionally ends with /<name>.git)
-
+
``options``
is any additional options to the git clone command
options.update(kwargs)
self.git.clone(self.path, path, **options)
return Repo(path)
-
+
def archive_tar(self, treeish = 'master', prefix = None):
"""
Archive the given treeish
-
+
``treeish``
is the treeish name/id (default 'master')
-
+
``prefix``
is the optional prefix
-
+
Examples::
-
+
>>> repo.archive_tar
<String containing tar archive>
-
+
>>> repo.archive_tar('a87ff14')
<String containing tar archive for commit a87ff14>
-
+
>>> repo.archive_tar('master', 'myproject/')
<String containing tar archive and prefixed with 'myproject/'>
-
+
Returns
str (containing tar archive)
"""
if prefix:
options['prefix'] = prefix
return self.git.archive(treeish, **options)
-
+
def archive_tar_gz(self, treeish = 'master', prefix = None):
"""
Archive and gzip the given treeish
-
+
``treeish``
is the treeish name/id (default 'master')
-
+
``prefix``
is the optional prefix
-
+
Examples::
-
+
>>> repo.archive_tar_gz
<String containing tar.gz archive>
-
+
>>> repo.archive_tar_gz('a87ff14')
<String containing tar.gz archive for commit a87ff14>
-
+
>>> repo.archive_tar_gz('master', 'myproject/')
<String containing tar.gz archive and prefixed with 'myproject/'>
-
+
Returns
str (containing tar.gz archive)
"""
if prefix:
kwargs['prefix'] = prefix
self.git.archive(treeish, "| gzip", **kwargs)
-
+
def enable_daemon_serve(self):
"""
Enable git-daemon serving of this repository by writing the
git-daemon-export-ok file to its git directory
-
+
Returns
None
"""
touch(os.path.join(self.path, DAEMON_EXPORT_FILE))
else:
touch(os.path.join(self.path, '.git', DAEMON_EXPORT_FILE))
-
+
def disable_daemon_serve(self):
"""
Disable git-daemon serving of this repository by ensuring there is no
git-daemon-export-ok file in its git directory
-
+
Returns
None
"""
return os.remove(os.path.join(self.path, DAEMON_EXPORT_FILE))
else:
return os.remove(os.path.join(self.path, '.git', DAEMON_EXPORT_FILE))
-
+
def _get_alternates(self):
"""
The list of alternates for this repo
-
+
Returns
list[str] (pathnames of alternates)
"""
return alts.strip().splitlines()
else:
return []
-
+
def _set_alternates(self, alts):
"""
Sets the alternates
-
+
``alts``
is the Array of String paths representing the alternates
-
+
Returns
None
"""
for alt in alts:
if not os.path.exists(alt):
raise NoSuchPathError("Could not set alternates. Alternate path %s must exist" % alt)
-
+
if not alts:
os.remove(os.path.join(self.path, *['objects', 'info', 'alternates']))
else:
f.write("\n".join(alts))
finally:
f.close()
-
+
alternates = property(_get_alternates, _set_alternates)
-
+
def __repr__(self):
return '<GitPython.Repo "%s">' % self.path
self.repo = repo
self.total = total
self.files = files
-
+
@classmethod
def list_from_string(cls, repo, text):
hsh = {'total': {'insertions': 0, 'deletions': 0, 'lines': 0, 'files': 0}, 'files': {}}
hsh['total']['lines'] = (hsh['total']['deletions'] + hsh['total']['insertions'])
hsh['total']['files'] += 1
hsh['files'][filename.strip()] = {'insertions': int(insertions), 'deletions': int(deletions)}
- return Stats(repo, hsh['total'], hsh['files'])
\ No newline at end of file
+ return Stats(repo, hsh['total'], hsh['files'])
class Tag(object):
def __init__(self, name, commit):
- """
+ """
Instantiate a new Tag
-
+
``name``
is the name of the head
-
+
``commit``
is the Commit that the head points to
-
+
Returns
``GitPython.Tag``
- """
+ """
self.name = name
self.commit = commit
-
+
@classmethod
def find_all(cls, repo, **kwargs):
- """
+ """
Find all Tags
-
+
``repo``
is the Repo
-
+
``kwargs``
is a dict of options
-
+
Returns
``GitPython.Tag[]``
"""
'format': "%(refname)%00%(objectname)"}
options.update(**kwargs)
- output = repo.git.for_each_ref("refs/tags", **options)
+ output = repo.git.for_each_ref("refs/tags", **options)
return cls.list_from_string(repo, output)
-
+
@classmethod
def list_from_string(cls, repo, text):
- """
+ """
Parse out tag information into an array of baked Tag objects
-
+
``repo``
is the Repo
-
+
``text``
is the text output from the git command
-
+
Returns
``GitPython.Tag[]``
"""
for line in text.splitlines():
tags.append(cls.from_string(repo, line))
return tags
-
+
@classmethod
def from_string(cls, repo, line):
- """
+ """
Create a new Tag instance from the given string.
-
+
``repo``
is the Repo
-
+
``line``
is the formatted tag information
-
+
Format
name: [a-zA-Z_/]+
<null byte>
id: [0-9A-Fa-f]{40}
-
+
Returns
``GitPython.Tag``
"""
name = full_name.split("/")[-1]
commit = Commit(repo, **{'id': ids})
return Tag(name, commit)
-
+
def __repr__(self):
return '<GitPython.Tag "%s">' % self.name
self.repo = repo
self.id = None
self.contents = None
-
+
for k, v in kwargs.items():
setattr(self, k, v)
@classmethod
def construct(cls, repo, treeish, paths = []):
- output = repo.git.ls_tree(treeish, *paths)
+ output = repo.git.ls_tree(treeish, *paths)
return Tree(repo, **{'id': treeish}).construct_initialize(repo, treeish, output)
def construct_initialize(self, repo, id, text):
self.id = id
self.contents = []
self.__baked__ = False
-
+
for line in text.splitlines():
self.contents.append(self.content_from_string(self.repo, line))
-
+
self.contents = [c for c in self.contents if c is not None]
self.__bake_it__()
def content_from_string(self, repo, text):
"""
Parse a content item and create the appropriate object
-
+
``repo``
is the Repo
-
+
``text``
is the single line containing the items data in `git ls-tree` format
mode, typ, id, name = text.expandtabs(1).split(" ", 4)
except:
return None
-
+
if typ == "tree":
return Tree(repo, **{'id': id, 'mode': mode, 'name': name})
elif typ == "blob":
Find the named object in this tree's contents
Examples::
-
+
>>> Repo('/path/to/python-git').tree/'lib'
<GitPython.Tree "6cc23ee138be09ff8c28b07162720018b244e95e">
>>> Repo('/path/to/python-git').tree/'README.txt'
<GitPython.Blob "8b1e02c0fb554eed2ce2ef737a68bb369d7527df">
-
+
Returns
``GitPython.Blob`` or ``GitPython.Tree`` or ``None`` if not found
"""
contents = [c for c in self.contents if c.name == file]
return contents and contents[0] or None
-
+
@property
def basename(self):
os.path.basename(self.name)
-
+
def __repr__(self):
return '<GitPython.Tree "%s">' % self.id