diff options
-rw-r--r-- | objects/submodule/base.py | 127 | ||||
-rw-r--r-- | objects/submodule/root.py | 235 | ||||
-rw-r--r-- | test/test_submodule.py | 69 |
3 files changed, 261 insertions, 170 deletions
diff --git a/objects/submodule/base.py b/objects/submodule/base.py index c47e2bfc..88cc42da 100644 --- a/objects/submodule/base.py +++ b/objects/submodule/base.py @@ -329,6 +329,15 @@ class Submodule(util.IndexObject, Iterable, Traversable): if progress is None: progress = UpdateProgress() #END handle progress + prefix = '' + if dry_run: + prefix = "DRY-RUN: " + #END handle prefix + + # to keep things plausible in dry-run mode + if dry_run: + mrepo = None + #END init mrepo # ASSURE REPO IS PRESENT AND UPTODATE ##################################### @@ -342,14 +351,16 @@ class Submodule(util.IndexObject, Iterable, Traversable): op |= BEGIN #END handle start - progress.update(op, i, len_rmts, "Fetching remote %s" % remote) + progress.update(op, i, len_rmts, prefix+"Fetching remote %s of submodule %r" % (remote, self.name)) #=============================== - remote.fetch(progress=progress) + 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, "Done fetching remote %s" % remote) + progress.update(op, i, len_rmts, prefix+"Done fetching remote of submodule %r" % self.name) #END fetch new data except InvalidGitRepositoryError: if not init: @@ -359,7 +370,7 @@ class Submodule(util.IndexObject, Iterable, Traversable): # there is no git-repository yet - but delete empty paths module_path = join_path_native(self.repo.working_tree_dir, self.path) - if os.path.isdir(module_path): + if not dry_run and os.path.isdir(module_path): try: os.rmdir(module_path) except OSError: @@ -369,33 +380,38 @@ class Submodule(util.IndexObject, Iterable, Traversable): # 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, "Cloning %s to %s" % (self.url, module_path)) - mrepo = git.Repo.clone_from(self.url, module_path, n=True) - progress.update(END|CLONE, 0, 1, "Done cloning to %s" % module_path) + progress.update(BEGIN|CLONE, 0, 1, prefix+"Cloning %s to %s in submodule %r" % (self.url, module_path, self.name)) + if not dry_run: + mrepo = git.Repo.clone_from(self.url, module_path, n=True) + #END handle dry-run + progress.update(END|CLONE, 0, 1, prefix+"Done cloning to %s" % module_path) - # 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: - print >> sys.stderr, "Warning: 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 ! - self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url) + 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: + print >> sys.stderr, "Warning: 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 ! + self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url) + #END handle dry_run #END handle initalization @@ -403,8 +419,12 @@ class Submodule(util.IndexObject, Iterable, Traversable): ############################ binsha = self.binsha hexsha = self.hexsha - is_detached = mrepo.head.is_detached - if to_latest_revision: + 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 not dry_run 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() @@ -421,29 +441,35 @@ class Submodule(util.IndexObject, Iterable, Traversable): # END handle to_latest_revision option # update the working tree - if mrepo.head.commit.binsha != binsha: - progress.update(BEGIN|UPDWKTREE, 0, 1, "Updating working tree at %s" % self.path) - 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=True) - else: - # TODO: allow to specify a rebase, merge, or reset - # TODO: Warn if the hexsha forces the tracking branch off the remote - # branch - this should be prevented when setting the branch option - mrepo.head.reset(hexsha, index=True, working_tree=True) - # END handle checkout - progress.update(END|UPDWKTREE, 0, 1, "Done updating working tree at %s" % self.path) + # handles dry_run + if mrepo is not None and mrepo.head.commit.binsha != binsha: + progress.update(BEGIN|UPDWKTREE, 0, 1, prefix+"Updating working tree at %s for submodule %r" % (self.path, self.name)) + if not dry_run: + 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=True) + else: + # TODO: allow to specify a rebase, merge, or reset + # TODO: Warn if the hexsha forces the tracking branch off the remote + # branch - this should be prevented when setting the branch option + mrepo.head.reset(hexsha, index=True, working_tree=True) + # END handle checkout + #END handle dry_run + progress.update(END|UPDWKTREE, 0, 1, prefix+"Done updating working tree for submodule %r" % self.name) # END update to new commit only if needed # HANDLE RECURSION ################## if recursive: - for submodule in self.iter_items(self.module()): - submodule.update(recursive, init, to_latest_revision, progress=progress) - # END handle recursive update + # in dry_run mode, the module might not exist + 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) + # END handle recursive update + #END handle dry run # END for each submodule return self @@ -843,8 +869,7 @@ class Submodule(util.IndexObject, Iterable, Traversable): def children(self): """ :return: IterableList(Submodule, ...) an iterable list of submodules instances - which are children of this submodule - :raise InvalidGitRepositoryError: if the submodule is not checked-out""" + which are children of this submodule or 0 if the submodule is not checked out""" return self._get_intermediate_items(self) #} END query interface diff --git a/objects/submodule/root.py b/objects/submodule/root.py index 49fb1417..36cd7209 100644 --- a/objects/submodule/root.py +++ b/objects/submodule/root.py @@ -7,7 +7,7 @@ import git import sys -__all__ = ["RootModule"] +__all__ = ["RootModule", "RootUpdateProgress"] class RootUpdateProgress(UpdateProgress): @@ -84,6 +84,10 @@ class RootModule(Submodule): progress = RootUpdateProgress() #END assure progress is set + prefix = '' + if dry_run: + prefix = 'DRY-RUN: ' + repo = self.repo # SETUP BASE COMMIT @@ -121,14 +125,16 @@ class RootModule(Submodule): # 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, "Removing submodule %s at %s" % (rsm.name, rsm.abspath)) + 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) + if not dry_run: + rsm.remove(configuration=False, module=True, force=force_remove) + #END handle dry-run if i == len_rrsm-1: op |= END #END handle end - progress.update(op, i, len_rrsm, "Done removing submodule %s" % rsm.name) + progress.update(op, i, len_rrsm, prefix+"Done removing submodule %r" % rsm.name) # END for each removed submodule # HANDLE PATH RENAMES @@ -143,18 +149,18 @@ class RootModule(Submodule): #PATH CHANGES ############## if sm.path != psm.path and psm.module_exists(): - progress.update(BEGIN|PATHCHANGE, i, len_csms, "Moving submodule's %s repository from %s to %s" % (sm.name, psm.abspath, sm.abspath)) + 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 - psm.move(sm.path, module=True, configuration=False) - progress.update(END|PATHCHANGE, i, len_csms, "Done moving repository of submodule %s" % sm.name) + 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: - progress.update(BEGIN|URLCHANGE, i, len_csms, "Changing url of submodule %s from %s to %s" % (sm.name, psm.url, sm.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 @@ -164,79 +170,81 @@ class RootModule(Submodule): # 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)) - 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 - 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 - - progress.update(END|URLCHANGE, i, len_csms, "Done adjusting url of submodule %s" % (sm.name)) + 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 + 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 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 @@ -245,34 +253,36 @@ class RootModule(Submodule): 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, "Changing branch of submodule %s from %s to %s" % (sm.name, psm.branch_path, sm.branch_path)) - 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 + 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, "Done changing branch of submodule %s" % sm.name) + 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 @@ -289,9 +299,12 @@ class RootModule(Submodule): # 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, - progress=progress, dry_run=dry_run) + # the module would exist by now if we are not in dry_run mode + 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) + #END handle dry_run #END handle recursive # END for each submodule to update diff --git a/test/test_submodule.py b/test/test_submodule.py index f69c27ea..b8a25e02 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -4,12 +4,20 @@ from git.test.lib import * from git.exc import * from git.objects.submodule.base import Submodule -from git.objects.submodule.root import RootModule +from git.objects.submodule.root import RootModule, RootUpdateProgress from git.util import to_native_path_linux, join_path_native import shutil import git import os +class TestRootProgress(RootUpdateProgress): + """Just prints messages, for now without checking the correctness of the states""" + + def update(self, op, index, max_count, message=''): + print message + +prog = TestRootProgress() + class TestSubmodule(TestBase): k_subm_current = "83a9e4a0dad595188ff3fb35bc3dfc4d931eff6d" @@ -130,6 +138,10 @@ class TestSubmodule(TestBase): self.failUnlessRaises(OSError, sm.update) os.rmdir(newdir) + # dry-run does nothing + sm.update(dry_run=True, progress=prog) + assert not sm.module_exists() + assert sm.update() is sm sm_repopath = sm.path # cache for later assert sm.module_exists() @@ -150,6 +162,11 @@ class TestSubmodule(TestBase): # delete the whole directory and re-initialize shutil.rmtree(sm.abspath) + assert len(sm.children()) == 0 + # dry-run does nothing + sm.update(dry_run=True, recursive=False, progress=prog) + assert len(sm.children()) == 0 + sm.update(recursive=False) assert len(list(rwrepo.iter_submodules())) == 2 assert len(sm.children()) == 1 # its not checked out yet @@ -162,8 +179,14 @@ class TestSubmodule(TestBase): csm.config_writer().set_value('url', new_csmclone_path) assert csm.url == new_csmclone_path + # dry-run does nothing + assert not csm.module_exists() + sm.update(recursive=True, dry_run=True, progress=prog) + assert not csm.module_exists() + # update recuesively again sm.update(recursive=True) + assert csm.module_exists() # tracking branch once again csm.module().head.ref.tracking_branch() is not None @@ -175,9 +198,15 @@ class TestSubmodule(TestBase): # reset both heads to the previous version, verify that to_latest_revision works smods = (sm.module(), csm.module()) for repo in smods: - repo.head.reset('HEAD~1', working_tree=1) + repo.head.reset('HEAD~2', working_tree=1) # END for each repo to reset + # dry run does nothing + sm.update(recursive=True, dry_run=True, progress=prog) + for repo in smods: + assert repo.head.commit != repo.head.ref.tracking_branch().commit + # END for each repo to check + sm.update(recursive=True, to_latest_revision=True) for repo in smods: assert repo.head.commit == repo.head.ref.tracking_branch().commit @@ -378,6 +407,11 @@ class TestSubmodule(TestBase): # assure we clone from a local source sm.config_writer().set_value('url', to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path))) + + # dry-run does nothing + sm.update(recursive=False, dry_run=True, progress=prog) + assert not sm.module_exists() + sm.update(recursive=False) assert sm.module_exists() sm.config_writer().set_value('path', fp) # change path to something with prefix AFTER url change @@ -397,7 +431,7 @@ class TestSubmodule(TestBase): cpathchange = rwrepo.index.commit("changed sm path") # finally we can commit # update puts the module into place - rm.update(recursive=False) + rm.update(recursive=False, progress=prog) sm.set_parent_commit(cpathchange) assert sm.module_exists() @@ -415,7 +449,12 @@ class TestSubmodule(TestBase): nsm.remove(configuration=False, module=True) assert not nsm.module_exists() and nsm.exists() - rm.update(recursive=False) + + # dry-run does nothing + rm.update(recursive=False, dry_run=True, progress=prog) + + # otherwise it will work + rm.update(recursive=False, progress=prog) assert nsm.module_exists() @@ -429,6 +468,10 @@ class TestSubmodule(TestBase): csmremoved = rwrepo.index.commit("Removed submodule") # an update will remove the module + # not in dry_run + rm.update(recursive=False, dry_run=True) + assert os.path.isdir(smp) + rm.update(recursive=False) assert not os.path.isdir(smp) @@ -444,7 +487,11 @@ class TestSubmodule(TestBase): nsm.set_parent_commit(csmpathchange) prev_commit = nsm.module().head.commit - rm.update(recursive=False) + # dry-run does nothing + rm.update(recursive=False, dry_run=True, progress=prog) + assert nsm.module().remotes.origin.url != nsmurl + + rm.update(recursive=False, progress=prog) assert nsm.module().remotes.origin.url == nsmurl # head changed, as the remote url and its commit changed assert prev_commit != nsm.module().head.commit @@ -473,7 +520,12 @@ class TestSubmodule(TestBase): assert nsmmh.ref.tracking_branch() is None # never set it up until now assert not nsmmh.is_detached - rm.update(recursive=False) + #dry run does nothing + rm.update(recursive=False, dry_run=True, progress=prog) + assert nsmmh.ref.tracking_branch() is None + + # the real thing does + rm.update(recursive=False, progress=prog) assert nsmmh.ref.tracking_branch() is not None assert not nsmmh.is_detached @@ -487,7 +539,8 @@ class TestSubmodule(TestBase): # assure we pull locally only nsmc = nsm.children()[0] nsmc.config_writer().set_value('url', async_url) - rm.update(recursive=True) + rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code + rm.update(recursive=True, progress=prog) - assert len(nsm.children()) == 1 and nsmc.module_exists() + assert len(nsm.children()) == 1 and nsmc.module_exists() |