Exemple #1
0
def hash_code(repo_path, catalogue_dir):
    """
    Get commit digest for current HEAD commit

    Returns the current HEAD commit digest for the code that is run.

    If the current working directory is dirty (or has untracked files other
    than those held in `catalogue_dir`), it raises a `RepositoryDirtyError`.

    Parameters
    ----------
    repo_path: str
        Path to analysis directory git repository.
    catalogue_dir: str
        Path to directory with catalogue output files.

    Returns
    -------
    str
        Git commit digest for the current HEAD commit of the git repository
    """

    try:
        repo = git.Repo(repo_path, search_parent_directories=True)
    except InvalidGitRepositoryError:
        raise InvalidGitRepositoryError(
            "provided code directory is not a valid git repository")

    untracked = prune_files(repo.untracked_files, catalogue_dir)
    if repo.is_dirty() or len(untracked) != 0:
        raise RepositoryDirtyError(
            repo, "git repository contains uncommitted changes")

    return repo.head.commit.hexsha
Exemple #2
0
 def __init__(self, repo: Repo = None):
     if not repo:
         try:
             self.repo = Repo(Path.cwd(), search_parent_directories=True)
         except InvalidGitRepositoryError:
             raise InvalidGitRepositoryError("Unable to find Repository from current working directory - aborting")
     else:
         self.repo = repo
 def module(self):
     if self._is_detached:
         raise TypeError("Raised intentionally to test behavior of "
                         "submodules with detached HEAD")
     elif not self._is_initialized:
         raise InvalidGitRepositoryError("Raised intentionally to test "
                                         "behavior of submodules that "
                                         "haven't been initialized")
     else:
         return MockRepo(self._full_path)
Exemple #4
0
    def __init__(self, path=os.getcwd()) -> None:
        try:
            self.repo = Repo(path, search_parent_directories=True)
            self.repo_path = os.path.dirname(self.repo.git_dir)
            self.branch = self.repo.active_branch
            self.display = Display()
            self.interact = Interact()

        except InvalidGitRepositoryError:
            raise InvalidGitRepositoryError(
                'Not a git repo, I have no power here...')
Exemple #5
0
    def get_git_repo(self, repo_url, local_repo):
        """ Get a git repository.

        Parameters
        ----------
        repo_url: str
            The location of the git repository (an url if local is False, a
            local path otherwise)
        local_repo: bool
            If True, get the repository from a local directory instead of the
            web

        Returns
        -------
        str
            The temporary path to which the repository has been copied
        GitRepo
            The repository object

        Raises
        ------
        FileNotFoundError
            If repo_url is not an existing directory
        git.InvalidGitRepositoryError
            If the directory in repo_url is not a git repository
        git.GitCommandError
            If the url in repo_url is not a git repository, or access to the
            repository is denied
        """
        project_path = tempfile.mkdtemp()
        if local_repo:
            project_path = os.path.join(tempfile.mkdtemp(), 'repo')
            try:
                shutil.copytree(repo_url, project_path)
                repo = GitRepo(project_path)
            except FileNotFoundError as e:
                shutil.rmtree(project_path)
                raise e
            except InvalidGitRepositoryError as e:
                shutil.rmtree(project_path)
                raise InvalidGitRepositoryError(
                    f"\"{repo_url}\" is not a local git repository.") from e
        else:
            try:
                GitRepo.clone_from(repo_url, project_path)
                repo = GitRepo(project_path)
            except GitCommandError as e:
                shutil.rmtree(project_path)
                raise e

        return project_path, repo
    def test_repo_error(self, mock_repo):
        # setup
        step_implementer = GitMixin()

        # set up mocks
        mock_get_value = MagicMock()
        step_implementer.get_value = mock_get_value

        def mock_get_value_side_effect(key):
            if 'git-repo-root' in key:
                return 'mock/bad/repo/path'

        mock_get_value.side_effect = mock_get_value_side_effect
        mock_repo.side_effect = InvalidGitRepositoryError('mock error')

        # run test
        with self.assertRaisesRegex(
                StepRunnerException,
                "Given git-repo-root \(mock/bad/repo/path\) is not a Git repository: mock error"
        ):
            step_implementer.git_repo

        # validate
        mock_repo.assert_called_once_with('mock/bad/repo/path')
Exemple #7
0
def git_query(repo_path, catalogue_dir, commit_changes=False):
    """
    Check status of a git repository

    Checks the git status of the repository on the provided path
    - if clean, returns true
    - if there are uncommitted changes (including untracked files other than
        those held in `catalogue_dir`), and the `commit_changes` flag is
        `True`, offer the user to stage and commit all tracked (and untracked)
        files and continue (returns `True`)
        otherwise, returns `False`

    If the `commit_changes` flag is `True` and the user accepts the offer
    to commit changes, a new branch is created with the name
    `"catalogue-%Y%m%d-%H%M%S"` and all tracked files that have been
    changed & any untracked files will be staged and committed.

    Parameters:
    ------------
    repo_path : str
        path to the code directory
    catalogue_dir : str
        path to directory with catalogue output files
    commit_changes : bool
        boolean indicating if the user should be prompted to stage and commit changes
        (optional, default is False)

    Returns:
    ---------
    Boolean indicating if git directory is clean
    """

    try:
        repo = git.Repo(repo_path, search_parent_directories=True)
    except InvalidGitRepositoryError:
        raise InvalidGitRepositoryError(
            "provided code directory is not a valid git repository")

    untracked = prune_files(repo.untracked_files, catalogue_dir)

    if repo.is_dirty() or (len(untracked) != 0):
        if commit_changes:
            print("Working directory contains uncommitted changes.")
            print("Do you want to stage and commit all changes? (y/[n])")
            user_choice = input().strip().lower()
            if user_choice == "y" or user_choice == "yes":
                timestamp = create_timestamp()
                try:
                    new_branch = repo.create_head("catalogue-" + timestamp)
                except BadName:
                    print(
                        "\nCannot create a branch for this commit as there are no existing commits.\nMake a commit manually, then run catalogue engage again.\n"
                    )
                    return False
                new_branch.checkout()
                changed_files = [item.a_path for item in repo.index.diff(None)]
                changed_files.extend(untracked)
                repo.index.add(changed_files)
                repo.index.commit("repro-catalogue tool auto commit at " +
                                  timestamp)
                return True
            elif user_choice == "n" or user_choice == "no" or user_choice == "":
                return False
            else:
                print("Unrecognized response, leaving repository unchaged")
                return False
        else:
            return False
    else:
        return True
Exemple #8
0
    def run(self, *args, **kwargs):
        self.validate_config()

        try:
            local_directory = args[0]
        except IndexError:
            local_directory = config['source']['local']

        try:
            repo_directory = args[1]
        except IndexError:
            repo_directory = config['source']['git']['local']

        branch = config['source']['git']['branch']
        repo = config['source']['git']['repo']

        env.host_string = 'localhost'
        pretty_print('[+] Repository check start: %s' % local_directory, 'info')

        # if not len(directory):
        #     pretty_print('Directory not selected, assuming current one.', 'info')
        #     directory = os.getcwd()

        # if os.path.isdir(directory):
        #     pretty_print('Directory found.', 'info')
        # else:
            # try:
            #     pretty_print('Directory not found, creating.', 'info')
            #     os.mkdir(directory)
            # except:
            #     raise Exception('Cannot create directory %s, please create the folder manually' % directory)

        old_dir = os.getcwd()
        os.chdir(local_directory)

        pretty_print("Checking if repository exists %s" % os.path.join(local_directory, repo_directory), "info")
        try:
            if not os.path.isdir(os.path.join(repo_directory, ".git")):  # repo = Repo(dir)
                if os.path.isdir(repo_directory):
                    raise IOError('Directory already exists and is not repository. Clone will fail. Please check your '
                                  'configuration')
                raise InvalidGitRepositoryError()

            repo = Repo(repo_directory)

            pretty_print('Repository found.', 'info')

        except InvalidGitRepositoryError:  # Repo doesn't exists
            pretty_print('Repository not found. Creating new one, using %s.' % repo, 'info')
            if len(repo) == 0:
                pretty_print('Repository not selected. Returning.', 'info')
                raise InvalidGitRepositoryError
            repo = Repo.clone_from(repo, repo_directory)

        if not len(branch):
            branch = repo.active_branch

        if repo.active_branch is not branch:
            pretty_print('Changing branch', 'info')
            repo.git.checkout(branch)

        pretty_print('Pulling changes', 'info')
        repo.git.pull('origin', branch)

        pretty_print('Fetching submodules', 'info')
        pretty_print('init')
        repo.git.submodule("init")
        pretty_print('update')
        try:
            repo.submodule_update(init=True, force_remove=True)
        except InvalidGitRepositoryError:
            pretty_print("Cannot update submodules, ommiting", 'info')

        os.chdir(old_dir)
        #repo.create_remote('origin', config.GIT_REPO)

        pretty_print('[+] Repository clone finished', 'info')
Exemple #9
0
    def test_get_git_commit_without_git(self, mock):
        mock.side_effect = InvalidGitRepositoryError()

        self.assertEqual(('unknown', '', ANY), get_git_commit())
def _single_repo_status(repo, verbose, follow_submodules):
    """
    :param repo: git.Repo.base.Repo
            a Repo object referencing a
            local repository
    :param verbose: int
            verbosity level
    :param follow_submodules: bool
            whether or not to include submodules
    :return: dict
            {field: info} pairs.  Fields (keys) are sufficient
            to create a "git-status"-like output for a
            repository, though many are set to None at lower
            `verbose` values
    """
    # TODO (future?): option to get info about branches other than current
    status = {
        # local branch compared to remote tracking branch
        'local_branch': None,
        'remote_branch': None,
        'n_commits_ahead': None,
        'n_commits_behind': None,
        # uncommitted local changes
        'n_staged': None,
        'files_staged': None,
        'n_not_staged': None,
        'files_not_staged': None,
        'n_untracked': None,
        'files_untracked': None,
        # alternate info for repos in a detached HEAD state
        'is_detached': False,
        'hexsha': None,
        'from_branch': None,
        'ref_sha': None,
        'detached_commits': None,
        # info for submodules (if any)
        'submodules': None
    }
    try:
        headcommit = repo.head.commit

    except ValueError as e:
        raise InvalidGitRepositoryError(
            "GitTracker currently doesn't support tracking newly "
            f"initialized repositories (can't track {repo.working_dir}"
        ) from e

    if repo.head.is_detached:
        # if HEAD is detached, report some slightly different information
        status['is_detached'] = True
        status['hexsha'] = headcommit.hexsha[:7]
        from_branch, ref_sha, n_new_commits = _detached_status(repo)
        status['from_branch'] = from_branch
        if ref_sha != status['hexsha']:
            # if commits have been made since detaching HEAD, report hash
            # where initially detached and number of new commits
            status['ref_sha'] = ref_sha
            status['detached_commits'] = n_new_commits

    else:
        local_branch = repo.active_branch
        local_branch_name = local_branch.name
        try:
            remote_branch_name = local_branch.tracking_branch().name
            n_ahead = len(list(repo.iter_commits(
                f"{remote_branch_name}..{local_branch_name}"
            )))
            n_behind = len(list(repo.iter_commits(
                f"{local_branch_name}..{remote_branch_name}"
            )))
        except AttributeError:
            # local branch isn't tracking a remote
            remote_branch_name = ''
            n_ahead = None
            n_behind = None

        status['local_branch'] = local_branch_name
        status['remote_branch'] = remote_branch_name
        status['n_commits_ahead'] = n_ahead
        status['n_commits_behind'] = n_behind

    staged = headcommit.diff()
    unstaged = repo.index.diff(None)
    untracked = repo.untracked_files
    status['n_staged'] = len(staged)
    status['n_not_staged'] = len(unstaged)
    status['n_untracked'] = len(untracked)

    # always computing individual file diff info would make Displayer
    # format methods and unit tests simpler, but skipping when
    # unnecessary saves noticeable time if dealing with many repositories
    if verbose == 3:
        # go through any staged changes & manually to handle renames
        files_staged = []
        for diff in staged:
            a_path = diff.a_path
            change_type = diff.change_type
            # new filepath only matters if file was renamed
            b_path = diff.b_path if change_type == 'R' else None
            files_staged.append((change_type, a_path, b_path))

        status['files_staged'] = files_staged
        status['files_untracked'] = untracked
        status['files_not_staged'] = [
            (diff.change_type, diff.a_path, None) for diff in unstaged
        ]

    if follow_submodules > 0 and any(repo.submodules):
        submodules = {}
        for sm in repo.submodules:
            sm_status = _submodule_status(sm, depth=follow_submodules)
            submodules[sm.path] = sm_status

        status['submodules'] = submodules

    return status
Exemple #11
0
def module_status(module, dirty_warning=False, notag_warning=False,
                  nogit_ok=False, nogit_warning=False):
    """Get status info (current hash, dirty/clean repo, tag) of module(s).

    Parameters
    ----------
    - module or list/iterable of modules (each must belong to a git repository)
    - dirty_warning: if True, prints a warning if some git repos are dirty.
    - notag_warning: if True, prints a warning if some git repos don't have tags
    - nogit_ok: if True, if some modules are not in a git repo, simply get
      their version number. If False (default), raise an error.
    - nogit_warning: if some modules are not in a git repo and nogit_ok is True,
      print a warning when this happens.

    Output
    ------
    Dict with module name as keys, and a dict {hash:, status:, tag:} as values
    """
    modules = _make_iterable(module)
    mods = {}  # dict {module name: dict of module info}

    for module in modules:

        name = module.__name__

        try:
            info = path_status(module.__file__)
        except InvalidGitRepositoryError:
            if nogit_ok:
                tag = 'v' + module.__version__
                info = {'status': 'not a git repository', 'tag': tag}
            else:
                raise InvalidGitRepositoryError(f'{module} not a git repo')
        mods[name] = info

    # Manage warnings if necessary -------------------------------------------

    if dirty_warning:

        dirty_modules = [module for module, info in mods.items()
                         if info['status'] == 'dirty']

        if len(dirty_modules) > 0:
            msg = '\nWarning: these modules have dirty git repositories: '
            msg += ', '.join(dirty_modules)
            print(msg)

    if notag_warning:

        tagless_modules = [module for module, info in mods.items()
                           if 'tag' not in info]

        if len(tagless_modules) > 0:
            msg = '\nWarning: these modules are missing a tag: '
            msg += ', '.join(tagless_modules)
            print(msg)

    if nogit_ok and nogit_warning:

        nogit_modules = [module for module, info in mods.items()
                         if info['status'] == 'not a git repository']

        if len(nogit_modules) > 0:
            msg = '\nWarning: these modules are not in a git repository: '
            msg += ', '.join(nogit_modules)
            print(msg)

    return mods
Exemple #12
0
    await repo.clone.__wrapped__(repo)

    kwargs = {"recursive": True, "depth": 1, "shallow-submodules": True}
    if branch:
        kwargs["branch"] = branch

    clone_from.assert_called_once_with(
        REPO_URL,
        str(tmp_path),
        **kwargs,
    )


@pytest.mark.parametrize(
    "git_error",
    [InvalidGitRepositoryError(),
     NoSuchPathError(),
     GitCommandError("clone")],
)
async def test_git_clone_error(coresys: CoreSys, tmp_path: Path,
                               clone_from: AsyncMock, git_error: GitError):
    """Test git clone error."""
    repo = GitRepo(coresys, tmp_path, REPO_URL)

    clone_from.side_effect = git_error
    with pytest.raises(StoreGitCloneError):
        await repo.clone.__wrapped__(repo)

    assert len(coresys.resolution.suggestions) == 0