summaryrefslogtreecommitdiff
path: root/git/db
diff options
context:
space:
mode:
Diffstat (limited to 'git/db')
-rw-r--r--git/db/cmd/advanced.py368
-rw-r--r--git/db/cmd/complex.py25
-rw-r--r--git/db/compat.py17
-rw-r--r--git/db/interface.py89
-rw-r--r--git/db/py/base.py74
-rw-r--r--git/db/py/complex.py18
-rw-r--r--git/db/py/resolve.py44
-rw-r--r--git/db/py/transport.py61
8 files changed, 635 insertions, 61 deletions
diff --git a/git/db/cmd/advanced.py b/git/db/cmd/advanced.py
new file mode 100644
index 00000000..c8bd2cd6
--- /dev/null
+++ b/git/db/cmd/advanced.py
@@ -0,0 +1,368 @@
+# repo.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
+
+class AdvancedFunctionalityMixin(object):
+ """An intermediate interface carrying advanced git functionality that can be used
+ in other comound repositories which do not implement this functionality themselves.
+
+ The mixin must be used with repositories that provide a git command object under
+ self.git.
+
+ :note: at some point, methods provided here are supposed to be provided by custom interfaces"""
+ DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
+
+ # 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(.*)$')
+
+ @property
+ def index(self):
+ """:return: IndexFile representing this repository's index."""
+ return IndexFile(self)
+
+ def commit(self, rev=None):
+ """The Commit object for the specified revision
+ :param rev: revision specifier, see git-rev-parse for viable options.
+ :return: ``git.Commit``"""
+ if rev is None:
+ return self.head.commit
+ else:
+ return self.rev_parse(str(rev)+"^0")
+
+ def iter_trees(self, *args, **kwargs):
+ """:return: Iterator yielding Tree objects
+ :note: Takes all arguments known to iter_commits method"""
+ return ( c.tree for c in self.iter_commits(*args, **kwargs) )
+
+ def tree(self, rev=None):
+ """The Tree object for the given treeish revision
+ Examples::
+
+ repo.tree(repo.heads[0])
+
+ :param rev: is a revision pointing to a Treeish ( being a commit or tree )
+ :return: ``git.Tree``
+
+ :note:
+ If you need a non-root level tree, find it by iterating the root tree. Otherwise
+ it cannot know about its path relative to the repository root and subsequent
+ operations might have unexpected results."""
+ if rev is None:
+ return self.head.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
+
+ :parm rev:
+ revision specifier, see git-rev-parse for viable options.
+ If None, the active branch will be used.
+
+ :parm paths:
+ is an optional path or a list of paths to limit the returned commits to
+ Commits that do not contain that path or the paths will not be returned.
+
+ :parm kwargs:
+ Arguments to be passed to git-rev-list - common ones are
+ max_count and skip
+
+ :note: to receive only commits between two named revisions, use the
+ "revA..revB" revision specifier
+
+ :return ``git.Commit[]``"""
+ if rev is None:
+ rev = self.head.commit
+
+ return Commit.iter_items(self, rev, paths, **kwargs)
+
+ def _get_daemon_export(self):
+ filename = join(self.git_dir, self.DAEMON_EXPORT_FILE)
+ return os.path.exists(filename)
+
+ def _set_daemon_export(self, value):
+ filename = join(self.git_dir, self.DAEMON_EXPORT_FILE)
+ fileexists = os.path.exists(filename)
+ if value and not fileexists:
+ touch(filename)
+ elif not value and fileexists:
+ os.unlink(filename)
+
+ daemon_export = property(_get_daemon_export, _set_daemon_export,
+ doc="If True, git-daemon may export this repository")
+ del _get_daemon_export
+ del _set_daemon_export
+
+ def is_dirty(self, index=True, working_tree=True, untracked_files=False):
+ """
+ :return:
+ ``True``, the repository is considered dirty. By default it will react
+ like a git-status without untracked files, hence it is dirty if the
+ index or the working copy have changes."""
+ if self._bare:
+ # Bare repositories with no associated working directory are
+ # always consired to be clean.
+ return False
+
+ # start from the one which is fastest to evaluate
+ default_args = ('--abbrev=40', '--full-index', '--raw')
+ if index:
+ # diff index against HEAD
+ if isfile(self.index.path) and self.head.is_valid() and \
+ len(self.git.diff('HEAD', '--cached', *default_args)):
+ return True
+ # END index handling
+ if working_tree:
+ # diff index against working tree
+ if len(self.git.diff(*default_args)):
+ return True
+ # END working tree handling
+ if untracked_files:
+ if len(self.untracked_files):
+ return True
+ # END untracked files
+ return False
+
+ @property
+ def untracked_files(self):
+ """
+ :return:
+ list(str,...)
+
+ Files currently untracked as they have not been staged yet. Paths
+ are relative to the current working directory of the git command.
+
+ :note:
+ ignored files will not appear here, i.e. files mentioned in .gitignore"""
+ # make sure we get all files, no only untracked directores
+ proc = self.git.status(untracked_files=True, as_process=True)
+ stream = iter(proc.stdout)
+ untracked_files = list()
+ for line in stream:
+ if not line.startswith("# Untracked files:"):
+ continue
+ # skip two lines
+ stream.next()
+ stream.next()
+
+ for untracked_info in stream:
+ if not untracked_info.startswith("#\t"):
+ break
+ untracked_files.append(untracked_info.replace("#\t", "").rstrip())
+ # END for each utracked info line
+ # END for each line
+ return untracked_files
+
+ def blame(self, rev, file):
+ """The blame information for the given file at the given revision.
+
+ :parm rev: revision specifier, see git-rev-parse for viable options.
+ :return:
+ 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 = self.git.blame(rev, '--', file, p=True)
+ commits = dict()
+ blames = list()
+ info = None
+
+ for line in data.splitlines(False):
+ parts = self.re_whitespace.split(line, 1)
+ firstpart = parts[0]
+ if self.re_hexsha_only.search(firstpart):
+ # handles
+ # 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7 - indicates blame-data start
+ # 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
+ digits = parts[-1].split(" ")
+ if len(digits) == 3:
+ info = {'id': firstpart}
+ blames.append([None, []])
+ # END blame data initialization
+ else:
+ m = self.re_author_committer_start.search(firstpart)
+ if m:
+ # handles:
+ # author Tom Preston-Werner
+ # author-mail <tom@mojombo.com>
+ # author-time 1192271832
+ # author-tz -0700
+ # committer Tom Preston-Werner
+ # committer-mail <tom@mojombo.com>
+ # committer-time 1192271832
+ # committer-tz -0700 - IGNORED BY US
+ role = m.group(0)
+ if firstpart.endswith('-mail'):
+ info["%s_email" % role] = parts[-1]
+ elif firstpart.endswith('-time'):
+ info["%s_date" % role] = int(parts[-1])
+ elif role == firstpart:
+ info[role] = parts[-1]
+ # END distinguish mail,time,name
+ else:
+ # handle
+ # filename lib/grit.rb
+ # summary add Blob
+ # <and rest>
+ if firstpart.startswith('filename'):
+ info['filename'] = parts[-1]
+ elif firstpart.startswith('summary'):
+ info['summary'] = parts[-1]
+ elif firstpart == '':
+ if info:
+ sha = info['id']
+ c = commits.get(sha)
+ if c is None:
+ c = Commit( self, hex_to_bin(sha),
+ author=Actor._from_string(info['author'] + ' ' + info['author_email']),
+ authored_date=info['author_date'],
+ committer=Actor._from_string(info['committer'] + ' ' + info['committer_email']),
+ committed_date=info['committer_date'],
+ message=info['summary'])
+ commits[sha] = c
+ # END if commit objects needs initial creation
+ m = self.re_tab_full_line.search(line)
+ text, = m.groups()
+ blames[-1][0] = c
+ blames[-1][1].append( text )
+ info = None
+ # END if we collected commit info
+ # END distinguish filename,summary,rest
+ # END distinguish author|committer vs filename,summary,rest
+ # END distinguish hexsha vs other information
+ return blames
+
+ @classmethod
+ def init(cls, path=None, mkdir=True, **kwargs):
+ """Initialize a git repository at the given path if specified
+
+ :param path:
+ is the full path to the repo (traditionally ends with /<name>.git)
+ or None in which case the repository will be created in the current
+ working directory
+
+ :parm mkdir:
+ if specified will create the repository directory if it doesn't
+ already exists. Creates the directory with a mode=0755.
+ Only effective if a path is explicitly given
+
+ :parm kwargs:
+ keyword arguments serving as additional options to the git-init command
+
+ :return: ``git.Repo`` (the newly created repo)"""
+
+ if mkdir and path and not os.path.exists(path):
+ os.makedirs(path, 0755)
+
+ # git command automatically chdir into the directory
+ git = Git(path)
+ output = git.init(**kwargs)
+ return Repo(path)
+
+ @classmethod
+ def _clone(cls, git, url, path, odb_default_type, **kwargs):
+ # special handling for windows for path at which the clone should be
+ # created.
+ # tilde '~' will be expanded to the HOME no matter where the ~ occours. Hence
+ # we at least give a proper error instead of letting git fail
+ prev_cwd = None
+ prev_path = None
+ odbt = kwargs.pop('odbt', odb_default_type)
+ if os.name == 'nt':
+ if '~' in path:
+ raise OSError("Git cannot handle the ~ character in path %r correctly" % path)
+
+ # on windows, git will think paths like c: are relative and prepend the
+ # current working dir ( before it fails ). We temporarily adjust the working
+ # dir to make this actually work
+ match = re.match("(\w:[/\\\])(.*)", path)
+ if match:
+ prev_cwd = os.getcwd()
+ prev_path = path
+ drive, rest_of_path = match.groups()
+ os.chdir(drive)
+ path = rest_of_path
+ kwargs['with_keep_cwd'] = True
+ # END cwd preparation
+ # END windows handling
+
+ try:
+ git.clone(url, path, **kwargs)
+ finally:
+ if prev_cwd is not None:
+ os.chdir(prev_cwd)
+ path = prev_path
+ # END reset previous working dir
+ # END bad windows handling
+
+ # 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 git.working_dir:
+ path = join(git._working_dir, path)
+
+ # adjust remotes - there may be operating systems which use backslashes,
+ # These might be given as initial paths, but when handling the config file
+ # that contains the remote from which we were clones, git stops liking it
+ # as it will escape the backslashes. Hence we undo the escaping just to be
+ # sure
+ repo = cls(os.path.abspath(path), odbt = odbt)
+ if repo.remotes:
+ repo.remotes[0].config_writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/"))
+ # END handle remote repo
+ return repo
+
+ def clone(self, path, **kwargs):
+ """Create a clone from this repository.
+ :param path:
+ is the full path of the new repo (traditionally ends with ./<name>.git).
+
+ :param kwargs:
+ odbt = ObjectDatabase Type, allowing to determine the object database
+ implementation used by the returned Repo instance
+
+ All remaining keyword arguments are given to the git-clone command
+
+ :return: ``git.Repo`` (the newly cloned repo)"""
+ return self._clone(self.git, self.git_dir, path, type(self.odb), **kwargs)
+
+ @classmethod
+ def clone_from(cls, url, to_path, **kwargs):
+ """Create a clone from the given URL
+ :param url: valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS
+ :param to_path: Path to which the repository should be cloned to
+ :param kwargs: see the ``clone`` method
+ :return: Repo instance pointing to the cloned directory"""
+ return cls._clone(Git(os.getcwd()), url, to_path, CmdGitDB, **kwargs)
+
+ def archive(self, ostream, treeish=None, prefix=None, **kwargs):
+ """Archive the tree at the given revision.
+ :parm ostream: file compatible stream object to which the archive will be written
+ :parm treeish: is the treeish name/id, defaults to active branch
+ :parm prefix: is the optional prefix to prepend to each filename in the archive
+ :parm kwargs:
+ Additional arguments passed to git-archive
+ NOTE: Use the 'format' argument to define the kind of format. Use
+ specialized ostreams to write any format supported by python
+
+ :raise GitCommandError: in case something went wrong
+ :return: self"""
+ if treeish is None:
+ treeish = self.head.commit
+ if prefix and 'prefix' not in kwargs:
+ kwargs['prefix'] = prefix
+ kwargs['output_stream'] = ostream
+
+ self.git.archive(treeish, **kwargs)
+ return self
+
+ def rev_parse(self, name):
+ return self.odb.resolve(name)
+
+ def __repr__(self):
+ return '<git.Repo "%s">' % self.git_dir
diff --git a/git/db/cmd/complex.py b/git/db/cmd/complex.py
index 73e2048f..2eed17ad 100644
--- a/git/db/cmd/complex.py
+++ b/git/db/cmd/complex.py
@@ -14,8 +14,13 @@ from git.util import (
hex_to_bin
)
from git.db.py.loose import PureLooseObjectODB
+from git.db.compat import RepoCompatInterface
from git.util import RemoteProgress
-from git.db.py.base import TransportDB
+from git.db.py.base import (
+ TransportDB,
+ PureRepositoryPathsMixin,
+ PureAlternatesFileMixin
+ )
from git.db.interface import FetchInfo as GitdbFetchInfo
from git.db.interface import PushInfo as GitdbPushInfo
@@ -33,7 +38,7 @@ import re
import sys
-__all__ = ('CmdGitDB', 'RemoteProgress' )
+__all__ = ('CmdGitDB', 'RemoteProgress', 'CmdCompatibilityGitDB' )
class PushInfo(GitdbPushInfo):
@@ -266,7 +271,7 @@ class FetchInfo(GitdbFetchInfo):
return cls(remote_local_ref, flags, note, old_commit_binsha)
-class CmdGitDB(PureLooseObjectODB, TransportDB):
+class CmdGitDB(PureLooseObjectODB, TransportDB, PureRepositoryPathsMixin, PureAlternatesFileMixin):
"""A database representing the default git object store, which includes loose
objects, pack files and an alternates file
@@ -276,7 +281,8 @@ class CmdGitDB(PureLooseObjectODB, TransportDB):
"""
def __init__(self, root_path, git):
"""Initialize this instance with the root and a git command"""
- super(CmdGitDB, self).__init__(root_path)
+ self._initialize(root_path)
+ super(CmdGitDB, self).__init__(self.objects_dir)
self._git = git
@classmethod
@@ -373,7 +379,8 @@ class CmdGitDB(PureLooseObjectODB, TransportDB):
#{ ODB Interface
-
+ # overrides from PureOdb Implementation, which is responsible only for writing
+ # objects
def info(self, sha):
hexsha, typename, size = self._git.get_object_header(bin_to_hex(sha))
return OInfo(hex_to_bin(hexsha), typename, size)
@@ -399,6 +406,10 @@ class CmdGitDB(PureLooseObjectODB, TransportDB):
except (GitCommandError, ValueError):
raise BadObject(partial_hexsha)
# END handle exceptions
+
+ @property
+ def git(self):
+ return self._git
#} END interface
@@ -432,3 +443,7 @@ class CmdGitDB(PureLooseObjectODB, TransportDB):
return self._get_fetch_info_from_stderr(proc, progress or RemoteProgress())
#} end transport db interface
+
+
+class CmdCompatibilityGitDB(CmdGitDB, RepoCompatInterface):
+ """Command git database with the compatabilty interface added for 0.3x code"""
diff --git a/git/db/compat.py b/git/db/compat.py
index 1c0ba6f5..16bf0562 100644
--- a/git/db/compat.py
+++ b/git/db/compat.py
@@ -11,3 +11,20 @@ class RepoCompatInterface(object):
@property
def bare(self):
return self.is_bare
+
+ def rev_parse(self, *args, **kwargs):
+ return self.resolve_object(*args, **kwargs)
+
+ @property
+ def odb(self):
+ """The odb is now an integrated part of each repository"""
+ return self
+
+ @property
+ def active_branch(self):
+ """The name of the currently active branch.
+
+ :return: Head to the active branch"""
+ return self.head.reference
+
+
diff --git a/git/db/interface.py b/git/db/interface.py
index bdda70b3..90421433 100644
--- a/git/db/interface.py
+++ b/git/db/interface.py
@@ -180,6 +180,7 @@ class CachingDB(object):
# END interface
+
class CompoundDB(object):
"""A database which delegates calls to sub-databases.
They should usually be cached and lazy-loaded"""
@@ -282,6 +283,7 @@ class TransportDB(object):
As refspecs involve symbolic names for references to be handled, we require
RefParse functionality. How this is done is up to the actual implementation."""
# The following variables need to be set by the derived class
+ __slots__ = tuple()
#{ Interface
@@ -325,6 +327,22 @@ class TransportDB(object):
:note: Remote objects can also be used for the actual push or fetch operation"""
raise NotImplementedError()
+ def remote(self, name='origin'):
+ """:return: Remote object with the given name
+ :note: it does not necessarily exist, hence this is just a more convenient way
+ to construct Remote objects"""
+ raise NotImplementedError()
+
+ def create_remote(self, name, url, **kwargs):
+ """Create a new remote with the given name pointing to the given url
+ :return: Remote instance, compatible to the Remote interface"""
+ return Remote.create(self, name, url, **kwargs)
+
+ def delete_remote(self, remote):
+ """Delete the given remote.
+ :param remote: a Remote instance"""
+ return Remote.remove(self, remote)
+
#}end interface
@@ -334,6 +352,7 @@ class ReferencesMixin(object):
The returned types are compatible to the interfaces of the pure python
reference implementation in GitDB.ref"""
+ __slots__ = tuple()
def resolve(self, name):
"""Resolve the given name into a binary sha. Valid names are as defined
@@ -342,6 +361,13 @@ class ReferencesMixin(object):
:raise AmbiguousObjectName:
:raise BadObject: """
raise NotImplementedError()
+
+ def resolve_object(self, name):
+ """As ``resolve()``, but returns the Objecft instance pointed to by the
+ resolved binary sha
+ :return: Object instance of the correct type, e.g. shas pointing to commits
+ will be represented by a Commit object"""
+ raise NotImplementedError()
@property
def references(self):
@@ -357,10 +383,64 @@ class ReferencesMixin(object):
raise NotImplementedError()
@property
+ def head(self):
+ """:return: HEAD Object pointing to the current head reference"""
+ raise NotImplementedError()
+
+ @property
def tags(self):
- """:return: An IterableList of TagReferences that are available in this repo"""
+ """:return: An IterableList of TagReferences or compatible items that
+ are available in this repo"""
+ raise NotImplementedError()
+
+ def tag(self, name):
+ """:return: Tag with the given name
+ :note: It does not necessarily exist, hence this is just a more convenient
+ way to construct TagReference objects"""
raise NotImplementedError()
+ def create_head(self, path, commit='HEAD', force=False, logmsg=None ):
+ """Create a new head within the repository.
+ :param commit: a resolvable name to the commit or a Commit or Reference instance the new head should point to
+ :param force: if True, a head will be created even though it already exists
+ Otherwise an exception will be raised.
+ :param logmsg: message to append to the reference log. If None, a default message
+ will be used
+ :return: newly created Head instances"""
+ raise NotImplementedError()
+
+ def delete_head(self, *heads):
+ """Delete the given heads
+ :param heads: list of Head references that are to be deleted"""
+ raise NotImplementedError()
+
+ def create_tag(self, path, ref='HEAD', message=None, force=False):
+ """Create a new tag reference.
+ :param path: name or path of the new tag.
+ :param ref: resolvable name of the reference or commit, or Commit or Reference
+ instance describing the commit the tag should point to.
+ :param message: message to be attached to the tag reference. This will
+ create an actual Tag object carrying the message. Otherwise a TagReference
+ will be generated.
+ :param force: if True, the Tag will be created even if another tag does already
+ exist at the given path. Otherwise an exception will be thrown
+ :return: TagReference object """
+ raise NotImplementedError()
+
+ def delete_tag(self, *tags):
+ """Delete the given tag references
+ :param tags: TagReferences to delete"""
+ raise NotImplementedError()
+
+
+ #{ Backward Compatability
+ # These aliases need to be provided by the implementing interface as well
+ refs = references
+ branches = heads
+ #} END backward compatability
+
+
+
class RepositoryPathsMixin(object):
"""Represents basic functionality of a full git repository. This involves an
@@ -385,6 +465,13 @@ class RepositoryPathsMixin(object):
only. Plain object databases need to be fed the "objects" directory path.
:param path: the path to initialize the repository with
+ It is a path to either the root git directory or the bare git repo::
+
+ repo = Repo("/Users/mtrier/Development/git-python")
+ repo = Repo("/Users/mtrier/Development/git-python.git")
+ repo = Repo("~/Development/git-python.git")
+ repo = Repo("$REPOSITORIES/Development/git-python.git")
+
:raise InvalidDBRoot:
"""
raise NotImplementedError()
diff --git a/git/db/py/base.py b/git/db/py/base.py
index f45711d5..cc326c27 100644
--- a/git/db/py/base.py
+++ b/git/db/py/base.py
@@ -35,7 +35,7 @@ import os
__all__ = ( 'PureObjectDBR', 'PureObjectDBW', 'PureRootPathDB', 'PureCompoundDB',
- 'PureConfigurationMixin', 'PureRepositoryPathsMixin')
+ 'PureConfigurationMixin', 'PureRepositoryPathsMixin', 'PureAlternatesFileMixin')
class PureObjectDBR(ObjectDBR):
@@ -385,3 +385,75 @@ class PureConfigurationMixin(ConfigurationMixin):
#} END interface
+
+class PureAlternatesFileMixin(object):
+ """Utility able to read and write an alternates file through the alternates property
+ It needs to be part of a type with the git_dir or db_path property.
+
+ The file by default is assumed to be located at the default location as imposed
+ by the standard git repository layout"""
+
+ #{ Configuration
+ alternates_filepath = os.path.join('info', 'alternates') # relative path to alternates file
+
+ #} END configuration
+
+ def __init__(self, *args, **kwargs):
+ super(PureAlternatesFileMixin, self).__init__(*args, **kwargs)
+ self._alternates_path() # throws on incompatible type
+
+ #{ Interface
+
+ def _alternates_path(self):
+ if hasattr(self, 'git_dir'):
+ return join(self.git_dir, 'objects', self.alternates_filepath)
+ elif hasattr(self, 'db_path'):
+ return self.db_path(self.alternates_filepath)
+ else:
+ raise AssertionError("This mixin requires a parent type with either the git_dir property or db_path method")
+ #END handle path
+
+ def _get_alternates(self):
+ """The list of alternates for this repo from which objects can be retrieved
+
+ :return: list of strings being pathnames of alternates"""
+ alternates_path = self._alternates_path()
+
+ if os.path.exists(alternates_path):
+ try:
+ f = open(alternates_path)
+ alts = f.read()
+ finally:
+ f.close()
+ return alts.strip().splitlines()
+ else:
+ return list()
+ # END handle path exists
+
+ def _set_alternates(self, alts):
+ """Sets the alternates
+
+ :parm alts:
+ is the array of string paths representing the alternates at which
+ git should look for objects, i.e. /home/user/repo/.git/objects
+
+ :raise NoSuchPathError:
+ :note:
+ The method does not check for the existance of the paths in alts
+ as the caller is responsible."""
+ alternates_path = self._alternates_path()
+ if not alts:
+ if isfile(alternates_path):
+ os.remove(alternates_path)
+ else:
+ try:
+ f = open(alternates_path, 'w')
+ f.write("\n".join(alts))
+ finally:
+ f.close()
+ # END file handling
+ # END alts handling
+
+ alternates = property(_get_alternates, _set_alternates, doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")
+
+ #} END interface
diff --git a/git/db/py/complex.py b/git/db/py/complex.py
index de68d4fd..6504b3ed 100644
--- a/git/db/py/complex.py
+++ b/git/db/py/complex.py
@@ -8,6 +8,7 @@ from base import (
PureRootPathDB,
PureRepositoryPathsMixin,
PureConfigurationMixin,
+ PureAlternatesFileMixin,
)
from resolve import PureReferencesMixin
@@ -17,6 +18,8 @@ from pack import PurePackedODB
from ref import PureReferenceDB
from submodule import PureSubmoduleDB
+from git.db.compat import RepoCompatInterface
+
from git.util import (
LazyMixin,
normpath,
@@ -30,10 +33,11 @@ from git.exc import (
)
import os
-__all__ = ('PureGitODB', 'PureGitDB')
+__all__ = ('PureGitODB', 'PureGitDB', 'PureCompatibilityGitDB')
-class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureSubmoduleDB):
+class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB,
+ PureSubmoduleDB, PureAlternatesFileMixin):
"""A git-style object-only database, which contains all objects in the 'objects'
subdirectory.
:note: The type needs to be initialized on the ./objects directory to function,
@@ -47,7 +51,7 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureSubmoduleDB)
# Directories
packs_dir = 'pack'
loose_dir = ''
- alternates_dir = os.path.join('info', 'alternates')
+
def __init__(self, root_path):
"""Initialize ourselves on a git ./objects directory"""
@@ -59,7 +63,7 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureSubmoduleDB)
loose_db = None
for subpath, dbcls in ((self.packs_dir, self.PackDBCls),
(self.loose_dir, self.LooseDBCls),
- (self.alternates_dir, self.PureReferenceDBCls)):
+ (self.alternates_filepath, self.PureReferenceDBCls)):
path = self.db_path(subpath)
if os.path.exists(path):
self._dbs.append(dbcls(path))
@@ -75,7 +79,7 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureSubmoduleDB)
# END handle error
# we the first one should have the store method
- assert loose_db is not None and hasattr(loose_db, 'store'), "First database needs store functionality"
+ assert loose_db is not None and hasattr(loose_db, 'store'), "One database needs store functionality"
# finally set the value
self._loose_db = loose_db
@@ -97,6 +101,7 @@ class PureGitODB(PureRootPathDB, PureObjectDBW, PureCompoundDB, PureSubmoduleDB)
#} END objectdbw interface
+
class PureGitDB(PureGitODB, PureRepositoryPathsMixin, PureConfigurationMixin, PureReferencesMixin):
"""Git like database with support for object lookup as well as reference resolution.
Our rootpath is set to the actual .git directory (bare on unbare).
@@ -112,3 +117,6 @@ class PureGitDB(PureGitODB, PureRepositoryPathsMixin, PureConfigurationMixin, Pu
+class PureCompatibilityGitDB(PureGitDB, RepoCompatInterface):
+ """Pure git database with a compatability layer required by 0.3x code"""
+
diff --git a/git/db/py/resolve.py b/git/db/py/resolve.py
index 9cce8efe..94992d11 100644
--- a/git/db/py/resolve.py
+++ b/git/db/py/resolve.py
@@ -5,6 +5,9 @@ from git.db.interface import ReferencesMixin
from git.exc import BadObject
from git.refs import SymbolicReference
from git.objects.base import Object
+from git.refs.head import HEAD
+from git.refs.headref import Head
+from git.refs.tag import TagReference
from git.util import (
join,
isdir,
@@ -281,17 +284,52 @@ class PureReferencesMixin(ReferencesMixin):
re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$')
+ #{ Configuration
+ # Types to use when instatiating references
+ TagReferenceCls = TagReference
+ HeadCls = Head
+ ReferenceCls = Reference
+ HEADCls = HEAD
+ #} END configuration
+
def resolve(self, name):
+ return self.resolve_object(name).binsha
+
+ def resolve_object(self, name):
return rev_parse(self, name)
@property
def references(self):
- raise NotImplementedError()
+ return self.ReferenceCls.list_items(self)
@property
def heads(self):
- raise NotImplementedError()
+ return self.HeadCls.list_items(self)
@property
def tags(self):
- raise NotImplementedError()
+ return self.TagReferenceCls.list_items(self)
+
+ def tag(self, name):
+ return self.tags[name]
+
+ @property
+ def head(self):
+ return self.HEADCls(self,'HEAD')
+
+ def create_head(self, path, commit='HEAD', force=False, logmsg=None ):
+ return self.HeadCls.create(self, path, commit, force, logmsg)
+
+ def delete_head(self, *heads, **kwargs):
+ return self.HeadCls.delete(self, *heads, **kwargs)
+
+ def create_tag(self, path, ref='HEAD', message=None, force=False, **kwargs):
+ return self.TagReferenceCls.create(self, path, ref, message, force, **kwargs)
+
+ def delete_tag(self, *tags):
+ return self.TagReferenceCls.delete(self, *tags)
+
+
+ # compat
+ branches = heads
+ refs = references
diff --git a/git/db/py/transport.py b/git/db/py/transport.py
index f8edfb23..00d222b0 100644
--- a/git/db/py/transport.py
+++ b/git/db/py/transport.py
@@ -9,6 +9,10 @@ from git.db.interface import ( TransportDB,
FetchInfo,
RefSpec )
+from git.refs.remote import RemoteReference
+from git.remote import Remote
+
+
__all__ = ["PureTransportDB"]
class PurePushInfo(PushInfo):
@@ -23,67 +27,32 @@ class PureFetchInfo(FetchInfo):
class PureTransportDB(TransportDB):
- """A database which allows to transport objects from and to different locations
- which are specified by urls (location) and refspecs (what to transport,
- see http://www.kernel.org/pub/software/scm/git/docs/git-fetch.html).
-
- At the beginning of a transport operation, it will be determined which objects
- have to be sent (either by this or by the other side).
-
- Afterwards a pack with the required objects is sent (or received). If there is
- nothing to send, the pack will be empty.
-
- The communication itself if implemented using a protocol instance which deals
- with the actual formatting of the lines sent.
-
- As refspecs involve symbolic names for references to be handled, we require
- RefParse functionality. How this is done is up to the actual implementation."""
# The following variables need to be set by the derived class
#{Configuration
protocol = None
+ RemoteCls = Remote
#}end configuraiton
#{ Interface
def fetch(self, url, refspecs, progress=None, **kwargs):
- """Fetch the objects defined by the given refspec from the given url.
- :param url: url identifying the source of the objects. It may also be
- a symbol from which the respective url can be resolved, like the
- name of the remote. The implementation should allow objects as input
- as well, these are assumed to resovle to a meaningful string though.
- :param refspecs: iterable of reference specifiers or RefSpec instance,
- identifying the references to be fetch from the remote.
- :param progress: callable which receives progress messages for user consumption
- :param kwargs: may be used for additional parameters that the actual implementation could
- find useful.
- :return: List of PureFetchInfo compatible instances which provide information about what
- was previously fetched, in the order of the input refspecs.
- :note: even if the operation fails, one of the returned PureFetchInfo instances
- may still contain errors or failures in only part of the refspecs.
- :raise: if any issue occours during the transport or if the url is not
- supported by the protocol.
- """
raise NotImplementedError()
def push(self, url, refspecs, progress=None, **kwargs):
- """Transport the objects identified by the given refspec to the remote
- at the given url.
- :param url: Decribes the location which is to receive the objects
- see fetch() for more details
- :param refspecs: iterable of refspecs strings or RefSpec instances
- to identify the objects to push
- :param progress: see fetch()
- :param kwargs: additional arguments which may be provided by the caller
- as they may be useful to the actual implementation
- :todo: what to return ?
- :raise: if any issue arises during transport or if the url cannot be handled"""
raise NotImplementedError()
@property
def remotes(self):
- """:return: An IterableList of Remote objects allowing to access and manipulate remotes
- :note: Remote objects can also be used for the actual push or fetch operation"""
- raise NotImplementedError()
+ return self.RemoteCls.list_items(self)
+
+ def remote(self, name='origin'):
+ return self.remotes[name]
+ def create_remote(self, name, url, **kwargs):
+ return self.RemoteCls.create(self, name, url, **kwargs)
+
+ def delete_remote(self, remote):
+ return self.RemoteCls.remove(self, remote)
+
#}end interface