Ejemplo n.º 1
0
 def wrapper(self, *args, **kwargs):
     if self.repo.bare:
         raise InvalidGitRepositoryError(
             "Method '%s' cannot operate on bare repositories" %
             func.__name__)
     #END bare method
     return func(self, *args, **kwargs)
Ejemplo n.º 2
0
 def module(self):
     """:return: Repo instance initialized from the repository at our submodule path
     :raise InvalidGitRepositoryError: if a repository was not available. This could
         also mean that it was not yet initialized"""
     # late import to workaround circular dependencies
     module_path = self.abspath
     try:
         repo = git.Repo(module_path)
         if repo != self.repo:
             return repo
         # END handle repo uninitialized
     except (InvalidGitRepositoryError, NoSuchPathError):
         raise InvalidGitRepositoryError("No valid repository at %s" %
                                         self.path)
     else:
         raise InvalidGitRepositoryError(
             "Repository at %r was not yet checked out" % module_path)
Ejemplo n.º 3
0
def find_first_remote_branch(remotes, branch_name):
	"""Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
	for remote in remotes:
		try:
			return remote.refs[branch_name]
		except IndexError:
			continue
		# END exception handling
	#END for remote
	raise InvalidGitRepositoryError("Didn't find remote branch %r in any of the given remotes", branch_name)
Ejemplo n.º 4
0
 def _to_relative_path(self, path):
     """:return: Version of path relative to our git directory or raise ValueError
     if it is not within our git direcotory"""
     if not os.path.isabs(path):
         return path
     if self.repo.bare:
         raise InvalidGitRepositoryError("require non-bare repository")
     relative_path = path.replace(self.repo.working_tree_dir + os.sep, "")
     if relative_path == path:
         raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
     return relative_path
 def common_dir(self) -> PathLike:
     """
     :return: The git dir that holds everything except possibly HEAD,
         FETCH_HEAD, ORIG_HEAD, COMMIT_EDITMSG, index, and logs/."""
     if self._common_dir:
         return self._common_dir
     elif self.git_dir:
         return self.git_dir
     else:
         # or could return ""
         raise InvalidGitRepositoryError()
Ejemplo n.º 6
0
	def module(self, repoType=None):
		""":return: Repository instance initialized from the repository at our submodule path
		:param repoType: The type of repository to be created. It must be possible to instatiate it
			from a single repository path.
			If None, a default repository type will be used
		:raise InvalidGitRepositoryError: if a repository was not available. This could 
			also mean that it was not yet initialized"""
		# late import to workaround circular dependencies
		module_path = self.abspath
		repoType = repoType or git.Repo
		
		try:
			repo = repoType(module_path)
			if repo != self.repo:
				return repo
			# END handle repo uninitialized
		except (InvalidGitRepositoryError, NoSuchPathError):
			raise InvalidGitRepositoryError("No valid repository at %s" % self.path)
		else:
			raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_path)
Ejemplo n.º 7
0
def find_first_remote_branch(remotes: Sequence['Remote'],
                             branch_name: str) -> 'RemoteReference':
    """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
    for remote in remotes:
        try:
            return remote.refs[branch_name]
        except IndexError:
            continue
        # END exception handling
    # END for remote
    raise InvalidGitRepositoryError(
        "Didn't find remote branch '%r' in any of the given remotes" %
        branch_name)
Ejemplo n.º 8
0
def load(repo_dir: str, repo_url: str) -> RepoUtil:
    """
    Load git repository from local directory into Python object.

    Arguments:
        :param repo_dir    (str) directory where .git file is located
        :param repo_url    (str) url to Github repository
    """
    repo = (RepoUtil if 'yangmodels/yang' in repo_dir else ModifiableRepoUtil)(
        repo_url, clone=False)
    try:
        repo.repo = Repo(repo_dir)
    except InvalidGitRepositoryError:
        raise InvalidGitRepositoryError(repo_dir)
    repo.local_dir = repo_dir
    return repo
Ejemplo n.º 9
0
    def __init__(self, remote_url: str, ssh_path: str = None, project_name: str = "",verbose=False) -> None:

        """Initializes Repository object

        The repository object holds information about the local directory as well as the remote of a repository.
        If

        Args:
            remote_url: URL of a remote repository i.e: ssh.github.om
            working_dir: local directory of the repository (working directory), i.e: ./repositories/

        Returns:

        """
        logger.remove()
        if verbose:
            logger.add(sys.stderr, format="{level: <8} | {message}", level="DEBUG")
        else:
            logger.add(sys.stderr, format="{level: <8} | {message}", level="INFO")
        # Check if complete URL is provided
        if not remote_url.endswith(".git"):
            raise InvalidGitRepositoryError("The URL doesn't seem valid. Please provide a valid url, e.g.: http://github.com/project/repo.git")

        self.repository_name = remote_url[(remote_url.rindex("/") + 1):remote_url.rindex(".git")]
        self.remote_url = remote_url
        self.path_prefix = project_name
        self.ssh_path = None
        self.progress_info = self.ProgressInfo()

        self.working_dir = os.path.join(get_project_root(), "temp", "repositories", self.path_prefix, self.repository_name)

        self.repository_git = git.Git(self.working_dir)
        if ssh_path is not None:
            self.ssh_path = str(ssh_path).replace("\\", "\\\\")
            self.repository_git.update_environment(GIT_SSH_COMMAND=f"ssh -i {self.ssh_path}")

        try:
            self.repository = git.Repo(self.working_dir)
        except InvalidGitRepositoryError:
            logger.info(
                f"{os.path.abspath(self.working_dir)} is not a valid git repository (no .git folder), clone {self.remote_url} into {os.path.abspath(self.working_dir)}")
            self.repository = self.__clone_from_remote()
        except NoSuchPathError:
            logger.info(
                f"{os.path.abspath(self.working_dir)} does not exist - trying to clone {self.remote_url} into {os.path.abspath(self.working_dir)}")
            self.repository = self.__clone_from_remote()
Ejemplo n.º 10
0
    def iter_items(cls, repo, parent_commit='HEAD'):
        """:return: iterator yielding Submodule instances available in the given repository"""
        pc = repo.commit(parent_commit)  # parent commit instance
        try:
            parser = cls._config_parser(repo, pc, read_only=True)
        except IOError:
            raise StopIteration
        # END handle empty iterator

        rt = pc.tree  # root tree

        for sms in parser.sections():
            n = sm_name(sms)
            p = parser.get_value(sms, 'path')
            u = parser.get_value(sms, 'url')
            b = cls.k_head_default
            if parser.has_option(sms, cls.k_head_option):
                b = str(parser.get_value(sms, cls.k_head_option))
            # END handle optional information

            # get the binsha
            index = repo.index
            try:
                sm = rt[p]
            except KeyError:
                # try the index, maybe it was just added
                try:
                    entry = index.entries[index.entry_key(p, 0)]
                    sm = Submodule(repo, entry.binsha, entry.mode, entry.path)
                except KeyError:
                    raise InvalidGitRepositoryError(
                        "Gitmodule path %r did not exist in revision of parent commit %s"
                        % (p, parent_commit))
                # END handle keyerror
            # END handle critical error

            # fill in remaining info - saves time as it doesn't have to be parsed again
            sm._name = n
            sm._parent_commit = pc
            sm._branch_path = git.Head.to_full_path(b)
            sm._url = u

            yield sm
Ejemplo n.º 11
0
    def _initialize(self, path):
        epath = abspath(expandvars(expanduser(path or os.getcwd())))

        if not exists(epath):
            raise NoSuchPathError(epath)
        #END check file

        self._working_tree_dir = None
        self._git_path = None
        curpath = epath

        # walk up the path to find the .git dir
        while curpath:
            if is_git_dir(curpath):
                self._git_path = curpath
                self._working_tree_dir = os.path.dirname(curpath)
                break
            gitpath = join(curpath, self.repo_dir)
            if is_git_dir(gitpath):
                self._git_path = gitpath
                self._working_tree_dir = curpath
                break
            curpath, dummy = os.path.split(curpath)
            if not dummy:
                break
        # END while curpath

        if self._git_path is None:
            raise InvalidGitRepositoryError(epath)
        # END path not found

        self._bare = self._working_tree_dir is None
        if hasattr(self, 'config_reader'):
            try:
                self._bare = self.config_reader("repository").getboolean(
                    'core', 'bare')
            except Exception:
                # lets not assume the option exists, although it should
                pass
            #END handle exception
        #END check bare flag
        self._working_tree_dir = self._bare and None or self._working_tree_dir
Ejemplo n.º 12
0
    def test_open_repository(self, repo_mock, print_error_mock, sys_exit_mock):
        config = {"main": {"repository": "./"}}
        repo_mock.side_effect = NoSuchPathError()
        _open_repository(config)
        print_error_mock.assert_called_once()
        sys_exit_mock.assert_called_with(ERROR_EXIT_CODE)

        repo_mock.side_effect = InvalidGitRepositoryError()
        print_error_mock.reset_mock()
        sys_exit_mock.reset_mock()
        _open_repository(config)
        print_error_mock.assert_called_once()
        sys_exit_mock.assert_called_with(ERROR_EXIT_CODE)

        repo_mock.side_effect = None
        print_error_mock.reset_mock()
        sys_exit_mock.reset_mock()
        _open_repository(config)
        repo_mock.assert_called_with(config["main"]["repository"])
        self.assertEqual(sys_exit_mock.call_count, 0)
        self.assertEqual(print_error_mock.call_count, 0)
Ejemplo n.º 13
0
    def __init__(self,
                 path=None,
                 odbt=DefaultDBType,
                 search_parent_directories=False,
                 expand_vars=True):
        """Create a new Repo instance

        :param path:
            the path to either the root git directory or the bare git repo::

                repo = Repo("/Users/mtrier/Development/git-python")
                repo = Repo("/Users/mtrier/Development/git-python.git")
                repo = Repo("~/Development/git-python.git")
                repo = Repo("$REPOSITORIES/Development/git-python.git")

            - In *Cygwin*, path may be a `'cygdrive/...'` prefixed path.
            - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also evals to false,
              the current-directory is used.
        :param odbt:
            Object DataBase type - a type which is constructed by providing
            the directory containing the database objects, i.e. .git/objects. It will
            be used to access all object data
        :param search_parent_directories:
            if True, all parent directories will be searched for a valid repo as well.

            Please note that this was the default behaviour in older versions of GitPython,
            which is considered a bug though.
        :raise InvalidGitRepositoryError:
        :raise NoSuchPathError:
        :return: git.Repo """

        epath = path or os.getenv('GIT_DIR')
        if not epath:
            epath = os.getcwd()
        if Git.is_cygwin():
            epath = decygpath(epath)

        epath = epath or path or os.getcwd()
        if expand_vars and ("%" in epath or "$" in epath):
            warnings.warn(
                "The use of environment variables in paths is deprecated" +
                "\nfor security reasons and may be removed in the future!!")
        epath = expand_path(epath, expand_vars)
        if not os.path.exists(epath):
            raise NoSuchPathError(epath)

        ## Walk up the path to find the `.git` dir.
        #
        curpath = epath
        while curpath:
            # ABOUT osp.NORMPATH
            # It's important to normalize the paths, as submodules will otherwise initialize their
            # repo instances with paths that depend on path-portions that will not exist after being
            # removed. It's just cleaner.
            if is_git_dir(curpath):
                self.git_dir = curpath
                self._working_tree_dir = os.getenv(
                    'GIT_WORK_TREE', os.path.dirname(self.git_dir))
                break

            dotgit = osp.join(curpath, '.git')
            sm_gitpath = find_submodule_git_dir(dotgit)
            if sm_gitpath is not None:
                self.git_dir = osp.normpath(sm_gitpath)

            sm_gitpath = find_submodule_git_dir(dotgit)
            if sm_gitpath is None:
                sm_gitpath = find_worktree_git_dir(dotgit)

            if sm_gitpath is not None:
                self.git_dir = expand_path(sm_gitpath, expand_vars)
                self._working_tree_dir = curpath
                break

            if not search_parent_directories:
                break
            curpath, tail = osp.split(curpath)
            if not tail:
                break
        # END while curpath

        if self.git_dir is None:
            raise InvalidGitRepositoryError(epath)

        self._bare = False
        try:
            self._bare = self.config_reader("repository").getboolean(
                'core', 'bare')
        except Exception:
            # lets not assume the option exists, although it should
            pass

        try:
            common_dir = open(osp.join(self.git_dir, 'commondir'),
                              'rt').readlines()[0].strip()
            self._common_dir = osp.join(self.git_dir, common_dir)
        except (OSError, IOError):
            self._common_dir = None

        # adjust the wd in case we are actually bare - we didn't know that
        # in the first place
        if self._bare:
            self._working_tree_dir = None
        # END working dir handling

        self.working_dir = self._working_tree_dir or self.common_dir
        self.git = self.GitCommandWrapperType(self.working_dir)

        # special handling, in special times
        args = [osp.join(self.common_dir, 'objects')]
        if issubclass(odbt, GitCmdObjectDB):
            args.append(self.git)
        self.odb = odbt(*args)
Ejemplo n.º 14
0
    def __init__(self,
                 path=None,
                 odbt=DefaultDBType,
                 search_parent_directories=False):
        """Create a new Repo instance

        :param path:
            the path to either the root git directory or the bare git repo::

                repo = Repo("/Users/mtrier/Development/git-python")
                repo = Repo("/Users/mtrier/Development/git-python.git")
                repo = Repo("~/Development/git-python.git")
                repo = Repo("$REPOSITORIES/Development/git-python.git")

        :param odbt:
            Object DataBase type - a type which is constructed by providing
            the directory containing the database objects, i.e. .git/objects. It will
            be used to access all object data
        :param search_parent_directories:
            if True, all parent directories will be searched for a valid repo as well.

            Please note that this was the default behaviour in older versions of GitPython,
            which is considered a bug though.
        :raise InvalidGitRepositoryError:
        :raise NoSuchPathError:
        :return: git.Repo """
        epath = _expand_path(path or os.getcwd())
        self.git = None  # should be set for __del__ not to fail in case we raise
        if not os.path.exists(epath):
            raise NoSuchPathError(epath)

        self.working_dir = None
        self._working_tree_dir = None
        self.git_dir = None
        curpath = epath

        # walk up the path to find the .git dir
        while curpath:
            # ABOUT os.path.NORMPATH
            # It's important to normalize the paths, as submodules will otherwise initialize their
            # repo instances with paths that depend on path-portions that will not exist after being
            # removed. It's just cleaner.
            if is_git_dir(curpath):
                self.git_dir = os.path.normpath(curpath)
                self._working_tree_dir = os.path.dirname(self.git_dir)
                break

            gitpath = find_git_dir(join(curpath, '.git'))
            if gitpath is not None:
                self.git_dir = os.path.normpath(gitpath)
                self._working_tree_dir = curpath
                break

            if not search_parent_directories:
                break
            curpath, dummy = os.path.split(curpath)
            if not dummy:
                break
        # END while curpath

        if self.git_dir is None:
            raise InvalidGitRepositoryError(epath)

        self._bare = False
        try:
            self._bare = self.config_reader("repository").getboolean(
                'core', 'bare')
        except Exception:
            # lets not assume the option exists, although it should
            pass

        # adjust the wd in case we are actually bare - we didn't know that
        # in the first place
        if self._bare:
            self._working_tree_dir = None
        # END working dir handling

        self.working_dir = self._working_tree_dir or self.git_dir
        self.git = self.GitCommandWrapperType(self.working_dir)

        # special handling, in special times
        args = [join(self.git_dir, 'objects')]
        if issubclass(odbt, GitCmdObjectDB):
            args.append(self.git)
        self.odb = odbt(*args)
Ejemplo n.º 15
0
    def __init__(self, path=None, odbt=DefaultDBType):
        """Create a new Repo instance

		:param path: is the path to either the root git directory or the bare git repo::

			repo = Repo("/Users/mtrier/Development/git-python")
			repo = Repo("/Users/mtrier/Development/git-python.git")
			repo = Repo("~/Development/git-python.git")
			repo = Repo("$REPOSITORIES/Development/git-python.git")
		
		:param odbt: Object DataBase type - a type which is constructed by providing 
			the directory containing the database objects, i.e. .git/objects. It will
			be used to access all object data
		:raise InvalidGitRepositoryError:
		:raise NoSuchPathError:
		:return: git.Repo """
        epath = os.path.abspath(
            os.path.expandvars(os.path.expanduser(path or os.getcwd())))

        if not os.path.exists(epath):
            raise NoSuchPathError(epath)

        self.working_dir = None
        self._working_tree_dir = None
        self.git_dir = None
        curpath = epath

        # walk up the path to find the .git dir
        while curpath:
            if is_git_dir(curpath):
                self.git_dir = curpath
                self._working_tree_dir = os.path.dirname(curpath)
                break
            gitpath = join(curpath, '.git')
            if is_git_dir(gitpath):
                self.git_dir = gitpath
                self._working_tree_dir = curpath
                break
            curpath, dummy = os.path.split(curpath)
            if not dummy:
                break
        # END while curpath

        if self.git_dir is None:
            raise InvalidGitRepositoryError(epath)

        self._bare = False
        try:
            self._bare = self.config_reader("repository").getboolean(
                'core', 'bare')
        except Exception:
            # lets not assume the option exists, although it should
            pass

        # adjust the wd in case we are actually bare - we didn't know that
        # in the first place
        if self._bare:
            self._working_tree_dir = None
        # END working dir handling

        self.working_dir = self._working_tree_dir or self.git_dir
        self.git = Git(self.working_dir)

        # special handling, in special times
        args = [join(self.git_dir, 'objects')]
        if issubclass(odbt, GitCmdObjectDB):
            args.append(self.git)
        self.odb = odbt(*args)
Ejemplo n.º 16
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)
    def __init__(self, path: Optional[PathLike] = None, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
                 search_parent_directories: bool = False, expand_vars: bool = True) -> None:
        """Create a new Repo instance

        :param path:
            the path to either the root git directory or the bare git repo::

                repo = Repo("/Users/mtrier/Development/git-python")
                repo = Repo("/Users/mtrier/Development/git-python.git")
                repo = Repo("~/Development/git-python.git")
                repo = Repo("$REPOSITORIES/Development/git-python.git")
                repo = Repo("C:\\Users\\mtrier\\Development\\git-python\\.git")

            - In *Cygwin*, path may be a `'cygdrive/...'` prefixed path.
            - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also evals to false,
              the current-directory is used.
        :param odbt:
            Object DataBase type - a type which is constructed by providing
            the directory containing the database objects, i.e. .git/objects. It will
            be used to access all object data
        :param search_parent_directories:
            if True, all parent directories will be searched for a valid repo as well.

            Please note that this was the default behaviour in older versions of GitPython,
            which is considered a bug though.
        :raise InvalidGitRepositoryError:
        :raise NoSuchPathError:
        :return: git.Repo """

        epath = path or os.getenv('GIT_DIR')
        if not epath:
            epath = os.getcwd()
        if Git.is_cygwin():
            epath = decygpath(epath)

        epath = epath or path or os.getcwd()
        if not isinstance(epath, str):
            epath = str(epath)
        if expand_vars and re.search(self.re_envvars, epath):
            warnings.warn("The use of environment variables in paths is deprecated" +
                          "\nfor security reasons and may be removed in the future!!")
        epath = expand_path(epath, expand_vars)
        if epath is not None:
            if not os.path.exists(epath):
                raise NoSuchPathError(epath)

        ## Walk up the path to find the `.git` dir.
        #
        curpath = epath
        while curpath:
            # ABOUT osp.NORMPATH
            # It's important to normalize the paths, as submodules will otherwise initialize their
            # repo instances with paths that depend on path-portions that will not exist after being
            # removed. It's just cleaner.
            if is_git_dir(curpath):
                self.git_dir = curpath
                # from man git-config : core.worktree
                # Set the path to the root of the working tree. If GIT_COMMON_DIR environment
                # variable is set, core.worktree is ignored and not used for determining the
                # root of working tree. This can be overridden by the GIT_WORK_TREE environment
                # variable. The value can be an absolute path or relative to the path to the .git
                # directory, which is either specified by GIT_DIR, or automatically discovered.
                # If GIT_DIR is specified but none of GIT_WORK_TREE and core.worktree is specified,
                # the current working directory is regarded as the top level of your working tree.
                self._working_tree_dir = os.path.dirname(self.git_dir)
                if os.environ.get('GIT_COMMON_DIR') is None:
                    gitconf = self.config_reader("repository")
                    if gitconf.has_option('core', 'worktree'):
                        self._working_tree_dir = gitconf.get('core', 'worktree')
                if 'GIT_WORK_TREE' in os.environ:
                    self._working_tree_dir = os.getenv('GIT_WORK_TREE')
                break

            dotgit = osp.join(curpath, '.git')
            sm_gitpath = find_submodule_git_dir(dotgit)
            if sm_gitpath is not None:
                self.git_dir = osp.normpath(sm_gitpath)

            sm_gitpath = find_submodule_git_dir(dotgit)
            if sm_gitpath is None:
                sm_gitpath = find_worktree_git_dir(dotgit)

            if sm_gitpath is not None:
                self.git_dir = expand_path(sm_gitpath, expand_vars)
                self._working_tree_dir = curpath
                break

            if not search_parent_directories:
                break
            curpath, tail = osp.split(curpath)
            if not tail:
                break
        # END while curpath

        if self.git_dir is None:
            self.git_dir = cast(PathLike, self.git_dir)
            raise InvalidGitRepositoryError(epath)

        self._bare = False
        try:
            self._bare = self.config_reader("repository").getboolean('core', 'bare')
        except Exception:
            # lets not assume the option exists, although it should
            pass

        try:
            common_dir = open(osp.join(self.git_dir, 'commondir'), 'rt').readlines()[0].strip()
            self._common_dir = osp.join(self.git_dir, common_dir)
        except OSError:
            self._common_dir = None

        # adjust the wd in case we are actually bare - we didn't know that
        # in the first place
        if self._bare:
            self._working_tree_dir = None
        # END working dir handling

        self.working_dir = self._working_tree_dir or self.common_dir  # type: Optional[PathLike]
        self.git = self.GitCommandWrapperType(self.working_dir)

        # special handling, in special times
        rootpath = osp.join(self.common_dir, 'objects')
        if issubclass(odbt, GitCmdObjectDB):
            self.odb = odbt(rootpath, self.git)
        else:
            self.odb = odbt(rootpath)
Ejemplo n.º 18
0
    def remove(self,
               module=True,
               force=False,
               configuration=True,
               dry_run=False):
        """Remove this submodule from the repository. This will remove our entry
        from the .gitmodules file and the entry in the .git/config file.

        :param module: If True, the module we point to will be deleted
            as well. If the module is currently on a commit which is not part
            of any branch in the remote, if the currently checked out branch
            working tree, or untracked files,
            is ahead of its tracking branch,  if you have modifications in the
            In case the removal of the repository fails for these reasons, the
            submodule status will not have been altered.
            If this submodule has child-modules on its own, these will be deleted
            prior to touching the own module.
        :param force: Enforces the deletion of the module even though it contains
            modifications. This basically enforces a brute-force file system based
            deletion.
        :param configuration: if True, the submodule is deleted from the configuration,
            otherwise it isn't. Although this should be enabled most of the times,
            this flag enables you to safely delete the repository of your submodule.
        :param dry_run: if True, we will not actually do anything, but throw the errors
            we would usually throw
        :return: self
        :note: doesn't work in bare repositories
        :raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
        :raise OSError: if directories or files could not be removed"""
        if not (module + configuration):
            raise ValueError(
                "Need to specify to delete at least the module, or the configuration"
            )
        # END handle params

        # DELETE MODULE REPOSITORY
        ##########################
        if module and self.module_exists():
            if force:
                # take the fast lane and just delete everything in our module path
                # TODO: If we run into permission problems, we have a highly inconsistent
                # state. Delete the .git folders last, start with the submodules first
                mp = self.abspath
                method = None
                if os.path.islink(mp):
                    method = os.remove
                elif os.path.isdir(mp):
                    method = rmtree
                elif os.path.exists(mp):
                    raise AssertionError(
                        "Cannot forcibly delete repository as it was neither a link, nor a directory"
                    )
                #END handle brutal deletion
                if not dry_run:
                    assert method
                    method(mp)
                #END apply deletion method
            else:
                # verify we may delete our module
                mod = self.module()
                if mod.is_dirty(untracked_files=True):
                    raise InvalidGitRepositoryError(
                        "Cannot delete module at %s with any modifications, unless force is specified"
                        % mod.working_tree_dir)
                # END check for dirt

                # figure out whether we have new commits compared to the remotes
                # NOTE: If the user pulled all the time, the remote heads might
                # not have been updated, so commits coming from the remote look
                # as if they come from us. But we stay strictly read-only and
                # don't fetch beforhand.
                for remote in mod.remotes:
                    num_branches_with_new_commits = 0
                    rrefs = remote.refs
                    for rref in rrefs:
                        num_branches_with_new_commits = len(
                            mod.git.cherry(rref)) != 0
                    # END for each remote ref
                    # not a single remote branch contained all our commits
                    if num_branches_with_new_commits == len(rrefs):
                        raise InvalidGitRepositoryError(
                            "Cannot delete module at %s as there are new commits"
                            % mod.working_tree_dir)
                    # END handle new commits
                    # have to manually delete references as python's scoping is
                    # not existing, they could keep handles open ( on windows this is a problem )
                    if len(rrefs):
                        del (rref)
                    #END handle remotes
                    del (rrefs)
                    del (remote)
                # END for each remote

                # gently remove all submodule repositories
                for sm in self.children():
                    sm.remove(module=True,
                              force=False,
                              configuration=False,
                              dry_run=dry_run)
                    del (sm)
                # END for each child-submodule

                # finally delete our own submodule
                if not dry_run:
                    wtd = mod.working_tree_dir
                    del (mod)  # release file-handles (windows)
                    rmtree(wtd)
                # END delete tree if possible
            # END handle force
        # END handle module deletion

        # DELETE CONFIGURATION
        ######################
        if configuration and not dry_run:
            # first the index-entry
            index = self.repo.index
            try:
                del (index.entries[index.entry_key(self.path, 0)])
            except KeyError:
                pass
            #END delete entry
            index.write()

            # now git config - need the config intact, otherwise we can't query
            # inforamtion anymore
            self.repo.config_writer().remove_section(sm_section(self.name))
            self.config_writer().remove_section()
        # END delete configuration

        # void our data not to delay invalid access
        self._clear_cache()

        return self
Ejemplo n.º 19
0
    def move(self, module_path, configuration=True, module=True):
        """Move the submodule to a another module path. This involves physically moving
        the repository at our current path, changing the configuration, as well as
        adjusting our index entry accordingly.

        :param module_path: the path to which to move our module, given as
            repository-relative path. Intermediate directories will be created
            accordingly. If the path already exists, it must be empty.
            Trailling (back)slashes are removed automatically
        :param configuration: if True, the configuration will be adjusted to let
            the submodule point to the given path.
        :param module: if True, the repository managed by this submodule
            will be moved, not the configuration. This will effectively
            leave your repository in an inconsistent state unless the configuration
            and index already point to the target location.
        :return: self
        :raise ValueError: if the module path existed and was not empty, or was a file
        :note: Currently the method is not atomic, and it could leave the repository
            in an inconsistent state if a sub-step fails for some reason
        """
        if module + configuration < 1:
            raise ValueError(
                "You must specify to move at least the module or the configuration of the submodule"
            )
        #END handle input

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

        # VERIFY DESTINATION
        if module_path == self.path:
            return self
        #END handle no change

        dest_path = join_path_native(self.repo.working_tree_dir, module_path)
        if os.path.isfile(dest_path):
            raise ValueError("Cannot move repository onto a file: %s" %
                             dest_path)
        # END handle target files

        index = self.repo.index
        tekey = index.entry_key(module_path, 0)
        # if the target item already exists, fail
        if configuration and tekey in index.entries:
            raise ValueError("Index entry for target path did alredy exist")
        #END handle index key already there

        # remove existing destination
        if module:
            if os.path.exists(dest_path):
                if len(os.listdir(dest_path)):
                    raise ValueError(
                        "Destination module directory was not empty")
                #END handle non-emptyness

                if os.path.islink(dest_path):
                    os.remove(dest_path)
                else:
                    os.rmdir(dest_path)
                #END handle link
            else:
                # recreate parent directories
                # NOTE: renames() does that now
                pass
            #END handle existance
        # END handle module

        # move the module into place if possible
        cur_path = self.abspath
        renamed_module = False
        if module and os.path.exists(cur_path):
            os.renames(cur_path, dest_path)
            renamed_module = True
        #END move physical module

        # rename the index entry - have to manipulate the index directly as
        # git-mv cannot be used on submodules ... yeah
        try:
            if configuration:
                try:
                    ekey = index.entry_key(self.path, 0)
                    entry = index.entries[ekey]
                    del (index.entries[ekey])
                    nentry = git.IndexEntry(entry[:3] + (module_path, ) +
                                            entry[4:])
                    index.entries[tekey] = nentry
                except KeyError:
                    raise InvalidGitRepositoryError(
                        "Submodule's entry at %r did not exist" % (self.path))
                #END handle submodule doesn't exist

                # update configuration
                writer = self.config_writer(index=index)  # auto-write
                writer.set_value('path', module_path)
                self.path = module_path
                del (writer)
            # END handle configuration flag
        except Exception:
            if renamed_module:
                os.renames(dest_path, cur_path)
            # END undo module renaming
            raise
        #END handle undo rename

        return self
Ejemplo n.º 20
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
Ejemplo n.º 21
0
 def test_git_repo_invalid(self):
     with patch("git.Repo") as mock:
         mock.side_effect = InvalidGitRepositoryError("Not a git repo")
         repo = GitRepo(".")
         self.assertFalse(repo.is_valid())