diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2010-11-18 23:11:26 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2010-11-18 23:11:26 +0100 |
commit | 7cf2d5fcf0a3db793678dd6ba9fc1c24d4eeb36a (patch) | |
tree | 8bd4b8829768fe0195d41a3aa067aa1ac7435605 | |
parent | ebe8f644e751c1b2115301c1a961bef14d2cce89 (diff) | |
parent | 9519f186ce757cdba217f222c95c20033d00f91d (diff) | |
download | gitpython-7cf2d5fcf0a3db793678dd6ba9fc1c24d4eeb36a.tar.gz |
Merge branch 'refactor' into submodule
-rw-r--r-- | lib/git/objects/__init__.py | 11 | ||||
-rw-r--r-- | lib/git/objects/submodule/__init__.py | 1 | ||||
-rw-r--r-- | lib/git/objects/submodule/base.py (renamed from lib/git/objects/submodule.py) | 381 | ||||
-rw-r--r-- | lib/git/objects/submodule/root.py | 263 | ||||
-rw-r--r-- | lib/git/objects/submodule/util.py | 101 | ||||
-rw-r--r-- | lib/git/objects/tree.py | 2 | ||||
-rw-r--r-- | test/git/test_submodule.py | 4 |
7 files changed, 409 insertions, 354 deletions
diff --git a/lib/git/objects/__init__.py b/lib/git/objects/__init__.py index 85c7e38c..e8e0ef39 100644 --- a/lib/git/objects/__init__.py +++ b/lib/git/objects/__init__.py @@ -3,11 +3,18 @@ Import all submodules main classes into the package space """ import inspect from base import * +# Fix import dependency - add IndexObject to the util module, so that it can be +# imported by the submodule.base +import submodule.util +submodule.util.IndexObject = IndexObject +from submodule.base import * +from submodule.root import * + +# must come after submodule was made available from tag import * from blob import * -from tree import * from commit import * -from submodule import * +from tree import * from util import Actor __all__ = [ name for name, obj in locals().items() diff --git a/lib/git/objects/submodule/__init__.py b/lib/git/objects/submodule/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/git/objects/submodule/__init__.py @@ -0,0 +1 @@ + diff --git a/lib/git/objects/submodule.py b/lib/git/objects/submodule/base.py index c769b160..347af58e 100644 --- a/lib/git/objects/submodule.py +++ b/lib/git/objects/submodule/base.py @@ -1,111 +1,39 @@ -import base -from util import Traversable +import util +from util import ( + mkhead, + sm_name, + sm_section, + unbare_repo, + SubmoduleConfigParser, + find_first_remote_branch + ) +from git.objects.util import Traversable from StringIO import StringIO # need a dict to set bloody .name field -from git.util import Iterable, join_path_native, to_native_path_linux -from git.config import GitConfigParser, SectionConstraint -from git.exc import InvalidGitRepositoryError, NoSuchPathError +from git.util import ( + Iterable, + join_path_native, + to_native_path_linux + ) +from git.config import SectionConstraint +from git.exc import ( + InvalidGitRepositoryError, + NoSuchPathError + ) import stat import git import os import sys -import weakref -import shutil - -__all__ = ("Submodule", "RootModule") - -#{ Utilities - -def sm_section(name): - """:return: section title used in .gitmodules configuration file""" - return 'submodule "%s"' % name - -def sm_name(section): - """:return: name of the submodule as parsed from the section name""" - section = section.strip() - return section[11:-1] - -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 - -def find_first_remote_branch(remotes, branch): - """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError""" - for remote in remotes: - try: - return remote.refs[branch.name] - except IndexError: - continue - # END exception handling - #END for remote - raise InvalidGitRepositoryError("Didn't find remote branch %r in any of the given remotes", branch) - -#} END utilities - - -#{ Classes - -class SubmoduleConfigParser(GitConfigParser): - """ - Catches calls to _write, and updates the .gitmodules blob in the index - 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 - def set_submodule(self, submodule): - """Set this instance's submodule. It must be called before - the first write operation begins""" - self._smref = weakref.ref(submodule) - def flush_to_index(self): - """Flush changes in our configuration file to the index""" - assert self._smref is not None - # should always have a file here - assert not isinstance(self._file_or_files, StringIO) - - sm = self._smref() - if sm is not None: - 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 +import shutil - #} END interface - - #{ Overridden Methods - def write(self): - rval = super(SubmoduleConfigParser, self).write() - self.flush_to_index() - return rval - # END overridden methods +__all__ = ["Submodule"] -class Submodule(base.IndexObject, Iterable, Traversable): +# IndexObject comes via util module, its a 'hacky' fix thanks to pythons import +# mechanism which cause plenty of trouble of the only reason for packages and +# modules is refactoring - subpackages shoudn't depend on parent packages +class Submodule(util.IndexObject, Iterable, Traversable): """Implements access to a git submodule. They are special in that their sha represents a commit in the submodule's repository which is to be checked out at the path of this instance. @@ -129,6 +57,7 @@ class Submodule(base.IndexObject, Iterable, Traversable): def __init__(self, repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, branch=None): """Initialize this instance with its attributes. We only document the ones that differ from ``IndexObject`` + :param repo: Our parent repository :param binsha: binary sha referring to a commit in the remote repository, see url parameter :param parent_commit: see set_parent_commit() @@ -251,6 +180,7 @@ class Submodule(base.IndexObject, Iterable, Traversable): as well as the .gitmodules file, but will not create a new commit. If the submodule already exists, no matter if the configuration differs from the one provided, the existing submodule will be returned. + :param repo: Repository instance which should receive the submodule :param name: The name/identifier for the submodule :param path: repository-relative or absolute path at which the submodule @@ -348,6 +278,7 @@ class Submodule(base.IndexObject, Iterable, Traversable): 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. + :param recursive: if True, we will operate recursively and update child- modules as well. :param init: if True, the module repository will be cloned into place if necessary @@ -470,6 +401,7 @@ class Submodule(base.IndexObject, Iterable, Traversable): """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. @@ -572,6 +504,7 @@ class Submodule(base.IndexObject, Iterable, Traversable): 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. + :param module: If True, the module we point to will be deleted as well. If the module is currently on a commit which is not part of any branch in the remote, if the currently checked out branch @@ -676,6 +609,7 @@ class Submodule(base.IndexObject, Iterable, Traversable): def set_parent_commit(self, commit, check=True): """Set this instance to use the given commit whose tree is supposed to contain the .gitmodules blob. + :param commit: Commit'ish reference pointing at the root_tree :param check: if True, relatively expensive checks will be performed to verify validity of the submodule. @@ -879,255 +813,4 @@ class Submodule(base.IndexObject, Iterable, Traversable): # END for each section #} END iterable interface - - -class RootModule(Submodule): - """A (virtual) Root of all submodules in the given repository. It can be used - to more easily traverse all submodules of the master repository""" - - __slots__ = tuple() - - k_root_name = '__ROOT__' - - def __init__(self, repo): - # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) - super(RootModule, self).__init__( - repo, - binsha = self.NULL_BIN_SHA, - mode = self.k_default_mode, - path = '', - name = self.k_root_name, - parent_commit = repo.head.commit, - url = '', - branch = mkhead(repo, self.k_head_default) - ) - - - def _clear_cache(self): - """May not do anything""" - pass - - #{ Interface - - def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, to_latest_revision=False): - """Update the submodules of this repository to the current HEAD commit. - This method behaves smartly by determining changes of the path of a submodules - repository, next to changes to the to-be-checked-out commit or the branch to be - checked out. This works if the submodules ID does not change. - Additionally it will detect addition and removal of submodules, which will be handled - gracefully. - - :param previous_commit: If set to a commit'ish, the commit we should use - as the previous commit the HEAD pointed to before it was set to the commit it points to now. - If None, it defaults to ORIG_HEAD otherwise, or the parent of the current - commit if it is not given - :param recursive: if True, the children of submodules will be updated as well - using the same technique - :param force_remove: If submodules have been deleted, they will be forcibly removed. - Otherwise the update may fail if a submodule's repository cannot be deleted as - changes have been made to it (see Submodule.update() for more information) - :param init: If we encounter a new module which would need to be initialized, then do it. - :param to_latest_revision: If True, instead of checking out the revision pointed to - by this submodule's sha, the checked out tracking branch will be merged with the - newest remote branch fetched from the repository's origin""" - if self.repo.bare: - raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") - # END handle bare - - repo = self.repo - - # HANDLE COMMITS - ################## - cur_commit = repo.head.commit - if previous_commit is None: - symref = repo.head.orig_head() - try: - previous_commit = symref.commit - except Exception: - pcommits = cur_commit.parents - if pcommits: - previous_commit = pcommits[0] - else: - # in this special case, we just diff against ourselve, which - # means exactly no change - previous_commit = cur_commit - # END handle initial commit - # END no ORIG_HEAD - else: - previous_commit = repo.commit(previous_commit) # obtain commit object - # END handle previous commit - - - psms = self.list_items(repo, parent_commit=previous_commit) - sms = self.list_items(self.module()) - spsms = set(psms) - ssms = set(sms) - - # HANDLE REMOVALS - ################### - for rsm in (spsms - ssms): - # fake it into thinking its at the current commit to allow deletion - # of previous module. Trigger the cache to be updated before that - #rsm.url - rsm._parent_commit = repo.head.commit - rsm.remove(configuration=False, module=True, force=force_remove) - # END for each removed submodule - - # HANDLE PATH RENAMES - ##################### - # url changes + branch changes - for csm in (spsms & ssms): - psm = psms[csm.name] - sm = sms[csm.name] - - if sm.path != psm.path and psm.module_exists(): - # move the module to the new path - psm.move(sm.path, module=True, configuration=False) - # END handle path changes - - if sm.module_exists(): - # handle url change - if sm.url != psm.url: - # Add the new remote, remove the old one - # This way, if the url just changes, the commits will not - # have to be re-retrieved - nn = '__new_origin__' - smm = sm.module() - rmts = smm.remotes - - # don't do anything if we already have the url we search in place - if len([r for r in rmts if r.url == sm.url]) == 0: - - - assert nn not in [r.name for r in rmts] - smr = smm.create_remote(nn, sm.url) - smr.fetch() - - # If we have a tracking branch, it should be available - # in the new remote as well. - if len([r for r in smr.refs if r.remote_head == sm.branch.name]) == 0: - raise ValueError("Submodule branch named %r was not available in new submodule remote at %r" % (sm.branch.name, sm.url)) - # END head is not detached - - # now delete the changed one - rmt_for_deletion = None - for remote in rmts: - if remote.url == psm.url: - rmt_for_deletion = remote - break - # END if urls match - # END for each remote - - # if we didn't find a matching remote, but have exactly one, - # we can safely use this one - if rmt_for_deletion is None: - if len(rmts) == 1: - rmt_for_deletion = rmts[0] - else: - # if we have not found any remote with the original url - # we may not have a name. This is a special case, - # and its okay to fail here - # Alternatively we could just generate a unique name and leave all - # existing ones in place - raise InvalidGitRepositoryError("Couldn't find original remote-repo at url %r" % psm.url) - #END handle one single remote - # END handle check we found a remote - - orig_name = rmt_for_deletion.name - smm.delete_remote(rmt_for_deletion) - # NOTE: Currently we leave tags from the deleted remotes - # as well as separate tracking branches in the possibly totally - # changed repository ( someone could have changed the url to - # another project ). At some point, one might want to clean - # it up, but the danger is high to remove stuff the user - # has added explicitly - - # rename the new remote back to what it was - smr.rename(orig_name) - - # early on, we verified that the our current tracking branch - # exists in the remote. Now we have to assure that the - # sha we point to is still contained in the new remote - # tracking branch. - smsha = sm.binsha - found = False - rref = smr.refs[self.branch.name] - for c in rref.commit.traverse(): - if c.binsha == smsha: - found = True - break - # END traverse all commits in search for sha - # END for each commit - - if not found: - # adjust our internal binsha to use the one of the remote - # this way, it will be checked out in the next step - # This will change the submodule relative to us, so - # the user will be able to commit the change easily - print >> sys.stderr, "WARNING: Current sha %s was not contained in the tracking branch at the new remote, setting it the the remote's tracking branch" % sm.hexsha - sm.binsha = rref.commit.binsha - #END reset binsha - - #NOTE: All checkout is performed by the base implementation of update - - # END skip remote handling if new url already exists in module - # END handle url - - if sm.branch != psm.branch: - # finally, create a new tracking branch which tracks the - # new remote branch - smm = sm.module() - smmr = smm.remotes - try: - tbr = git.Head.create(smm, sm.branch.name) - except git.GitCommandError, e: - if e.status != 128: - raise - #END handle something unexpected - - # ... or reuse the existing one - tbr = git.Head(smm, git.Head.to_full_path(sm.branch.name)) - #END assure tracking branch exists - - tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch)) - # figure out whether the previous tracking branch contains - # new commits compared to the other one, if not we can - # delete it. - try: - tbr = find_first_remote_branch(smmr, psm.branch) - if len(smm.git.cherry(tbr, psm.branch)) == 0: - psm.branch.delete(smm, psm.branch) - #END delete original tracking branch if there are no changes - except InvalidGitRepositoryError: - # ignore it if the previous branch couldn't be found in the - # current remotes, this just means we can't handle it - pass - # END exception handling - - #NOTE: All checkout is done in the base implementation of update - - #END handle branch - #END handle - # END for each common submodule - - # FINALLY UPDATE ALL ACTUAL SUBMODULES - ###################################### - for sm in sms: - # update the submodule using the default method - sm.update(recursive=True, init=init, to_latest_revision=to_latest_revision) - - # update recursively depth first - question is which inconsitent - # state will be better in case it fails somewhere. Defective branch - # or defective depth. The RootSubmodule type will never process itself, - # which was done in the previous expression - if recursive: - type(self)(sm.module()).update(recursive=True, force_remove=force_remove, - init=init, to_latest_revision=to_latest_revision) - #END handle recursive - # END for each submodule to update - def module(self): - """:return: the actual repository containing the submodules""" - return self.repo - #} END interface -#} END classes diff --git a/lib/git/objects/submodule/root.py b/lib/git/objects/submodule/root.py new file mode 100644 index 00000000..82b8b271 --- /dev/null +++ b/lib/git/objects/submodule/root.py @@ -0,0 +1,263 @@ +from base import Submodule +from util import ( + mkhead, + find_first_remote_branch + ) +from git.exc import InvalidGitRepositoryError +import git + +import sys + +__all__ = ["RootModule"] + + +class RootModule(Submodule): + """A (virtual) Root of all submodules in the given repository. It can be used + to more easily traverse all submodules of the master repository""" + + __slots__ = tuple() + + k_root_name = '__ROOT__' + + def __init__(self, repo): + # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) + super(RootModule, self).__init__( + repo, + binsha = self.NULL_BIN_SHA, + mode = self.k_default_mode, + path = '', + name = self.k_root_name, + parent_commit = repo.head.commit, + url = '', + branch = mkhead(repo, self.k_head_default) + ) + + + def _clear_cache(self): + """May not do anything""" + pass + + #{ Interface + + def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, to_latest_revision=False): + """Update the submodules of this repository to the current HEAD commit. + This method behaves smartly by determining changes of the path of a submodules + repository, next to changes to the to-be-checked-out commit or the branch to be + checked out. This works if the submodules ID does not change. + Additionally it will detect addition and removal of submodules, which will be handled + gracefully. + + :param previous_commit: If set to a commit'ish, the commit we should use + as the previous commit the HEAD pointed to before it was set to the commit it points to now. + If None, it defaults to ORIG_HEAD otherwise, or the parent of the current + commit if it is not given + :param recursive: if True, the children of submodules will be updated as well + using the same technique + :param force_remove: If submodules have been deleted, they will be forcibly removed. + Otherwise the update may fail if a submodule's repository cannot be deleted as + changes have been made to it (see Submodule.update() for more information) + :param init: If we encounter a new module which would need to be initialized, then do it. + :param to_latest_revision: If True, instead of checking out the revision pointed to + by this submodule's sha, the checked out tracking branch will be merged with the + newest remote branch fetched from the repository's origin""" + if self.repo.bare: + raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") + # END handle bare + + repo = self.repo + + # HANDLE COMMITS + ################## + cur_commit = repo.head.commit + if previous_commit is None: + symref = repo.head.orig_head() + try: + previous_commit = symref.commit + except Exception: + pcommits = cur_commit.parents + if pcommits: + previous_commit = pcommits[0] + else: + # in this special case, we just diff against ourselve, which + # means exactly no change + previous_commit = cur_commit + # END handle initial commit + # END no ORIG_HEAD + else: + previous_commit = repo.commit(previous_commit) # obtain commit object + # END handle previous commit + + + psms = self.list_items(repo, parent_commit=previous_commit) + sms = self.list_items(self.module()) + spsms = set(psms) + ssms = set(sms) + + # HANDLE REMOVALS + ################### + for rsm in (spsms - ssms): + # fake it into thinking its at the current commit to allow deletion + # of previous module. Trigger the cache to be updated before that + #rsm.url + rsm._parent_commit = repo.head.commit + rsm.remove(configuration=False, module=True, force=force_remove) + # END for each removed submodule + + # HANDLE PATH RENAMES + ##################### + # url changes + branch changes + for csm in (spsms & ssms): + psm = psms[csm.name] + sm = sms[csm.name] + + if sm.path != psm.path and psm.module_exists(): + # move the module to the new path + psm.move(sm.path, module=True, configuration=False) + # END handle path changes + + if sm.module_exists(): + # handle url change + if sm.url != psm.url: + # Add the new remote, remove the old one + # This way, if the url just changes, the commits will not + # have to be re-retrieved + nn = '__new_origin__' + smm = sm.module() + rmts = smm.remotes + + # don't do anything if we already have the url we search in place + if len([r for r in rmts if r.url == sm.url]) == 0: + + + assert nn not in [r.name for r in rmts] + smr = smm.create_remote(nn, sm.url) + smr.fetch() + + # If we have a tracking branch, it should be available + # in the new remote as well. + if len([r for r in smr.refs if r.remote_head == sm.branch.name]) == 0: + raise ValueError("Submodule branch named %r was not available in new submodule remote at %r" % (sm.branch.name, sm.url)) + # END head is not detached + + # now delete the changed one + rmt_for_deletion = None + for remote in rmts: + if remote.url == psm.url: + rmt_for_deletion = remote + break + # END if urls match + # END for each remote + + # if we didn't find a matching remote, but have exactly one, + # we can safely use this one + if rmt_for_deletion is None: + if len(rmts) == 1: + rmt_for_deletion = rmts[0] + else: + # if we have not found any remote with the original url + # we may not have a name. This is a special case, + # and its okay to fail here + # Alternatively we could just generate a unique name and leave all + # existing ones in place + raise InvalidGitRepositoryError("Couldn't find original remote-repo at url %r" % psm.url) + #END handle one single remote + # END handle check we found a remote + + orig_name = rmt_for_deletion.name + smm.delete_remote(rmt_for_deletion) + # NOTE: Currently we leave tags from the deleted remotes + # as well as separate tracking branches in the possibly totally + # changed repository ( someone could have changed the url to + # another project ). At some point, one might want to clean + # it up, but the danger is high to remove stuff the user + # has added explicitly + + # rename the new remote back to what it was + smr.rename(orig_name) + + # early on, we verified that the our current tracking branch + # exists in the remote. Now we have to assure that the + # sha we point to is still contained in the new remote + # tracking branch. + smsha = sm.binsha + found = False + rref = smr.refs[self.branch.name] + for c in rref.commit.traverse(): + if c.binsha == smsha: + found = True + break + # END traverse all commits in search for sha + # END for each commit + + if not found: + # adjust our internal binsha to use the one of the remote + # this way, it will be checked out in the next step + # This will change the submodule relative to us, so + # the user will be able to commit the change easily + print >> sys.stderr, "WARNING: Current sha %s was not contained in the tracking branch at the new remote, setting it the the remote's tracking branch" % sm.hexsha + sm.binsha = rref.commit.binsha + #END reset binsha + + #NOTE: All checkout is performed by the base implementation of update + + # END skip remote handling if new url already exists in module + # END handle url + + if sm.branch != psm.branch: + # finally, create a new tracking branch which tracks the + # new remote branch + smm = sm.module() + smmr = smm.remotes + try: + tbr = git.Head.create(smm, sm.branch.name) + except git.GitCommandError, e: + if e.status != 128: + raise + #END handle something unexpected + + # ... or reuse the existing one + tbr = git.Head(smm, git.Head.to_full_path(sm.branch.name)) + #END assure tracking branch exists + + tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch)) + # figure out whether the previous tracking branch contains + # new commits compared to the other one, if not we can + # delete it. + try: + tbr = find_first_remote_branch(smmr, psm.branch) + if len(smm.git.cherry(tbr, psm.branch)) == 0: + psm.branch.delete(smm, psm.branch) + #END delete original tracking branch if there are no changes + except InvalidGitRepositoryError: + # ignore it if the previous branch couldn't be found in the + # current remotes, this just means we can't handle it + pass + # END exception handling + + #NOTE: All checkout is done in the base implementation of update + + #END handle branch + #END handle + # END for each common submodule + + # FINALLY UPDATE ALL ACTUAL SUBMODULES + ###################################### + for sm in sms: + # update the submodule using the default method + sm.update(recursive=True, init=init, to_latest_revision=to_latest_revision) + + # update recursively depth first - question is which inconsitent + # state will be better in case it fails somewhere. Defective branch + # or defective depth. The RootSubmodule type will never process itself, + # which was done in the previous expression + if recursive: + type(self)(sm.module()).update(recursive=True, force_remove=force_remove, + init=init, to_latest_revision=to_latest_revision) + #END handle recursive + # END for each submodule to update + + def module(self): + """:return: the actual repository containing the submodules""" + return self.repo + #} END interface +#} END classes diff --git a/lib/git/objects/submodule/util.py b/lib/git/objects/submodule/util.py new file mode 100644 index 00000000..ab5e345a --- /dev/null +++ b/lib/git/objects/submodule/util.py @@ -0,0 +1,101 @@ +import git +from git.exc import InvalidGitRepositoryError +from git.config import GitConfigParser +from StringIO import StringIO +import weakref + +__all__ = ( 'sm_section', 'sm_name', 'mkhead', 'unbare_repo', 'find_first_remote_branch', + 'SubmoduleConfigParser') + +#{ Utilities + +def sm_section(name): + """:return: section title used in .gitmodules configuration file""" + return 'submodule "%s"' % name + +def sm_name(section): + """:return: name of the submodule as parsed from the section name""" + section = section.strip() + return section[11:-1] + +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 + +def find_first_remote_branch(remotes, branch): + """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError""" + for remote in remotes: + try: + return remote.refs[branch.name] + except IndexError: + continue + # END exception handling + #END for remote + raise InvalidGitRepositoryError("Didn't find remote branch %r in any of the given remotes", branch) + +#} END utilities + + +#{ Classes + +class SubmoduleConfigParser(GitConfigParser): + """ + Catches calls to _write, and updates the .gitmodules blob in the index + 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 + def set_submodule(self, submodule): + """Set this instance's submodule. It must be called before + the first write operation begins""" + self._smref = weakref.ref(submodule) + + def flush_to_index(self): + """Flush changes in our configuration file to the index""" + assert self._smref is not None + # should always have a file here + assert not isinstance(self._file_or_files, StringIO) + + sm = self._smref() + if sm is not None: + 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 + + #} END interface + + #{ Overridden Methods + def write(self): + rval = super(SubmoduleConfigParser, self).write() + self.flush_to_index() + return rval + # END overridden methods + + +#} END classes diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py index 68c1ef2d..67431686 100644 --- a/lib/git/objects/tree.py +++ b/lib/git/objects/tree.py @@ -7,7 +7,7 @@ import util from base import IndexObject from git.util import join_path from blob import Blob -from submodule import Submodule +from submodule.base import Submodule import git.diff as diff from fun import ( diff --git a/test/git/test_submodule.py b/test/git/test_submodule.py index b2b61f39..e7807dcd 100644 --- a/test/git/test_submodule.py +++ b/test/git/test_submodule.py @@ -3,7 +3,8 @@ from test.testlib import * from git.exc import * -from git.objects.submodule import * +from git.objects.submodule.base import Submodule +from git.objects.submodule.root import RootModule from git.util import to_native_path_linux, join_path_native import shutil import git @@ -315,7 +316,6 @@ class TestSubmodule(TestBase): # Error if there is no submodule file here self.failUnlessRaises(IOError, Submodule._config_parser, rwrepo, rwrepo.commit(self.k_no_subm_tag), True) - @with_rw_repo(k_subm_current) def test_base_rw(self, rwrepo): self._do_base_tests(rwrepo) |