Exemplo n.º 1
0
 def branch_name(self):
     """:return: the name of the branch, which is the shortest possible branch name"""
     # use an instance method, for this we create a temporary Head instance
     # which uses a repository that is available at least ( it makes no difference )
     return git.Head(self.repo, self._branch_path).name
Exemplo n.º 2
0
    def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
        """Add a new submodule to the given repository. This will alter the index
		as well as the .gitmodules file, but will not create a new commit.
		If the submodule already exists, no matter if the configuration differs
		from the one provided, the existing submodule will be returned.
		
		:param repo: Repository instance which should receive the submodule
		:param name: The name/identifier for the submodule
		:param path: repository-relative or absolute path at which the submodule 
			should be located
			It will be created as required during the repository initialization.
		:param url: git-clone compatible URL, see git-clone reference for more information
			If None, the repository is assumed to exist, and the url of the first
			remote is taken instead. This is useful if you want to make an existing
			repository a submodule of anotherone.
		:param branch: name of branch at which the submodule should (later) be checked out.
			The given branch must exist in the remote repository, and will be checked
			out locally as a tracking branch.
			It will only be written into the configuration if it not None, which is
			when the checked out branch will be the one the remote HEAD pointed to.
			The result you get in these situation is somewhat fuzzy, and it is recommended
			to specify at least 'master' here.
			Examples are 'master' or 'feature/new'
		:param no_checkout: if True, and if the repository has to be cloned manually, 
			no checkout will be performed
		:return: The newly created submodule instance
		:note: works atomically, such that no change will be done if the repository
			update fails for instance"""
        if repo.bare:
            raise InvalidGitRepositoryError(
                "Cannot add submodules to bare repositories")
        # END handle bare repos

        path = to_native_path_linux(path)
        if path.endswith('/'):
            path = path[:-1]
        # END handle trailing slash

        # assure we never put backslashes into the url, as some operating systems
        # like it ...
        if url != None:
            url = to_native_path_linux(url)
        #END assure url correctness

        # INSTANTIATE INTERMEDIATE SM
        sm = cls(repo, cls.NULL_BIN_SHA, cls.k_default_mode, path, name)
        if sm.exists():
            # reretrieve submodule from tree
            try:
                return repo.head.commit.tree[path]
            except KeyError:
                # could only be in index
                index = repo.index
                entry = index.entries[index.entry_key(path, 0)]
                sm.binsha = entry.binsha
                return sm
            # END handle exceptions
        # END handle existing

        # fake-repo - we only need the functionality on the branch instance
        br = git.Head(repo,
                      git.Head.to_full_path(str(branch) or cls.k_head_default))
        has_module = sm.module_exists()
        branch_is_default = branch is None
        if has_module and url is not None:
            if url not in [r.url for r in sm.module().remotes]:
                raise ValueError(
                    "Specified URL '%s' does not match any remote url of the repository at '%s'"
                    % (url, sm.abspath))
            # END check url
        # END verify urls match

        mrepo = None
        if url is None:
            if not has_module:
                raise ValueError(
                    "A URL was not given and existing repository did not exsit at %s"
                    % path)
            # END check url
            mrepo = sm.module()
            urls = [r.url for r in mrepo.remotes]
            if not urls:
                raise ValueError(
                    "Didn't find any remote url in repository at %s" %
                    sm.abspath)
            # END verify we have url
            url = urls[0]
        else:
            # clone new repo
            kwargs = {'n': no_checkout}
            if not branch_is_default:
                kwargs['b'] = br.name
            # END setup checkout-branch
            mrepo = git.Repo.clone_from(url, path, **kwargs)
        # END verify url

        # update configuration and index
        index = sm.repo.index
        writer = sm.config_writer(index=index, write=False)
        writer.set_value('url', url)
        writer.set_value('path', path)

        sm._url = url
        if not branch_is_default:
            # store full path
            writer.set_value(cls.k_head_option, br.path)
            sm._branch_path = br.path
        # END handle path
        del (writer)

        # we deliberatly assume that our head matches our index !
        pcommit = repo.head.commit
        sm._parent_commit = pcommit
        sm.binsha = mrepo.head.commit.binsha
        index.add([sm], write=True)

        return sm
Exemplo n.º 3
0
def mkhead(repo, path):
    """:return: New branch/head instance"""
    return git.Head(repo, git.Head.to_full_path(path))
Exemplo n.º 4
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)
Exemplo n.º 5
0
def mkhead(repo: 'Repo', path: PathLike) -> 'Head':
    """:return: New branch/head instance"""
    return git.Head(repo, git.Head.to_full_path(path))