summaryrefslogtreecommitdiff
path: root/lib/git
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2010-07-07 17:43:36 +0200
committerSebastian Thiel <byronimo@gmail.com>2010-07-07 17:43:36 +0200
commitca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d (patch)
tree647089e9f091411c69ff4ee62747c53606671485 /lib/git
parenta4287f65878000b42d11704692f9ea3734014b4c (diff)
parent01ab5b96e68657892695c99a93ef909165456689 (diff)
downloadgitpython-ca288d443f4fc9d790eecb6e1cdf82b6cdd8dc0d.tar.gz
Merge branch 'revparse'
Diffstat (limited to 'lib/git')
-rw-r--r--lib/git/db.py30
-rw-r--r--lib/git/exc.py4
m---------lib/git/ext/gitdb0
-rw-r--r--lib/git/index/__init__.py2
-rw-r--r--lib/git/index/base.py2
-rw-r--r--lib/git/objects/base.py16
-rw-r--r--lib/git/refs.py22
-rw-r--r--lib/git/remote.py2
-rw-r--r--lib/git/repo/__init__.py3
-rw-r--r--lib/git/repo/base.py (renamed from lib/git/repo.py)94
-rw-r--r--lib/git/repo/fun.py231
11 files changed, 335 insertions, 71 deletions
diff --git a/lib/git/db.py b/lib/git/db.py
index c36446d0..6339569f 100644
--- a/lib/git/db.py
+++ b/lib/git/db.py
@@ -1,11 +1,18 @@
-"""Module with our own gitdb implementation - it uses the git command"""
+"""Module with our own gitdb implementation - it uses the git command"""
+from exc import (
+ GitCommandError,
+ BadObject
+ )
+
from gitdb.base import (
OInfo,
OStream
)
-from gitdb.util import bin_to_hex
-
+from gitdb.util import (
+ bin_to_hex,
+ hex_to_bin
+ )
from gitdb.db import GitDB
from gitdb.db import LooseObjectDB
@@ -35,3 +42,20 @@ class GitCmdObjectDB(LooseObjectDB):
t = self._git.stream_object_data(bin_to_hex(sha))
return OStream(*t)
+
+ # { Interface
+
+ def partial_to_complete_sha_hex(self, partial_hexsha):
+ """:return: Full binary 20 byte sha from the given partial hexsha
+ :raise AmbiguousObjectName:
+ :raise BadObject:
+ :note: currently we only raise BadObject as git does not communicate
+ AmbiguousObjects separately"""
+ try:
+ hexsha, typename, size = self._git.get_object_header(partial_hexsha)
+ return hex_to_bin(hexsha)
+ except (GitCommandError, ValueError):
+ raise BadObject(partial_hexsha)
+ # END handle exceptions
+
+ #} END interface
diff --git a/lib/git/exc.py b/lib/git/exc.py
index 93919d5e..d2cb8d7e 100644
--- a/lib/git/exc.py
+++ b/lib/git/exc.py
@@ -1,10 +1,12 @@
-# errors.py
+# exc.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
# 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, """
+from gitdb.exc import *
+
class InvalidGitRepositoryError(Exception):
""" Thrown if the given repository appears to have an invalid format. """
diff --git a/lib/git/ext/gitdb b/lib/git/ext/gitdb
-Subproject 6c8721a7d5d32e54bb4ffd3725ed23ac5d76a59
+Subproject ac7d4757ab4041f5f0f5806934130024b098bb8
diff --git a/lib/git/index/__init__.py b/lib/git/index/__init__.py
index 13f874b0..fe4a7f59 100644
--- a/lib/git/index/__init__.py
+++ b/lib/git/index/__init__.py
@@ -1,4 +1,4 @@
-"""Initialize the index module"""
+"""Initialize the index package"""
from base import *
from typ import * \ No newline at end of file
diff --git a/lib/git/index/base.py b/lib/git/index/base.py
index 4b3197a2..0f02352f 100644
--- a/lib/git/index/base.py
+++ b/lib/git/index/base.py
@@ -1122,7 +1122,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# item. Handle existing -R flags properly. Transform strings to the object
# so that we can call diff on it
if isinstance(other, basestring):
- other = Object.new(self.repo, other)
+ other = self.repo.rev_parse(other)
# END object conversion
if isinstance(other, Object):
diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py
index d4a46788..41862ac2 100644
--- a/lib/git/objects/base.py
+++ b/lib/git/objects/base.py
@@ -49,10 +49,18 @@ class Object(LazyMixin):
:note: This cannot be a __new__ method as it would always call __init__
with the input id which is not necessarily a binsha."""
- hexsha, typename, size = repo.git.get_object_header(id)
- inst = get_object_type_by_name(typename)(repo, hex_to_bin(hexsha))
- inst.size = size
- return inst
+ return repo.rev_parse(str(id))
+
+ @classmethod
+ def new_from_sha(cls, repo, sha1):
+ """
+ :return: new object instance of a type appropriate to represent the given
+ binary sha1
+ :param sha1: 20 byte binary sha1"""
+ oinfo = repo.odb.info(sha1)
+ inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha)
+ inst.size = oinfo.size
+ return inst
def _set_self_from_args_(self, args_dict):
"""Initialize attributes on self from the given dict that was retrieved
diff --git a/lib/git/refs.py b/lib/git/refs.py
index 343a0afb..03b80690 100644
--- a/lib/git/refs.py
+++ b/lib/git/refs.py
@@ -68,7 +68,7 @@ class SymbolicReference(object):
:return:
In case of symbolic references, the shortest assumable name
is the path itself."""
- return self.path
+ return self.path
def _abs_path(self):
return join_path_native(self.repo.git_dir, self.path)
@@ -109,6 +109,19 @@ class SymbolicReference(object):
# I believe files are closing themselves on destruction, so it is
# alright.
+ @classmethod
+ def dereference_recursive(cls, repo, ref_path):
+ """
+ :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all
+ intermediate references as required
+ :param repo: the repository containing the reference at ref_path"""
+ while True:
+ ref = cls(repo, ref_path)
+ hexsha, ref_path = ref._get_ref_info()
+ if hexsha is not None:
+ return hexsha
+ # END recursive dereferencing
+
def _get_ref_info(self):
"""Return: (sha, target_ref_path) if available, the sha the file at
rela_path points to, or None. target_ref_path is the reference we
@@ -195,9 +208,8 @@ class SymbolicReference(object):
try:
write_value = ref.commit.hexsha
except AttributeError:
- sha = str(ref)
try:
- obj = Object.new(self.repo, sha)
+ obj = self.repo.rev_parse(ref+"^{}") # optionally deref tags
if obj.type != "commit":
raise TypeError("Invalid object type behind sha: %s" % sha)
write_value = obj.hexsha
@@ -333,7 +345,7 @@ class SymbolicReference(object):
# figure out target data
target = reference
if resolve:
- target = Object.new(repo, reference)
+ target = repo.rev_parse(str(reference))
if not force and isfile(abs_ref_path):
target_data = str(target)
@@ -523,7 +535,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
always point to the actual object as it gets re-created on each query"""
# have to be dynamic here as we may be a tag which can point to anything
# Our path will be resolved to the hexsha which will be used accordingly
- return Object.new(self.repo, self.path)
+ return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
def _set_object(self, ref):
"""
diff --git a/lib/git/remote.py b/lib/git/remote.py
index 1598e55a..801dcd62 100644
--- a/lib/git/remote.py
+++ b/lib/git/remote.py
@@ -391,7 +391,7 @@ class FetchInfo(object):
split_token = '...'
if control_character == ' ':
split_token = split_token[:-1]
- old_commit = Commit.new(repo, operation.split(split_token)[0])
+ old_commit = repo.rev_parse(operation.split(split_token)[0])
# END handle refspec
# END reference flag handling
diff --git a/lib/git/repo/__init__.py b/lib/git/repo/__init__.py
new file mode 100644
index 00000000..8902a254
--- /dev/null
+++ b/lib/git/repo/__init__.py
@@ -0,0 +1,3 @@
+"""Initialize the Repo package"""
+
+from base import * \ No newline at end of file
diff --git a/lib/git/repo.py b/lib/git/repo/base.py
index 62202364..ed805991 100644
--- a/lib/git/repo.py
+++ b/lib/git/repo/base.py
@@ -4,27 +4,32 @@
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
-from exc import InvalidGitRepositoryError, NoSuchPathError
-from cmd import Git
-from objects import Actor
-from refs import *
-from index import IndexFile
-from objects import *
-from config import GitConfigParser
-from remote import Remote
-
-from db import (
+from git.exc import InvalidGitRepositoryError, NoSuchPathError
+from git.cmd import Git
+from git.objects import Actor
+from git.refs import *
+from git.index import IndexFile
+from git.objects import *
+from git.config import GitConfigParser
+from git.remote import Remote
+from git.db import (
GitCmdObjectDB,
GitDB
)
+
from gitdb.util import (
join,
- isdir,
isfile,
- join,
hex_to_bin
)
+
+from fun import (
+ rev_parse,
+ is_git_dir,
+ touch
+ )
+
import os
import sys
import re
@@ -32,23 +37,6 @@ import re
__all__ = ('Repo', )
-def touch(filename):
- fp = open(filename, "a")
- fp.close()
-
-def is_git_dir(d):
- """ This is taken from the git setup.c:is_git_directory
- function."""
-
- if isdir(d) and \
- isdir(join(d, 'objects')) and \
- isdir(join(d, 'refs')):
- headref = join(d, 'HEAD')
- return isfile(headref) or \
- (os.path.islink(headref) and
- os.readlink(headref).startswith('refs'))
- return False
-
class Repo(object):
"""Represents a git repository and allows you to query references,
@@ -70,6 +58,7 @@ class Repo(object):
# precompiled regex
re_whitespace = re.compile(r'\s+')
re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
+ re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$')
re_author_committer_start = re.compile(r'^(author|committer)')
re_tab_full_line = re.compile(r'^\t(.*)$')
@@ -109,7 +98,7 @@ class Repo(object):
self.git_dir = curpath
self._working_tree_dir = os.path.dirname(curpath)
break
- gitpath = os.path.join(curpath, '.git')
+ gitpath = join(curpath, '.git')
if is_git_dir(gitpath):
self.git_dir = gitpath
self._working_tree_dir = curpath
@@ -139,7 +128,7 @@ class Repo(object):
self.git = Git(self.working_dir)
# special handling, in special times
- args = [os.path.join(self.git_dir, 'objects')]
+ args = [join(self.git_dir, 'objects')]
if issubclass(odbt, GitCmdObjectDB):
args.append(self.git)
self.odb = odbt(*args)
@@ -160,11 +149,11 @@ class Repo(object):
# Description property
def _get_description(self):
- filename = os.path.join(self.git_dir, 'description')
+ filename = join(self.git_dir, 'description')
return file(filename).read().rstrip()
def _set_description(self, descr):
- filename = os.path.join(self.git_dir, 'description')
+ filename = join(self.git_dir, 'description')
file(filename, 'w').write(descr+'\n')
description = property(_get_description, _set_description,
@@ -334,11 +323,9 @@ class Repo(object):
:param rev: revision specifier, see git-rev-parse for viable options.
:return: ``git.Commit``"""
if rev is None:
- rev = self.active_branch
-
- c = Object.new(self, rev)
- assert c.type == "commit", "Revision %s did not point to a commit, but to %s" % (rev, c)
- return c
+ return self.active_branch.commit
+ else:
+ return self.rev_parse(str(rev)+"^0")
def iter_trees(self, *args, **kwargs):
""":return: Iterator yielding Tree objects
@@ -359,14 +346,9 @@ class Repo(object):
it cannot know about its path relative to the repository root and subsequent
operations might have unexpected results."""
if rev is None:
- rev = self.active_branch
-
- c = Object.new(self, rev)
- if c.type == "commit":
- return c.tree
- elif c.type == "tree":
- return c
- raise ValueError( "Revision %s did not point to a treeish, but to %s" % (rev, c))
+ return self.active_branch.commit.tree
+ else:
+ return self.rev_parse(str(rev)+"^{tree}")
def iter_commits(self, rev=None, paths='', **kwargs):
"""A list of Commit objects representing the history of a given ref/commit
@@ -393,11 +375,11 @@ class Repo(object):
return Commit.iter_items(self, rev, paths, **kwargs)
def _get_daemon_export(self):
- filename = os.path.join(self.git_dir, self.DAEMON_EXPORT_FILE)
+ filename = join(self.git_dir, self.DAEMON_EXPORT_FILE)
return os.path.exists(filename)
def _set_daemon_export(self, value):
- filename = os.path.join(self.git_dir, self.DAEMON_EXPORT_FILE)
+ filename = join(self.git_dir, self.DAEMON_EXPORT_FILE)
fileexists = os.path.exists(filename)
if value and not fileexists:
touch(filename)
@@ -413,7 +395,7 @@ class Repo(object):
"""The list of alternates for this repo from which objects can be retrieved
:return: list of strings being pathnames of alternates"""
- alternates_path = os.path.join(self.git_dir, 'objects', 'info', 'alternates')
+ alternates_path = join(self.git_dir, 'objects', 'info', 'alternates')
if os.path.exists(alternates_path):
try:
@@ -436,9 +418,9 @@ class Repo(object):
:note:
The method does not check for the existance of the paths in alts
as the caller is responsible."""
- alternates_path = os.path.join(self.git_dir, 'objects', 'info', 'alternates')
+ alternates_path = join(self.git_dir, 'objects', 'info', 'alternates')
if not alts:
- if os.path.isfile(alternates_path):
+ if isfile(alternates_path):
os.remove(alternates_path)
else:
try:
@@ -466,7 +448,7 @@ class Repo(object):
default_args = ('--abbrev=40', '--full-index', '--raw')
if index:
# diff index against HEAD
- if os.path.isfile(self.index.path) and self.head.is_valid() and \
+ if isfile(self.index.path) and self.head.is_valid() and \
len(self.git.diff('HEAD', '--cached', *default_args)):
return True
# END index handling
@@ -674,7 +656,7 @@ class Repo(object):
# our git command could have a different working dir than our actual
# environment, hence we prepend its working dir if required
if not os.path.isabs(path) and self.git.working_dir:
- path = os.path.join(self.git._working_dir, path)
+ path = join(self.git._working_dir, path)
return Repo(os.path.abspath(path), odbt = odbt)
@@ -693,11 +675,13 @@ class Repo(object):
if treeish is None:
treeish = self.active_branch
if prefix and 'prefix' not in kwargs:
- kwargs['prefix'] = prefix
+ kwargs['prefix'] = prefix
kwargs['output_stream'] = ostream
self.git.archive(treeish, **kwargs)
return self
-
+
+ rev_parse = rev_parse
+
def __repr__(self):
return '<git.Repo "%s">' % self.git_dir
diff --git a/lib/git/repo/fun.py b/lib/git/repo/fun.py
new file mode 100644
index 00000000..a0f66fe5
--- /dev/null
+++ b/lib/git/repo/fun.py
@@ -0,0 +1,231 @@
+"""Package with general repository related functions"""
+
+from gitdb.exc import BadObject
+from git.refs import SymbolicReference
+from git.objects import Object
+from gitdb.util import (
+ join,
+ isdir,
+ isfile,
+ hex_to_bin,
+ bin_to_hex
+ )
+from string import digits
+
+__all__ = ('rev_parse', 'is_git_dir', 'touch')
+
+def touch(filename):
+ fp = open(filename, "a")
+ fp.close()
+
+def is_git_dir(d):
+ """ This is taken from the git setup.c:is_git_directory
+ function."""
+ if isdir(d) and \
+ isdir(join(d, 'objects')) and \
+ isdir(join(d, 'refs')):
+ headref = join(d, 'HEAD')
+ return isfile(headref) or \
+ (os.path.islink(headref) and
+ os.readlink(headref).startswith('refs'))
+ return False
+
+
+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.
+ :param hexsha: hexsha with less than 40 byte"""
+ try:
+ return bin_to_hex(odb.partial_to_complete_sha_hex(hexsha))
+ except BadObject:
+ return None
+ # END exception handling
+
+
+def name_to_object(repo, name):
+ """:return: object specified by the given name, hexshas ( short and long )
+ as well as references are supported"""
+ hexsha = None
+
+ # is it a hexsha ? Try the most common ones, which is 7 to 40
+ if repo.re_hexsha_shortened.match(name):
+ if len(name) != 40:
+ # find long sha for short sha
+ hexsha = short_to_long(repo.odb, name)
+ else:
+ hexsha = name
+ # END handle short shas
+ else:
+ for base in ('%s', 'refs/%s', 'refs/tags/%s', 'refs/heads/%s', 'refs/remotes/%s', 'refs/remotes/%s/HEAD'):
+ try:
+ hexsha = SymbolicReference.dereference_recursive(repo, base % name)
+ break
+ except ValueError:
+ pass
+ # END for each base
+ # END handle hexsha
+
+ # tried everything ? fail
+ if hexsha is None:
+ raise BadObject(name)
+ # END assert hexsha was found
+
+ return Object.new_from_sha(repo, hex_to_bin(hexsha))
+
+def deref_tag(tag):
+ """Recursively dereerence a tag and return the resulting object"""
+ while True:
+ try:
+ tag = tag.object
+ except AttributeError:
+ break
+ # END dereference tag
+ return tag
+
+def to_commit(obj):
+ """Convert the given object to a commit if possible and return it"""
+ if obj.type == 'tag':
+ obj = deref_tag(obj)
+
+ if obj.type != "commit":
+ raise ValueError("Cannot convert object %r to type commit" % obj)
+ # END verify type
+ return obj
+
+def rev_parse(repo, rev):
+ """
+ :return: Object at the given revision, either Commit, Tag, Tree or Blob
+ :param rev: git-rev-parse compatible revision specification, please see
+ http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html
+ for details
+ :note: Currently there is no access to the rev-log, rev-specs may only contain
+ topological tokens such ~ and ^.
+ :raise BadObject: if the given revision could not be found"""
+ if '@' in rev:
+ raise ValueError("There is no rev-log support yet")
+
+
+ # colon search mode ?
+ if rev.startswith(':/'):
+ # colon search mode
+ raise NotImplementedError("commit by message search ( regex )")
+ # END handle search
+
+ obj = None
+ output_type = "commit"
+ start = 0
+ parsed_to = 0
+ lr = len(rev)
+ while start < lr:
+ if rev[start] not in "^~:":
+ start += 1
+ continue
+ # END handle start
+
+ if obj is None:
+ # token is a rev name
+ obj = name_to_object(repo, rev[:start])
+ # END initialize obj on first token
+
+ token = rev[start]
+ start += 1
+
+ # try to parse {type}
+ if start < lr and rev[start] == '{':
+ end = rev.find('}', start)
+ if end == -1:
+ raise ValueError("Missing closing brace to define type in %s" % rev)
+ output_type = rev[start+1:end] # exclude brace
+
+ # handle type
+ if output_type == 'commit':
+ pass # default
+ elif output_type == 'tree':
+ try:
+ obj = to_commit(obj).tree
+ except (AttributeError, ValueError):
+ pass # error raised later
+ # END exception handling
+ elif output_type in ('', 'blob'):
+ if obj.type == 'tag':
+ obj = deref_tag(obj)
+ else:
+ # cannot do anything for non-tags
+ pass
+ # END handle tag
+ else:
+ raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
+ # END handle output type
+
+ # empty output types don't require any specific type, its just about dereferencing tags
+ if output_type and obj.type != output_type:
+ raise ValueError("Could not accomodate requested object type %r, got %s" % (output_type, obj.type))
+ # END verify ouput type
+
+ start = end+1 # skip brace
+ parsed_to = start
+ continue
+ # END parse type
+
+ # try to parse a number
+ num = 0
+ if token != ":":
+ found_digit = False
+ while start < lr:
+ if rev[start] in digits:
+ num = num * 10 + int(rev[start])
+ start += 1
+ found_digit = True
+ else:
+ break
+ # END handle number
+ # END number parse loop
+
+ # no explicit number given, 1 is the default
+ # It could be 0 though
+ if not found_digit:
+ num = 1
+ # END set default num
+ # END number parsing only if non-blob mode
+
+
+ parsed_to = start
+ # handle hiererarchy walk
+ try:
+ if token == "~":
+ obj = to_commit(obj)
+ for item in xrange(num):
+ obj = obj.parents[0]
+ # END for each history item to walk
+ elif token == "^":
+ obj = to_commit(obj)
+ # must be n'th parent
+ if num:
+ obj = obj.parents[num-1]
+ elif token == ":":
+ if obj.type != "tree":
+ obj = obj.tree
+ # END get tree type
+ obj = obj[rev[start:]]
+ parsed_to = lr
+ else:
+ raise ValueError("Invalid token: %r" % token)
+ # END end handle tag
+ except (IndexError, AttributeError):
+ raise BadObject("Invalid Revision in %s" % rev)
+ # END exception handling
+ # END parse loop
+
+ # still no obj ? Its probably a simple name
+ if obj is None:
+ obj = name_to_object(repo, rev)
+ parsed_to = lr
+ # END handle simple name
+
+ if obj is None:
+ raise ValueError("Revision specifier could not be parsed: %s" % rev)
+
+ if parsed_to != lr:
+ raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to]))
+
+ return obj