GitHub Viewer
from . import util
from .util import (
mkhead,
sm_name,
sm_section,
SubmoduleConfigParser,
find_first_remote_branch
)
from git.objects.util import Traversable
from io import BytesIO # need a dict to set bloody .name field
from git.util import (
Iterable,
join_path_native,
to_native_path_linux,
RemoteProgress,
rmtree,
unbare_repo
)
from git.config import (
SectionConstraint,
GitConfigParser,
cp
)
from git.exc import (
InvalidGitRepositoryError,
NoSuchPathError,
RepositoryDirtyError
)
from git.compat import (
string_types,
defenc
)
import stat
import git
import os
import logging
import uuid
__all__ = ["Submodule", "UpdateProgress"]
log = logging.getLogger('git.objects.submodule.base')
class UpdateProgress(RemoteProgress):
"""Class providing detailed progress information to the caller who should
derive from it and implement the ``update(...)`` message"""
CLONE, FETCH, UPDWKTREE = [1 =v1.8.0: move submodule repository to new name)
As .gitmodules will be changed, you would need to make a commit afterwards. The changed .gitmodules file
will already be added to the index
:return: this submodule instance
"""
if self.name == new_name:
return self
# .git/config
pw = self.repo.config_writer()
# As we ourselves didn't write anything about submodules into the parent .git/config, we will not require
# it to exist, and just ignore missing entries
if pw.has_section(sm_section(self.name)):
pw.rename_section(sm_section(self.name), sm_section(new_name))
# end
pw.release()
# .gitmodules
cw = self.config_writer(write=True).config
cw.rename_section(sm_section(self.name), sm_section(new_name))
cw.release()
self._name = new_name
# .git/modules
mod = self.module()
if mod.has_separate_working_tree():
destination_module_abspath = self._module_abspath(self.repo, self.path, new_name)
source_dir = mod.git_dir
# Let's be sure the submodule name is not so obviously tied to a directory
if destination_module_abspath.startswith(mod.git_dir):
tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4()))
os.renames(source_dir, tmp_dir)
source_dir = tmp_dir
# end handle self-containment
os.renames(source_dir, destination_module_abspath)
self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath)
# end move separate git repository
return self
#} END edit interface
#{ Query Interface
@unbare_repo
def module(self):
""":return: Repo instance initialized from the repository at our submodule path
:raise InvalidGitRepositoryError: if a repository was not available. This could
also mean that it was not yet initialized"""
# late import to workaround circular dependencies
module_checkout_abspath = self.abspath
try:
repo = git.Repo(module_checkout_abspath)
if repo != self.repo:
return repo
# END handle repo uninitialized
except (InvalidGitRepositoryError, NoSuchPathError):
raise InvalidGitRepositoryError("No valid repository at %s" % module_checkout_abspath)
else:
raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath)
# END handle exceptions
def module_exists(self):
""":return: True if our module exists and is a valid git repository. See module() method"""
try:
self.module()
return True
except Exception:
return False
# END handle exception
def exists(self):
"""
:return: True if the submodule exists, False otherwise. Please note that
a submodule may exist (in the .gitmodules file) even though its module
doesn't exist on disk"""
# keep attributes for later, and restore them if we have no valid data
# this way we do not actually alter the state of the object
loc = locals()
for attr in self._cache_attrs:
try:
if hasattr(self, attr):
loc[attr] = getattr(self, attr)
# END if we have the attribute cache
except (cp.NoSectionError, ValueError):
# on PY3, this can happen apparently ... don't know why this doesn't happen on PY2
pass
# END for each attr
self._clear_cache()
try:
try:
self.path
return True
except Exception:
return False
# END handle exceptions
finally:
for attr in self._cache_attrs:
if attr in loc:
setattr(self, attr, loc[attr])
# END if we have a cache
# END reapply each attribute
# END handle object state consistency
@property
def branch(self):
""":return: The branch instance that we are to checkout
:raise InvalidGitRepositoryError: if our module is not yet checked out"""
return mkhead(self.module(), self._branch_path)
@property
def branch_path(self):
"""
:return: full (relative) path as string to the branch we would checkout
from the remote and track"""
return self._branch_path
@property
def branch_name(self):
""":return: the name of the branch, which is the shortest possible branch name"""
# use an instance method, for this we create a temporary Head instance
# which uses a repository that is available at least ( it makes no difference )
return git.Head(self.repo, self._branch_path).name
@property
def url(self):
""":return: The url to the repository which our module-repository refers to"""
return self._url
@property
def parent_commit(self):
""":return: Commit instance with the tree containing the .gitmodules file
:note: will always point to the current head's commit if it was not set explicitly"""
if self._parent_commit is None:
return self.repo.commit()
return self._parent_commit
@property
def name(self):
""":return: The name of this submodule. It is used to identify it within the
.gitmodules file.
:note: by default, the name is the path at which to find the submodule, but
in git-python it should be a unique identifier similar to the identifiers
used for remotes, which allows to change the path of the submodule
easily
"""
return self._name
def config_reader(self):
"""
:return: ConfigReader instance which allows you to qurey the configuration values
of this submodule, as provided by the .gitmodules file
:note: The config reader will actually read the data directly from the repository
and thus does not need nor care about your working tree.
:note: Should be cached by the caller and only kept as long as needed
:raise IOError: If the .gitmodules file/blob could not be read"""
return self._config_parser_constrained(read_only=True)
def children(self):
"""
:return: IterableList(Submodule, ...) an iterable list of submodules instances
which are children of this submodule or 0 if the submodule is not checked out"""
return self._get_intermediate_items(self)
#} END query interface
#{ Iterable Interface
@classmethod
def iter_items(cls, repo, parent_commit='HEAD'):
""":return: iterator yielding Submodule instances available in the given repository"""
pc = repo.commit(parent_commit) # parent commit instance
try:
parser = cls._config_parser(repo, pc, read_only=True)
except IOError:
raise StopIteration
# END handle empty iterator
rt = pc.tree # root tree
for sms in parser.sections():
n = sm_name(sms)
p = parser.get_value(sms, 'path')
u = parser.get_value(sms, 'url')
b = cls.k_head_default
if parser.has_option(sms, cls.k_head_option):
b = str(parser.get_value(sms, cls.k_head_option))
# END handle optional information
# get the binsha
index = repo.index
try:
sm = rt[p]
except KeyError:
# try the index, maybe it was just added
try:
entry = index.entries[index.entry_key(p, 0)]
sm = Submodule(repo, entry.binsha, entry.mode, entry.path)
except KeyError:
raise InvalidGitRepositoryError(
"Gitmodule path %r did not exist in revision of parent commit %s" % (p, parent_commit))
# END handle keyerror
# END handle critical error
# fill in remaining info - saves time as it doesn't have to be parsed again
sm._name = n
if pc != repo.commit():
sm._parent_commit = pc
# end set only if not most recent !
sm._branch_path = git.Head.to_full_path(b)
sm._url = u
yield sm
# END for each section
#} END iterable interface