Exemple #1
0
    def update(self,
               recursive=False,
               init=True,
               to_latest_revision=False,
               progress=None,
               dry_run=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
        :param to_latest_revision: if True, the submodule's sha will be ignored during checkout.
            Instead, the remote will be fetched, and the local tracking branch updated.
            This only works if we have a local tracking branch, which is the case
            if the remote repository had a master branch, or of the 'branch' option
            was specified for this submodule and the branch existed remotely
        :param progress: UpdateProgress instance or None of no progress should be shown
        :param dry_run: if True, the operation will only be simulated, but not performed.
            All performed operations are read-only
        :note: does nothing in bare repositories
        :note: method is definitely not atomic if recurisve is True
        :return: self"""
        if self.repo.bare:
            return self
        #END pass in bare mode

        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
        #####################################
        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
            import git

            # there is no git-repository yet - but delete empty paths
            module_path = join_path_native(self.repo.working_tree_dir,
                                           self.path)
            if not dry_run and os.path.isdir(module_path):
                try:
                    os.rmdir(module_path)
                except OSError:
                    raise OSError(
                        "Module directory at %r does already exist and is non-empty"
                        % module_path)
                # 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, 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)

            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

        # 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:
                    print >> sys.stderr, "%s a tracking branch was not set for local branch '%s'" % (
                        msg_base, mrepo.head.ref)
                # END handle remote ref
            else:
                print >> sys.stderr, "%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:
            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:
                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:
            # 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
Exemple #2
0
    def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
               to_latest_revision=False, progress=None, dry_run=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 HEAD@{1} otherwise
        :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
        :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."""
        if self.repo.bare:
            raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
        # END handle bare

        if progress is None:
            progress = RootUpdateProgress()
        # END assure progress is set

        prefix = ''
        if dry_run:
            prefix = 'DRY-RUN: '

        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
            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, 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
                                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

                # 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

        # 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)

            # 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:
                # 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)
Exemple #3
0
	def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, 
				dry_run=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
		:param to_latest_revision: if True, the submodule's sha will be ignored during checkout.
			Instead, the remote will be fetched, and the local tracking branch updated.
			This only works if we have a local tracking branch, which is the case
			if the remote repository had a master branch, or of the 'branch' option 
			was specified for this submodule and the branch existed remotely
		:param progress: UpdateProgress instance or None of no progress should be shown
		:param dry_run: if True, the operation will only be simulated, but not performed.
			All performed operations are read-only
		:note: does nothing in bare repositories
		:note: method is definitely not atomic if recurisve is True
		:return: self"""
		if self.repo.bare:
			return self
		#END pass in bare mode
		
		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
		#####################################
		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
			module_path = join_path_native(self.repo.working_tree_dir, self.path)
			if not dry_run and os.path.isdir(module_path):
				try:
					os.rmdir(module_path)
				except OSError:
					raise OSError("Module directory at %r does already exist and is non-empty" % module_path)
				# 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, module_path, self.name))
			if not dry_run:
				mrepo = type(self.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)
			
			
			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
		
		
		# 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:
					print >> sys.stderr, "%s a tracking branch was not set for local branch '%s'" % (msg_base, mrepo.head.ref) 
				# END handle remote ref
			else:
				print >> sys.stderr, "%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:
			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:
				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:
			# 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