summaryrefslogtreecommitdiff
path: root/git/objects/commit.py
diff options
context:
space:
mode:
Diffstat (limited to 'git/objects/commit.py')
-rw-r--r--git/objects/commit.py178
1 files changed, 88 insertions, 90 deletions
diff --git a/git/objects/commit.py b/git/objects/commit.py
index bc437e8b..d778f2d7 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -4,7 +4,7 @@
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
-from git.util import (
+from git.util import (
Actor,
Iterable,
Stats,
@@ -27,7 +27,7 @@ from util import (
parse_actor_and_date
)
from time import (
- time,
+ time,
altzone
)
import os
@@ -35,53 +35,54 @@ import sys
__all__ = ('Commit', )
+
class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
+
"""Wraps a git Commit object.
-
- This class will act lazily on some of its attributes and will query the
+
+ 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."""
-
+
# ENVIRONMENT VARIABLES
# read when creating new commits
env_author_date = "GIT_AUTHOR_DATE"
env_committer_date = "GIT_COMMITTER_DATE"
-
+
# CONFIGURATION KEYS
conf_encoding = 'i18n.commitencoding'
-
+
# INVARIANTS
default_encoding = "UTF-8"
-
-
- # object configuration
+
+ # object configuration
type = "commit"
__slots__ = ("tree",
"author", "authored_date", "author_tz_offset",
"committer", "committed_date", "committer_tz_offset",
"message", "parents", "encoding", "gpgsig")
_id_attribute_ = "binsha"
-
+
def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None,
committer=None, committed_date=None, committer_tz_offset=None,
- message=None, parents=None, encoding=None, gpgsig=None):
+ message=None, parents=None, encoding=None, gpgsig=None):
"""Instantiate a new Commit. All keyword arguments taking None as default will
be implicitly set on first query.
:param binsha: 20 byte sha1
- :param parents: tuple( Commit, ... )
+ :param parents: tuple( Commit, ... )
is a tuple of commit ids or actual Commits
:param tree: Tree
Tree object
:param author: Actor
is the author string ( will be implicitly converted into an Actor object )
:param authored_date: int_seconds_since_epoch
- is the authored DateTime - use time.gmtime() to convert it into a
+ is the authored DateTime - use time.gmtime() to convert it into a
different format
:param author_tz_offset: int_seconds_west_of_utc
is the timezone that the authored_date is in
:param committer: Actor
is the committer string
:param committed_date: int_seconds_since_epoch
- is the committed DateTime - use time.gmtime() to convert it into a
+ is the committed DateTime - use time.gmtime() to convert it into a
different format
:param committer_tz_offset: int_seconds_west_of_utc
is the timezone that the authored_date is in
@@ -90,14 +91,14 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
:param encoding: string
encoding of the message, defaults to UTF-8
:param parents:
- List or tuple of Commit objects which are our parent(s) in the commit
+ List or tuple of Commit objects which are our parent(s) in the commit
dependency graph
:return: git.Commit
-
- :note: Timezone information is in the same format and in the same sign
- as what time.altzone returns. The sign is inverted compared to git's
+
+ :note: Timezone information is in the same format and in the same sign
+ as what time.altzone returns. The sign is inverted compared to git's
UTC timezone."""
- super(Commit,self).__init__(repo, binsha)
+ super(Commit, self).__init__(repo, binsha)
if tree is not None:
assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
if tree is not None:
@@ -139,12 +140,12 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
def summary(self):
""":return: First line of the commit message"""
return self.message.split('\n', 1)[0]
-
+
def count(self, paths='', **kwargs):
"""Count the number of commits reachable from this commit
:param paths:
- is an optinal path or a list of paths restricting the return value
+ is an optinal path or a list of paths restricting the return value
to commits actually containing the paths
:param kwargs:
@@ -157,7 +158,6 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
return len(self.repo.git.rev_list(self.hexsha, '--', paths, **kwargs).splitlines())
else:
return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines())
-
@property
def name_rev(self):
@@ -174,7 +174,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
:param repo: is the Repo
:param rev: revision specifier, see git-rev-parse for viable options
:param paths:
- is an optinal path or list of paths, if set only Commits that include the path
+ is an optinal path or list of paths, if set only Commits that include the path
or paths will be considered
:param kwargs:
optional keyword arguments to git rev-list where
@@ -192,28 +192,28 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
proc = repo.git.rev_list(rev, args, as_process=True, **kwargs)
return cls._iter_from_process_or_stream(repo, proc)
-
+
def iter_parents(self, paths='', **kwargs):
"""Iterate _all_ parents of this commit.
-
+
:param paths:
- Optional path or list of paths limiting the Commits to those that
+ Optional path or list of paths limiting the Commits to those that
contain at least one of the paths
:param kwargs: All arguments allowed by git-rev-list
:return: Iterator yielding Commit objects which are parents of self """
# skip ourselves
skip = kwargs.get("skip", 1)
- if skip == 0: # skip ourselves
+ if skip == 0: # skip ourselves
skip = 1
kwargs['skip'] = skip
-
+
return self.iter_items(self.repo, self, paths, **kwargs)
@property
def stats(self):
- """Create a git stat from changes between this commit and its first parent
+ """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.hexsha, '--', numstat=True, root=True)
@@ -235,9 +235,9 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
:param proc: git-rev-list process instance - one sha per line
:return: iterator returning Commit objects"""
stream = proc_or_stream
- if not hasattr(stream,'readline'):
+ if not hasattr(stream, 'readline'):
stream = proc_or_stream.stdout
-
+
readline = stream.readline
while True:
line = readline()
@@ -248,7 +248,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
# split additional information, as returned by bisect for instance
hexsha, rest = line.split(None, 1)
# END handle extra info
-
+
assert len(hexsha) == 40, "Invalid line: %s" % hexsha
yield Commit(repo, hex_to_bin(hexsha))
# END for each line in stream
@@ -256,103 +256,101 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
# due to many developers trying to fix the open file handles issue
if hasattr(proc_or_stream, 'wait'):
finalize_process(proc_or_stream)
-
-
+
@classmethod
def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False):
"""Commit the given tree, creating a commit object.
-
- :param repo: Repo object the commit should be part of
- :param tree: Tree object or hex or bin sha
+
+ :param repo: Repo object the commit should be part of
+ :param tree: Tree object or hex or bin sha
the tree of the new commit
:param message: Commit message. It may be an empty string if no message is provided.
It will be converted to a string in any case.
:param parent_commits:
Optional Commit objects to use as parents for the new commit.
- If empty list, the commit will have no parents at all and become
+ If empty list, the commit will have no parents at all and become
a root commit.
- If None , the current head commit will be the parent of the
+ If None , the current head commit will be the parent of the
new commit object
:param head:
If True, the HEAD will be advanced to the new commit automatically.
- Else the HEAD will remain pointing on the previous commit. This could
+ Else the HEAD will remain pointing on the previous commit. This could
lead to undesired results when diffing files.
-
+
:return: Commit object representing the new commit
-
+
:note:
Additional information about the committer and Author are taken from the
- environment or from the git configuration, see git-commit-tree for
+ environment or from the git configuration, see git-commit-tree for
more information"""
parents = parent_commits
if parent_commits is None:
try:
- parent_commits = [ repo.head.commit ]
+ parent_commits = [repo.head.commit]
except ValueError:
# empty repositories have no head commit
parent_commits = list()
# END handle parent commits
# END if parent commits are unset
-
- # retrieve all additional information, create a commit object, and
+
+ # retrieve all additional information, create a commit object, and
# serialize it
- # Generally:
+ # Generally:
# * Environment variables override configuration values
# * Sensible defaults are set according to the git documentation
-
+
# COMMITER AND AUTHOR INFO
cr = repo.config_reader()
env = os.environ
-
+
committer = Actor.committer(cr)
author = Actor.author(cr)
-
+
# PARSE THE DATES
unix_time = int(time())
offset = altzone
-
+
author_date_str = env.get(cls.env_author_date, '')
if author_date_str:
author_time, author_offset = parse_date(author_date_str)
else:
author_time, author_offset = unix_time, offset
# END set author time
-
+
committer_date_str = env.get(cls.env_committer_date, '')
- if committer_date_str:
+ if committer_date_str:
committer_time, committer_offset = parse_date(committer_date_str)
else:
committer_time, committer_offset = unix_time, offset
# END set committer time
-
+
# assume utf8 encoding
enc_section, enc_option = cls.conf_encoding.split('.')
conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
-
-
+
# if the tree is no object, make sure we create one - otherwise
# the created commit object is invalid
if isinstance(tree, str):
tree = repo.tree(tree)
# END tree conversion
-
+
# CREATE NEW COMMIT
- new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
- author, author_time, author_offset,
+ new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
+ author, author_time, author_offset,
committer, committer_time, committer_offset,
message, parent_commits, conf_encoding)
-
+
stream = StringIO()
new_commit._serialize(stream)
streamlen = stream.tell()
stream.seek(0)
-
+
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
new_commit.binsha = istream.binsha
-
+
if head:
# need late import here, importing git at the very beginning throws
- # as well ...
+ # as well ...
import git.refs
try:
repo.head.set_commit(new_commit, logmsg="commit: %s" % message)
@@ -363,49 +361,49 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
master = git.refs.Head.create(repo, repo.head.ref, new_commit, logmsg="commit (initial): %s" % message)
repo.head.set_reference(master, logmsg='commit: Switching to %s' % master)
# END handle empty repositories
- # END advance head handling
-
+ # END advance head handling
+
return new_commit
-
+
#{ Serializable Implementation
-
+
def _serialize(self, stream):
write = stream.write
write("tree %s\n" % self.tree)
for p in self.parents:
write("parent %s\n" % p)
-
+
a = self.author
aname = a.name
if isinstance(aname, unicode):
aname = aname.encode(self.encoding)
# END handle unicode in name
-
+
c = self.committer
fmt = "%s %s <%s> %s %s\n"
- write(fmt % ("author", aname, a.email,
- self.authored_date,
+ write(fmt % ("author", aname, a.email,
+ self.authored_date,
altz_to_utctz_str(self.author_tz_offset)))
-
+
# encode committer
aname = c.name
if isinstance(aname, unicode):
aname = aname.encode(self.encoding)
# END handle unicode in name
- write(fmt % ("committer", aname, c.email,
+ write(fmt % ("committer", aname, c.email,
self.committed_date,
altz_to_utctz_str(self.committer_tz_offset)))
-
+
if self.encoding != self.default_encoding:
write("encoding %s\n" % self.encoding)
if self.gpgsig:
write("gpgsig")
for sigline in self.gpgsig.rstrip("\n").split("\n"):
- write(" "+sigline+"\n")
+ write(" " + sigline + "\n")
write("\n")
-
+
# write plain bytes, be sure its encoded according to our encoding
if isinstance(self.message, unicode):
write(self.message.encode(self.encoding))
@@ -413,12 +411,12 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
write(self.message)
# END handle encoding
return self
-
+
def _deserialize(self, stream):
""":param from_rev_list: if true, the stream format is coming from the rev-list command
Otherwise it is assumed to be a plain data stream from our object"""
readline = stream.readline
- self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id<<12, '')
+ self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '')
self.parents = list()
next_line = None
@@ -431,7 +429,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1])))
# END for each parent line
self.parents = tuple(self.parents)
-
+
self.author, self.authored_date, self.author_tz_offset = parse_actor_and_date(next_line)
self.committer, self.committed_date, self.committer_tz_offset = parse_actor_and_date(readline())
@@ -441,7 +439,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
next_line = readline()
while next_line.startswith(' '):
next_line = readline()
-
+
# now we can have the encoding line, or an empty line followed by the optional
# message.
self.encoding = self.default_encoding
@@ -451,9 +449,9 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
buf = enc.strip()
while buf != "":
if buf[0:10] == "encoding ":
- self.encoding = buf[buf.find(' ')+1:]
+ self.encoding = buf[buf.find(' ') + 1:]
elif buf[0:7] == "gpgsig ":
- sig = buf[buf.find(' ')+1:] + "\n"
+ sig = buf[buf.find(' ') + 1:] + "\n"
is_next_header = False
while True:
sigbuf = readline()
@@ -470,18 +468,18 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
# decode the authors name
try:
- self.author.name = self.author.name.decode(self.encoding)
+ self.author.name = self.author.name.decode(self.encoding)
except UnicodeDecodeError:
print >> sys.stderr, "Failed to decode author name '%s' using encoding %s" % (self.author.name, self.encoding)
# END handle author's encoding
-
+
# decode committer name
try:
- self.committer.name = self.committer.name.decode(self.encoding)
+ self.committer.name = self.committer.name.decode(self.encoding)
except UnicodeDecodeError:
print >> sys.stderr, "Failed to decode committer name '%s' using encoding %s" % (self.committer.name, self.encoding)
# END handle author's encoding
-
+
# a stream from our data simply gives us the plain message
# The end of our message stream is marked with a newline that we strip
self.message = stream.read()
@@ -489,7 +487,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
self.message = self.message.decode(self.encoding)
except UnicodeDecodeError:
print >> sys.stderr, "Failed to decode message '%s' using encoding %s" % (self.message, self.encoding)
- # END exception handling
+ # END exception handling
return self
-
+
#} END serializable implementation