diff options
-rw-r--r-- | git/objects/submodule/base.py | 305 | ||||
-rw-r--r-- | git/objects/submodule/root.py | 421 | ||||
-rw-r--r-- | git/test/test_submodule.py | 7 |
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 |