diff options
Diffstat (limited to 'lib/git')
-rw-r--r-- | lib/git/db.py | 30 | ||||
-rw-r--r-- | lib/git/exc.py | 4 | ||||
m--------- | lib/git/ext/gitdb | 0 | ||||
-rw-r--r-- | lib/git/index/__init__.py | 2 | ||||
-rw-r--r-- | lib/git/index/base.py | 2 | ||||
-rw-r--r-- | lib/git/objects/base.py | 16 | ||||
-rw-r--r-- | lib/git/refs.py | 22 | ||||
-rw-r--r-- | lib/git/remote.py | 2 | ||||
-rw-r--r-- | lib/git/repo/__init__.py | 3 | ||||
-rw-r--r-- | lib/git/repo/base.py (renamed from lib/git/repo.py) | 94 | ||||
-rw-r--r-- | lib/git/repo/fun.py | 231 |
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 |