summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--git/objects/submodule/base.py305
-rw-r--r--git/objects/submodule/root.py421
-rw-r--r--git/test/test_submodule.py7
3 files changed, 381 insertions, 352 deletions
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index b8924291..7f06a7b1 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -429,7 +429,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
return sm
def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False,
- force=False):
+ force=False, keep_going=False):
"""Update the repository of this submodule to point to the checkout
we point at with the binsha of this instance.
@@ -450,6 +450,10 @@ class Submodule(util.IndexObject, Iterable, Traversable):
remote branch. This will essentially 'forget' commits.
If False, local tracking branches that are in the future of their respective remote branches will simply
not be moved.
+ :param keep_going: if True, we will ignore but log all errors, and keep going recursively.
+ Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see
+ otherwise.
+ In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
:note: does nothing in bare repositories
:note: method is definitely not atomic if recurisve is True
:return: self"""
@@ -470,152 +474,158 @@ class Submodule(util.IndexObject, Iterable, Traversable):
mrepo = None
# END init mrepo
- # ASSURE REPO IS PRESENT AND UPTODATE
- #####################################
try:
- mrepo = self.module()
- rmts = mrepo.remotes
- len_rmts = len(rmts)
- for i, remote in enumerate(rmts):
- op = FETCH
- if i == 0:
- op |= BEGIN
- # END handle start
-
- progress.update(op, i, len_rmts, prefix + "Fetching remote %s of submodule %r" % (remote, self.name))
- #===============================
+ # ASSURE REPO IS PRESENT AND UPTODATE
+ #####################################
+ try:
+ mrepo = self.module()
+ rmts = mrepo.remotes
+ len_rmts = len(rmts)
+ for i, remote in enumerate(rmts):
+ op = FETCH
+ if i == 0:
+ op |= BEGIN
+ # END handle start
+
+ progress.update(op, i, len_rmts, prefix + "Fetching remote %s of submodule %r" % (remote, self.name))
+ #===============================
+ if not dry_run:
+ remote.fetch(progress=progress)
+ # END handle dry-run
+ #===============================
+ if i == len_rmts - 1:
+ op |= END
+ # END handle end
+ progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
+ # END fetch new data
+ except InvalidGitRepositoryError:
+ if not init:
+ return self
+ # END early abort if init is not allowed
+
+ # there is no git-repository yet - but delete empty paths
+ checkout_module_abspath = self.abspath
+ if not dry_run and os.path.isdir(checkout_module_abspath):
+ try:
+ os.rmdir(checkout_module_abspath)
+ except OSError:
+ raise OSError("Module directory at %r does already exist and is non-empty"
+ % checkout_module_abspath)
+ # END handle OSError
+ # END handle directory removal
+
+ # don't check it out at first - nonetheless it will create a local
+ # branch according to the remote-HEAD if possible
+ progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning url '%s' to '%s' in submodule %r" %
+ (self.url, checkout_module_abspath, self.name))
if not dry_run:
- remote.fetch(progress=progress)
+ mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True)
# END handle dry-run
- #===============================
- if i == len_rmts - 1:
- op |= END
- # END handle end
- progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
- # END fetch new data
- except InvalidGitRepositoryError:
- if not init:
- return self
- # END early abort if init is not allowed
+ progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath)
- # there is no git-repository yet - but delete empty paths
- checkout_module_abspath = self.abspath
- if not dry_run and os.path.isdir(checkout_module_abspath):
- try:
- os.rmdir(checkout_module_abspath)
- except OSError:
- raise OSError("Module directory at %r does already exist and is non-empty"
- % checkout_module_abspath)
- # END handle OSError
- # END handle directory removal
-
- # don't check it out at first - nonetheless it will create a local
- # branch according to the remote-HEAD if possible
- progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning %s to %s in submodule %r" %
- (self.url, checkout_module_abspath, self.name))
- if not dry_run:
- mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True)
- # END handle dry-run
- progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath)
-
- if not dry_run:
- # see whether we have a valid branch to checkout
- try:
- # find a remote which has our branch - we try to be flexible
- remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
- local_branch = mkhead(mrepo, self.branch_path)
-
- # have a valid branch, but no checkout - make sure we can figure
- # that out by marking the commit with a null_sha
- local_branch.set_object(util.Object(mrepo, self.NULL_BIN_SHA))
- # END initial checkout + branch creation
-
- # make sure HEAD is not detached
- mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
- mrepo.head.ref.set_tracking_branch(remote_branch)
- except IndexError:
- log.warn("Failed to checkout tracking branch %s", self.branch_path)
- # END handle tracking branch
-
- # NOTE: Have to write the repo config file as well, otherwise
- # the default implementation will be offended and not update the repository
- # Maybe this is a good way to assure it doesn't get into our way, but
- # we want to stay backwards compatible too ... . Its so redundant !
- writer = self.repo.config_writer()
- writer.set_value(sm_section(self.name), 'url', self.url)
- writer.release()
+ if not dry_run:
+ # see whether we have a valid branch to checkout
+ try:
+ # find a remote which has our branch - we try to be flexible
+ remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
+ local_branch = mkhead(mrepo, self.branch_path)
+
+ # have a valid branch, but no checkout - make sure we can figure
+ # that out by marking the commit with a null_sha
+ local_branch.set_object(util.Object(mrepo, self.NULL_BIN_SHA))
+ # END initial checkout + branch creation
+
+ # make sure HEAD is not detached
+ mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
+ mrepo.head.ref.set_tracking_branch(remote_branch)
+ except IndexError:
+ log.warn("Failed to checkout tracking branch %s", self.branch_path)
+ # END handle tracking branch
+
+ # NOTE: Have to write the repo config file as well, otherwise
+ # the default implementation will be offended and not update the repository
+ # Maybe this is a good way to assure it doesn't get into our way, but
+ # we want to stay backwards compatible too ... . Its so redundant !
+ writer = self.repo.config_writer()
+ writer.set_value(sm_section(self.name), 'url', self.url)
+ writer.release()
+ # END handle dry_run
+ # END handle initalization
+
+ # DETERMINE SHAS TO CHECKOUT
+ ############################
+ binsha = self.binsha
+ hexsha = self.hexsha
+ if mrepo is not None:
+ # mrepo is only set if we are not in dry-run mode or if the module existed
+ is_detached = mrepo.head.is_detached
# END handle dry_run
- # END handle initalization
-
- # DETERMINE SHAS TO CHECKOUT
- ############################
- binsha = self.binsha
- hexsha = self.hexsha
- if mrepo is not None:
- # mrepo is only set if we are not in dry-run mode or if the module existed
- is_detached = mrepo.head.is_detached
- # END handle dry_run
-
- if mrepo is not None and to_latest_revision:
- msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
- if not is_detached:
- rref = mrepo.head.ref.tracking_branch()
- if rref is not None:
- rcommit = rref.commit
- binsha = rcommit.binsha
- hexsha = rcommit.hexsha
- else:
- log.error("%s a tracking branch was not set for local branch '%s'", msg_base, mrepo.head.ref)
- # END handle remote ref
- else:
- log.error("%s there was no local tracking branch", msg_base)
- # END handle detached head
- # END handle to_latest_revision option
-
- # update the working tree
- # handles dry_run
- if mrepo is not None and mrepo.head.commit.binsha != binsha:
- # We must assure that our destination sha (the one to point to) is in the future of our current head.
- # Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed
- # We also handle the case that history has been rewritten, leaving no merge-base. In that case
- # we behave conservatively, protecting possible changes the user had done
- may_reset = True
- if mrepo.head.commit.binsha != self.NULL_BIN_SHA:
- base_commit = mrepo.merge_base(mrepo.head.commit, hexsha)
- if len(base_commit) == 0 or base_commit[0].hexsha == hexsha:
- if force:
- log.debug("Will force checkout or reset on local branch that is possibly in the future of" +
- "the commit it will be checked out to, effectively 'forgetting' new commits")
+
+ if mrepo is not None and to_latest_revision:
+ msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
+ if not is_detached:
+ rref = mrepo.head.ref.tracking_branch()
+ if rref is not None:
+ rcommit = rref.commit
+ binsha = rcommit.binsha
+ hexsha = rcommit.hexsha
else:
- log.info("Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits",
- is_detached and "checkout" or "reset", mrepo.head, mrepo)
- may_reset = False
- # end handle force
- # end handle if we are in the future
-
- if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True):
- raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository")
- # end handle force and dirty state
- # end handle empty repo
-
- # end verify future/past
- progress.update(BEGIN | UPDWKTREE, 0, 1, prefix +
- "Updating working tree at %s for submodule %r to revision %s"
- % (self.path, self.name, hexsha))
-
- if not dry_run and may_reset:
- if is_detached:
- # NOTE: for now we force, the user is no supposed to change detached
- # submodules anyway. Maybe at some point this becomes an option, to
- # properly handle user modifications - see below for future options
- # regarding rebase and merge.
- mrepo.git.checkout(hexsha, force=force)
+ log.error("%s a tracking branch was not set for local branch '%s'", msg_base, mrepo.head.ref)
+ # END handle remote ref
else:
- mrepo.head.reset(hexsha, index=True, working_tree=True)
- # END handle checkout
- # if we may reset/checkout
- progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name)
- # END update to new commit only if needed
+ log.error("%s there was no local tracking branch", msg_base)
+ # END handle detached head
+ # END handle to_latest_revision option
+
+ # update the working tree
+ # handles dry_run
+ if mrepo is not None and mrepo.head.commit.binsha != binsha:
+ # We must assure that our destination sha (the one to point to) is in the future of our current head.
+ # Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed
+ # We also handle the case that history has been rewritten, leaving no merge-base. In that case
+ # we behave conservatively, protecting possible changes the user had done
+ may_reset = True
+ if mrepo.head.commit.binsha != self.NULL_BIN_SHA:
+ base_commit = mrepo.merge_base(mrepo.head.commit, hexsha)
+ if len(base_commit) == 0 or base_commit[0].hexsha == hexsha:
+ if force:
+ log.debug("Will force checkout or reset on local branch that is possibly in the future of" +
+ "the commit it will be checked out to, effectively 'forgetting' new commits")
+ else:
+ log.info("Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits",
+ is_detached and "checkout" or "reset", mrepo.head, mrepo)
+ may_reset = False
+ # end handle force
+ # end handle if we are in the future
+
+ if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True):
+ raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository")
+ # end handle force and dirty state
+ # end handle empty repo
+
+ # end verify future/past
+ progress.update(BEGIN | UPDWKTREE, 0, 1, prefix +
+ "Updating working tree at %s for submodule %r to revision %s"
+ % (self.path, self.name, hexsha))
+
+ if not dry_run and may_reset:
+ if is_detached:
+ # NOTE: for now we force, the user is no supposed to change detached
+ # submodules anyway. Maybe at some point this becomes an option, to
+ # properly handle user modifications - see below for future options
+ # regarding rebase and merge.
+ mrepo.git.checkout(hexsha, force=force)
+ else:
+ mrepo.head.reset(hexsha, index=True, working_tree=True)
+ # END handle checkout
+ # if we may reset/checkout
+ progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name)
+ # END update to new commit only if needed
+ except Exception as err:
+ if not keep_going:
+ raise
+ log.error(str(err))
+ # end handle keep_going
# HANDLE RECURSION
##################
@@ -624,7 +634,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
if mrepo is not None:
for submodule in self.iter_items(self.module()):
submodule.update(recursive, init, to_latest_revision, progress=progress, dry_run=dry_run,
- force=force)
+ force=force, keep_going=keep_going)
# END handle recursive update
# END handle dry run
# END for each submodule
@@ -887,13 +897,16 @@ class Submodule(util.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, or None to always point to the
+
+ :param commit:
+ Commit'ish reference pointing at the root_tree, or None to always point to the
most recent commit
- :param check: if True, relatively expensive checks will be performed to verify
+ :param check:
+ if True, relatively expensive checks will be performed to verify
validity of the submodule.
:raise ValueError: if the commit's tree didn't contain the .gitmodules blob.
- :raise ValueError: if the parent commit didn't store this submodule under the
- current path
+ :raise ValueError:
+ if the parent commit didn't store this submodule under the current path
:return: self"""
if commit is None:
self._parent_commit = None
@@ -976,7 +989,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
pw.release()
# .gitmodules
- cw = self.config_writer(write=False).config
+ cw = self.config_writer(write=True).config
cw.rename_section(sm_section(self.name), sm_section(new_name))
cw.release()
diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py
index 352e0f8b..dd023d46 100644
--- a/git/objects/submodule/root.py
+++ b/git/objects/submodule/root.py
@@ -61,7 +61,8 @@ class RootModule(Submodule):
#{ Interface
def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
- to_latest_revision=False, progress=None, dry_run=False, force_reset=False):
+ to_latest_revision=False, progress=None, dry_run=False, force_reset=False,
+ keep_going=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
@@ -89,6 +90,10 @@ class RootModule(Submodule):
:param progress: RootUpdateProgress instance or None if no progress should be sent
:param dry_run: if True, operations will not actually be performed. Progress messages
will change accordingly to indicate the WOULD DO state of the operation.
+ :param keep_going: if True, we will ignore but log all errors, and keep going recursively.
+ Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see
+ otherwise.
+ In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
:return: self"""
if self.repo.bare:
raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
@@ -104,218 +109,225 @@ class RootModule(Submodule):
repo = self.repo
- # SETUP BASE COMMIT
- ###################
- cur_commit = repo.head.commit
- if previous_commit is None:
- try:
- previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
- if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
- raise IndexError
- # END handle initial commit
- except IndexError:
- # in new repositories, there is no previous commit
- previous_commit = cur_commit
- # END exception handling
- 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(repo)
- spsms = set(psms)
- ssms = set(sms)
-
- # HANDLE REMOVALS
- ###################
- rrsm = (spsms - ssms)
- len_rrsm = len(rrsm)
- for i, rsm in enumerate(rrsm):
- op = REMOVE
- if i == 0:
- op |= BEGIN
- # END handle begin
-
- # fake it into thinking its at the current commit to allow deletion
- # of previous module. Trigger the cache to be updated before that
- progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
- rsm._parent_commit = repo.head.commit
- rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run)
-
- if i == len_rrsm - 1:
- op |= END
- # END handle end
- progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
- # END for each removed submodule
-
- # HANDLE PATH RENAMES
- #####################
- # url changes + branch changes
- csms = (spsms & ssms)
- len_csms = len(csms)
- for i, csm in enumerate(csms):
- psm = psms[csm.name]
- sm = sms[csm.name]
-
- # PATH CHANGES
- ##############
- if sm.path != psm.path and psm.module_exists():
- progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
- "Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath))
- # move the module to the new path
- if not dry_run:
- psm.move(sm.path, module=True, configuration=False)
- # END handle dry_run
- progress.update(
- END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
- # 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:
- progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
- "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
+ try:
+ # SETUP BASE COMMIT
+ ###################
+ cur_commit = repo.head.commit
+ if previous_commit is None:
+ try:
+ previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
+ if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
+ raise IndexError
+ # END handle initial commit
+ except IndexError:
+ # in new repositories, there is no previous commit
+ previous_commit = cur_commit
+ # END exception handling
+ 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(repo)
+ spsms = set(psms)
+ ssms = set(sms)
+
+ # HANDLE REMOVALS
+ ###################
+ rrsm = (spsms - ssms)
+ len_rrsm = len(rrsm)
+
+ for i, rsm in enumerate(rrsm):
+ op = REMOVE
+ if i == 0:
+ op |= BEGIN
+ # END handle begin
+
+ # fake it into thinking its at the current commit to allow deletion
+ # of previous module. Trigger the cache to be updated before that
+ progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
+ rsm._parent_commit = repo.head.commit
+ rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run)
+
+ if i == len_rrsm - 1:
+ op |= END
+ # END handle end
+ progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
+ # END for each removed submodule
+
+ # HANDLE PATH RENAMES
+ #####################
+ # url changes + branch changes
+ csms = (spsms & ssms)
+ len_csms = len(csms)
+ for i, csm in enumerate(csms):
+ psm = psms[csm.name]
+ sm = sms[csm.name]
+
+ # PATH CHANGES
+ ##############
+ if sm.path != psm.path and psm.module_exists():
+ progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
+ "Moving repository of submodule %r from %s to %s" % (sm.name, psm.abspath, sm.abspath))
+ # move the module to the new path
+ if not dry_run:
+ psm.move(sm.path, module=True, configuration=False)
+ # END handle dry_run
+ progress.update(
+ END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
+ # 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:
+ progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
+ "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
+
+ if not dry_run:
+ assert nn not in [r.name for r in rmts]
+ smr = smm.create_remote(nn, sm.url)
+ smr.fetch(progress=progress)
+
+ # 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
+ log.warn("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 handle dry_run
+ progress.update(
+ END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
+ # END skip remote handling if new url already exists in module
+ # END handle url
+
+ # HANDLE PATH CHANGES
+ #####################
+ if sm.branch_path != psm.branch_path:
+ # finally, create a new tracking branch which tracks the
+ # new remote branch
+ progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
+ "Changing branch of submodule %r from %s to %s"
+ % (sm.name, psm.branch_path, sm.branch_path))
if not dry_run:
- assert nn not in [r.name for r in rmts]
- smr = smm.create_remote(nn, sm.url)
- smr.fetch(progress=progress)
-
- # 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
- log.warn("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
+ smm = sm.module()
+ smmr = smm.remotes
+ try:
+ tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD')
+ except OSError:
+ # ... or reuse the existing one
+ tbr = git.Head(smm, sm.branch_path)
+ # END assure tracking branch exists
+
+ tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
+ # 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_name)
+ 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 dry_run
- progress.update(
- END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
- # END skip remote handling if new url already exists in module
- # END handle url
-
- # HANDLE PATH CHANGES
- #####################
- if sm.branch_path != psm.branch_path:
- # finally, create a new tracking branch which tracks the
- # new remote branch
- progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
- "Changing branch of submodule %r from %s to %s"
- % (sm.name, psm.branch_path, sm.branch_path))
- if not dry_run:
- smm = sm.module()
- smmr = smm.remotes
- try:
- tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD')
- except OSError:
- # ... or reuse the existing one
- tbr = git.Head(smm, sm.branch_path)
- # END assure tracking branch exists
-
- tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
- # 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_name)
- 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 dry_run
- progress.update(
- END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
- # END handle branch
- # END handle
- # END for each common submodule
+ progress.update(
+ END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
+ # END handle branch
+ # END handle
+ # END for each common submodule
+ except Exception as err:
+ if not keep_going:
+ raise
+ log.error(str(err))
+ # end handle keep_going
# FINALLY UPDATE ALL ACTUAL SUBMODULES
######################################
for sm in sms:
# update the submodule using the default method
sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
- progress=progress, dry_run=dry_run, force=force_reset)
+ progress=progress, dry_run=dry_run, force=force_reset, keep_going=keep_going)
# update recursively depth first - question is which inconsitent
# state will be better in case it fails somewhere. Defective branch
@@ -326,7 +338,8 @@ class RootModule(Submodule):
if sm.module_exists():
type(self)(sm.module()).update(recursive=True, force_remove=force_remove,
init=init, to_latest_revision=to_latest_revision,
- progress=progress, dry_run=dry_run, force_reset=force_reset)
+ progress=progress, dry_run=dry_run, force_reset=force_reset,
+ keep_going=keep_going)
# END handle dry_run
# END handle recursive
# END for each submodule to update
diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py
index 047e0049..b9502f75 100644
--- a/git/test/test_submodule.py
+++ b/git/test/test_submodule.py
@@ -731,18 +731,21 @@ class TestSubmodule(TestBase):
new_name = csm.name + '/mine'
assert csm.rename(new_name).name == new_name
assert_exists(csm)
+ assert csm.repo.is_dirty(index=True, working_tree=False), "index must contain changed .gitmodules file"
+ csm.repo.index.commit("renamed module")
# keep_going evaluation
rsm = parent.submodule_update()
assert_exists(sm)
assert_exists(csm)
- csm_writer = csm.config_writer().set_value('url', 'foo')
+ csm_writer = csm.config_writer().set_value('url', 'bar')
csm_writer.release()
csm.repo.index.commit("Have to commit submodule change for algorithm to pick it up")
- assert csm.url == 'foo'
+ assert csm.url == 'bar'
self.failUnlessRaises(Exception, rsm.update, recursive=True, to_latest_revision=True, progress=prog)
assert_exists(csm)
+ rsm.update(recursive=True, to_latest_revision=True, progress=prog, keep_going=True)
# remove
sm_module_path = sm.module().git_dir