summaryrefslogtreecommitdiff
path: root/lib/git/objects/submodule.py
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2010-11-17 15:24:48 +0100
committerSebastian Thiel <byronimo@gmail.com>2010-11-17 15:24:48 +0100
commita1e6234c27abf041e4c8cd1a799950e7cd9104f6 (patch)
tree3e39fa29ea1478f9a31f378a25bd9c58bb71ed91 /lib/git/objects/submodule.py
parentb03933057df80ea9f860cc616eb7733f140f866e (diff)
downloadgitpython-a1e6234c27abf041e4c8cd1a799950e7cd9104f6.tar.gz
Inital implementation of Submodule.move including a very simple and to-be-improved test
Diffstat (limited to 'lib/git/objects/submodule.py')
-rw-r--r--lib/git/objects/submodule.py145
1 files changed, 123 insertions, 22 deletions
diff --git a/lib/git/objects/submodule.py b/lib/git/objects/submodule.py
index e07117a6..8a1ab6af 100644
--- a/lib/git/objects/submodule.py
+++ b/lib/git/objects/submodule.py
@@ -28,6 +28,19 @@ def sm_name(section):
def mkhead(repo, path):
""":return: New branch/head instance"""
return git.Head(repo, git.Head.to_full_path(path))
+
+def unbare_repo(func):
+ """Methods with this decorator raise InvalidGitRepositoryError if they
+ encounter a bare repository"""
+ def wrapper(self, *args, **kwargs):
+ if self.repo.bare:
+ raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__)
+ #END bare method
+ return func(self, *args, **kwargs)
+ # END wrapper
+ wrapper.__name__ = func.__name__
+ return wrapper
+
#} END utilities
@@ -39,10 +52,14 @@ class SubmoduleConfigParser(GitConfigParser):
with the new data, if we have written into a stream. Otherwise it will
add the local file to the index to make it correspond with the working tree.
Additionally, the cache must be cleared
+
+ Please note that no mutating method will work in bare mode
"""
def __init__(self, *args, **kwargs):
self._smref = None
+ self._index = None
+ self._auto_write = True
super(SubmoduleConfigParser, self).__init__(*args, **kwargs)
#{ Interface
@@ -59,7 +76,11 @@ class SubmoduleConfigParser(GitConfigParser):
sm = self._smref()
if sm is not None:
- sm.repo.index.add([sm.k_modules_file])
+ index = self._index
+ if index is None:
+ index = sm.repo.index
+ # END handle index
+ index.add([sm.k_modules_file], write=self._auto_write)
sm._clear_cache()
# END handle weakref
@@ -102,6 +123,7 @@ class Submodule(base.IndexObject, Iterable, Traversable):
:param url: The url to the remote repository which is the submodule
:param branch: Head instance to checkout when cloning the remote repository"""
super(Submodule, self).__init__(repo, binsha, mode, path)
+ self.size = 0
if parent_commit is not None:
self._parent_commit = parent_commit
if url is not None:
@@ -113,9 +135,7 @@ class Submodule(base.IndexObject, Iterable, Traversable):
self._name = name
def _set_cache_(self, attr):
- if attr == 'size':
- raise ValueError("Submodules do not have a size as they do not refer to anything in this repository")
- elif attr == '_parent_commit':
+ if attr == '_parent_commit':
# set a default value, which is the root tree of the current head
self._parent_commit = self.repo.commit()
elif attr in ('path', '_url', '_branch'):
@@ -235,8 +255,8 @@ class Submodule(base.IndexObject, Iterable, Traversable):
:note: works atomically, such that no change will be done if the repository
update fails for instance"""
if repo.bare:
- raise InvalidGitRepositoryError("Cannot add a submodule to bare repositories")
- #END handle bare mode
+ raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
+ # END handle bare repos
path = to_native_path_linux(path)
if path.endswith('/'):
@@ -280,7 +300,8 @@ class Submodule(base.IndexObject, Iterable, Traversable):
# END verify url
# update configuration and index
- writer = sm.config_writer()
+ index = sm.repo.index
+ writer = sm.config_writer(index=index, write=False)
writer.set_value('url', url)
writer.set_value('path', path)
@@ -302,11 +323,10 @@ class Submodule(base.IndexObject, Iterable, Traversable):
pcommit = repo.head.commit
sm._parent_commit = pcommit
sm.binsha = mrepo.head.commit.binsha
- repo.index.add([sm], write=True)
+ index.add([sm], write=True)
return sm
-
def update(self, recursive=False, init=True, to_latest_revision=False):
"""Update the repository of this submodule to point to the checkout
we point at with the binsha of this instance.
@@ -426,6 +446,85 @@ class Submodule(base.IndexObject, Iterable, Traversable):
return self
+ @unbare_repo
+ def move(self, module_path):
+ """Move the submodule to a another module path. This involves physically moving
+ the repository at our current path, changing the configuration, as well as
+ adjusting our index entry accordingly.
+ :param module_path: the path to which to move our module, given as
+ repository-relative path. Intermediate directories will be created
+ accordingly. If the path already exists, it must be empty.
+ Trailling (back)slashes are removed automatically
+ :return: self
+ :raise ValueError: if the module path existed and was not empty, or was a file
+ :note: Currently the method is not atomic, and it could leave the repository
+ in an inconsistent state if a sub-step fails for some reason
+ """
+ module_path = to_native_path_linux(module_path)
+ if module_path.endswith('/'):
+ module_path = module_path[:-1]
+ # END handle trailing slash
+
+ # VERIFY DESTINATION
+ if module_path == self.path:
+ return self
+ #END handle no change
+
+ dest_path = join_path_native(self.repo.working_tree_dir, module_path)
+ if os.path.isfile(dest_path):
+ raise ValueError("Cannot move repository onto a file: %s" % dest_path)
+ # END handle target files
+
+ # remove existing destination
+ if os.path.exists(dest_path):
+ if len(os.listdir(dest_path)):
+ raise ValueError("Destination module directory was not empty")
+ #END handle non-emptyness
+
+ if os.path.islink(dest_path):
+ os.remove(dest_path)
+ else:
+ os.rmdir(dest_path)
+ #END handle link
+ else:
+ # recreate parent directories
+ # NOTE: renames() does that now
+ pass
+ #END handle existance
+
+ # move the module into place if possible
+ cur_path = self.module_path()
+ if os.path.exists(cur_path):
+ os.renames(cur_path, dest_path)
+ #END move physical module
+
+ # NOTE: from now on, we would have to undo the rename !
+
+ # rename the index entry - have to manipulate the index directly as
+ # git-mv cannot be used on submodules ... yeah
+ index = self.repo.index
+ try:
+ ekey = index.entry_key(self.path, 0)
+ entry = index.entries[ekey]
+ del(index.entries[ekey])
+ nentry = git.IndexEntry(entry[:3]+(module_path,)+entry[4:])
+ ekey = index.entry_key(module_path, 0)
+ index.entries[ekey] = nentry
+ except KeyError:
+ raise ValueError("Submodule's entry at %r did not exist" % (self.path))
+ #END handle submodule doesn't exist
+
+ # update configuration
+ writer = self.config_writer(index=index) # auto-write
+ writer.set_value('path', module_path)
+ self.path = module_path
+ del(writer)
+
+ return self
+
+
+
+ @unbare_repo
def remove(self, module=True, force=False, configuration=True, dry_run=False):
"""Remove this submodule from the repository. This will remove our entry
from the .gitmodules file and the entry in the .git/config file.
@@ -449,10 +548,6 @@ class Submodule(base.IndexObject, Iterable, Traversable):
:note: doesn't work in bare repositories
:raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
:raise OSError: if directories or files could not be removed"""
- if self.repo.bare:
- raise InvalidGitRepositoryError("Cannot delete a submodule in bare repository")
- # END handle bare mode
-
if not (module + configuration):
raise ValueError("Need to specify to delete at least the module, or the configuration")
# END handle params
@@ -565,31 +660,37 @@ class Submodule(base.IndexObject, Iterable, Traversable):
return self
- def config_writer(self):
+ @unbare_repo
+ def config_writer(self, index=None, write=True):
""":return: a config writer instance allowing you to read and write the data
belonging to this submodule into the .gitmodules file.
+ :param index: if not None, an IndexFile instance which should be written.
+ defaults to the index of the Submodule's parent repository.
+ :param write: if True, the index will be written each time a configuration
+ value changes.
+ :note: the parameters allow for a more efficient writing of the index,
+ as you can pass in a modified index on your own, prevent automatic writing,
+ and write yourself once the whole operation is complete
:raise ValueError: if trying to get a writer on a parent_commit which does not
match the current head commit
:raise IOError: If the .gitmodules file/blob could not be read"""
- if self.repo.bare:
- raise InvalidGitRepositoryError("Cannot change submodule configuration in a bare repository")
- return self._config_parser_constrained(read_only=False)
+ writer = self._config_parser_constrained(read_only=False)
+ if index is not None:
+ writer.config._index = index
+ writer.config._auto_write = write
+ return writer
#} 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
-
- if self.repo.bare:
- raise InvalidGitRepositoryError("Cannot retrieve module repository in bare parent repositories")
- # END handle bare mode
-
module_path = self.module_path()
try:
repo = git.Repo(module_path)