Esempio n. 1
0
    def test_checkout_with_create(self):
        repo_clone = self.get_clone_repo()

        new_branch = 'new_branch'
        assert repo_clone._current_branch() == 'master'
        assert set(repo_clone.branches) == set(('master',))
        repo_clone._checkout(new_branch, create=True)

        # Branches is a lazy property so we need to recrete the Repo object.
        repo_clone = GitRepository(repo_clone.path)
        assert set(repo_clone.branches) == set(('master', new_branch))
        assert repo_clone._current_branch() == new_branch
Esempio n. 2
0
class TestGitRepository:

    # pylint: disable=protected-access

    def __check_for_existing_repo(self):
        if os.path.exists(TEST_GIT_REPO_CLONE):
            self.fail('Cannot test git clone repo as location %s already '
                      'exists. You should manually remove it first.'
                      % TEST_GIT_REPO_CLONE)

    @pytest.fixture(autouse=True)
    def prepare(self, request, pylonsapp):
        self.repo = GitRepository(TEST_GIT_REPO, bare=True)

    def get_clone_repo(self):
        """
        Return a non bare clone of the base repo.
        """
        clone_path = next(REPO_PATH_GENERATOR)
        repo_clone = GitRepository(
            clone_path, create=True, src_url=self.repo.path, bare=False)

        return repo_clone

    def get_empty_repo(self, bare=False):
        """
        Return a non bare empty repo.
        """
        return GitRepository(next(REPO_PATH_GENERATOR), create=True, bare=bare)

    def test_wrong_repo_path(self):
        wrong_repo_path = '/tmp/errorrepo'
        with pytest.raises(RepositoryError):
            GitRepository(wrong_repo_path)

    def test_repo_clone(self):
        self.__check_for_existing_repo()
        repo = GitRepository(TEST_GIT_REPO)
        repo_clone = GitRepository(
            TEST_GIT_REPO_CLONE,
            src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
        assert len(repo.commit_ids) == len(repo_clone.commit_ids)
        # Checking hashes of commits should be enough
        for commit in repo.get_commits():
            raw_id = commit.raw_id
            assert raw_id == repo_clone.get_commit(raw_id).raw_id

    def test_repo_clone_without_create(self):
        with pytest.raises(RepositoryError):
            GitRepository(
                TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)

    def test_repo_clone_with_update(self):
        repo = GitRepository(TEST_GIT_REPO)
        clone_path = TEST_GIT_REPO_CLONE + '_with_update'
        repo_clone = GitRepository(
            clone_path,
            create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
        assert len(repo.commit_ids) == len(repo_clone.commit_ids)

        # check if current workdir was updated
        fpath = os.path.join(clone_path, 'MANIFEST.in')
        assert os.path.isfile(fpath)

    def test_repo_clone_without_update(self):
        repo = GitRepository(TEST_GIT_REPO)
        clone_path = TEST_GIT_REPO_CLONE + '_without_update'
        repo_clone = GitRepository(
            clone_path,
            create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
        assert len(repo.commit_ids) == len(repo_clone.commit_ids)
        # check if current workdir was *NOT* updated
        fpath = os.path.join(clone_path, 'MANIFEST.in')
        # Make sure it's not bare repo
        assert not repo_clone.bare
        assert not os.path.isfile(fpath)

    def test_repo_clone_into_bare_repo(self):
        repo = GitRepository(TEST_GIT_REPO)
        clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
        repo_clone = GitRepository(
            clone_path, create=True, src_url=repo.path, bare=True)
        assert repo_clone.bare

    def test_create_repo_is_not_bare_by_default(self):
        repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
        assert not repo.bare

    def test_create_bare_repo(self):
        repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
        assert repo.bare

    def test_update_server_info(self):
        self.repo._update_server_info()

    def test_fetch(self, vcsbackend_git):
        # Note: This is a git specific part of the API, it's only implemented
        # by the git backend.
        source_repo = vcsbackend_git.repo
        target_repo = vcsbackend_git.create_repo()
        target_repo.fetch(source_repo.path)
        # Note: Get a fresh instance, avoids caching trouble
        target_repo = vcsbackend_git.backend(target_repo.path)
        assert len(source_repo.commit_ids) == len(target_repo.commit_ids)

    def test_commit_ids(self):
        # there are 112 commits (by now)
        # so we can assume they would be available from now on
        subset = set([
            'c1214f7e79e02fc37156ff215cd71275450cffc3',
            '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
            'fa6600f6848800641328adbf7811fd2372c02ab2',
            '102607b09cdd60e2793929c4f90478be29f85a17',
            '49d3fd156b6f7db46313fac355dca1a0b94a0017',
            '2d1028c054665b962fa3d307adfc923ddd528038',
            'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
            'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
            'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
            '8430a588b43b5d6da365400117c89400326e7992',
            'd955cd312c17b02143c04fa1099a352b04368118',
            'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
            'add63e382e4aabc9e1afdc4bdc24506c269b7618',
            'f298fe1189f1b69779a4423f40b48edf92a703fc',
            'bd9b619eb41994cac43d67cf4ccc8399c1125808',
            '6e125e7c890379446e98980d8ed60fba87d0f6d1',
            'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
            '0b05e4ed56c802098dfc813cbe779b2f49e92500',
            '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
            '45223f8f114c64bf4d6f853e3c35a369a6305520',
            'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
            'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
            '27d48942240f5b91dfda77accd2caac94708cc7d',
            '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
            'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
        assert subset.issubset(set(self.repo.commit_ids))

    def test_slicing(self):
        # 4 1 5 10 95
        for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
                                 (10, 20, 10), (5, 100, 95)]:
            commit_ids = list(self.repo[sfrom:sto])
            assert len(commit_ids) == size
            assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom)
            assert commit_ids[-1] == self.repo.get_commit(commit_idx=sto - 1)

    def test_branches(self):
        # TODO: Need more tests here
        # Removed (those are 'remotes' branches for cloned repo)
        # assert 'master' in self.repo.branches
        # assert 'gittree' in self.repo.branches
        # assert 'web-branch' in self.repo.branches
        for __, commit_id in self.repo.branches.items():
            assert isinstance(self.repo.get_commit(commit_id), GitCommit)

    def test_tags(self):
        # TODO: Need more tests here
        assert 'v0.1.1' in self.repo.tags
        assert 'v0.1.2' in self.repo.tags
        for __, commit_id in self.repo.tags.items():
            assert isinstance(self.repo.get_commit(commit_id), GitCommit)

    def _test_single_commit_cache(self, commit_id):
        commit = self.repo.get_commit(commit_id)
        assert commit_id in self.repo.commits
        assert commit is self.repo.commits[commit_id]

    def test_initial_commit(self):
        commit_id = self.repo.commit_ids[0]
        init_commit = self.repo.get_commit(commit_id)
        init_author = init_commit.author

        assert init_commit.message == 'initial import\n'
        assert init_author == 'Marcin Kuzminski <*****@*****.**>'
        assert init_author == init_commit.committer
        for path in ('vcs/__init__.py',
                     'vcs/backends/BaseRepository.py',
                     'vcs/backends/__init__.py'):
            assert isinstance(init_commit.get_node(path), FileNode)
        for path in ('', 'vcs', 'vcs/backends'):
            assert isinstance(init_commit.get_node(path), DirNode)

        with pytest.raises(NodeDoesNotExistError):
            init_commit.get_node(path='foobar')

        node = init_commit.get_node('vcs/')
        assert hasattr(node, 'kind')
        assert node.kind == NodeKind.DIR

        node = init_commit.get_node('vcs')
        assert hasattr(node, 'kind')
        assert node.kind == NodeKind.DIR

        node = init_commit.get_node('vcs/__init__.py')
        assert hasattr(node, 'kind')
        assert node.kind == NodeKind.FILE

    def test_not_existing_commit(self):
        with pytest.raises(RepositoryError):
            self.repo.get_commit('f' * 40)

    def test_commit10(self):

        commit10 = self.repo.get_commit(self.repo.commit_ids[9])
        README = """===
VCS
===

Various Version Control System management abstraction layer for Python.

Introduction
------------

TODO: To be written...

"""
        node = commit10.get_node('README.rst')
        assert node.kind == NodeKind.FILE
        assert node.content == README

    def test_head(self):
        assert self.repo.head == self.repo.get_commit().raw_id

    def test_checkout_with_create(self):
        repo_clone = self.get_clone_repo()

        new_branch = 'new_branch'
        assert repo_clone._current_branch() == 'master'
        assert set(repo_clone.branches) == set(('master',))
        repo_clone._checkout(new_branch, create=True)

        # Branches is a lazy property so we need to recrete the Repo object.
        repo_clone = GitRepository(repo_clone.path)
        assert set(repo_clone.branches) == set(('master', new_branch))
        assert repo_clone._current_branch() == new_branch

    def test_checkout(self):
        repo_clone = self.get_clone_repo()

        repo_clone._checkout('new_branch', create=True)
        repo_clone._checkout('master')

        assert repo_clone._current_branch() == 'master'

    def test_checkout_same_branch(self):
        repo_clone = self.get_clone_repo()

        repo_clone._checkout('master')
        assert repo_clone._current_branch() == 'master'

    def test_checkout_branch_already_exists(self):
        repo_clone = self.get_clone_repo()

        with pytest.raises(RepositoryError):
            repo_clone._checkout('master', create=True)

    def test_checkout_bare_repo(self):
        with pytest.raises(RepositoryError):
            self.repo._checkout('master')

    def test_current_branch_bare_repo(self):
        with pytest.raises(RepositoryError):
            self.repo._current_branch()

    def test_current_branch_empty_repo(self):
        repo = self.get_empty_repo()
        assert repo._current_branch() is None

    def test_local_clone(self):
        clone_path = next(REPO_PATH_GENERATOR)
        self.repo._local_clone(clone_path, 'master')
        repo_clone = GitRepository(clone_path)

        assert self.repo.commit_ids == repo_clone.commit_ids

    def test_local_clone_with_specific_branch(self):
        source_repo = self.get_clone_repo()

        # Create a new branch in source repo
        new_branch_commit = source_repo.commit_ids[-3]
        source_repo._checkout(new_branch_commit)
        source_repo._checkout('new_branch', create=True)

        clone_path = next(REPO_PATH_GENERATOR)
        source_repo._local_clone(clone_path, 'new_branch')
        repo_clone = GitRepository(clone_path)

        assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids

        clone_path = next(REPO_PATH_GENERATOR)
        source_repo._local_clone(clone_path, 'master')
        repo_clone = GitRepository(clone_path)

        assert source_repo.commit_ids == repo_clone.commit_ids

    def test_local_clone_fails_if_target_exists(self):
        with pytest.raises(RepositoryError):
            self.repo._local_clone(self.repo.path, 'master')

    def test_local_fetch(self):
        target_repo = self.get_empty_repo()
        source_repo = self.get_clone_repo()

        # Create a new branch in source repo
        master_commit = source_repo.commit_ids[-1]
        new_branch_commit = source_repo.commit_ids[-3]
        source_repo._checkout(new_branch_commit)
        source_repo._checkout('new_branch', create=True)

        target_repo._local_fetch(source_repo.path, 'new_branch')
        assert target_repo._last_fetch_heads() == [new_branch_commit]

        target_repo._local_fetch(source_repo.path, 'master')
        assert target_repo._last_fetch_heads() == [master_commit]

    def test_local_fetch_from_bare_repo(self):
        target_repo = self.get_empty_repo()
        target_repo._local_fetch(self.repo.path, 'master')

        master_commit = self.repo.commit_ids[-1]
        assert target_repo._last_fetch_heads() == [master_commit]

    def test_local_fetch_from_same_repo(self):
        with pytest.raises(ValueError):
            self.repo._local_fetch(self.repo.path, 'master')

    def test_local_fetch_branch_does_not_exist(self):
        target_repo = self.get_empty_repo()

        with pytest.raises(RepositoryError):
            target_repo._local_fetch(self.repo.path, 'new_branch')

    def test_local_pull(self):
        target_repo = self.get_empty_repo()
        source_repo = self.get_clone_repo()

        # Create a new branch in source repo
        master_commit = source_repo.commit_ids[-1]
        new_branch_commit = source_repo.commit_ids[-3]
        source_repo._checkout(new_branch_commit)
        source_repo._checkout('new_branch', create=True)

        target_repo._local_pull(source_repo.path, 'new_branch')
        target_repo = GitRepository(target_repo.path)
        assert target_repo.head == new_branch_commit

        target_repo._local_pull(source_repo.path, 'master')
        target_repo = GitRepository(target_repo.path)
        assert target_repo.head == master_commit

    def test_local_pull_in_bare_repo(self):
        with pytest.raises(RepositoryError):
            self.repo._local_pull(self.repo.path, 'master')

    def test_local_merge(self):
        target_repo = self.get_empty_repo()
        source_repo = self.get_clone_repo()

        # Create a new branch in source repo
        master_commit = source_repo.commit_ids[-1]
        new_branch_commit = source_repo.commit_ids[-3]
        source_repo._checkout(new_branch_commit)
        source_repo._checkout('new_branch', create=True)

        # This is required as one cannot do a -ff-only merge in an empty repo.
        target_repo._local_pull(source_repo.path, 'new_branch')

        target_repo._local_fetch(source_repo.path, 'master')
        merge_message = 'Merge message\n\nDescription:...'
        user_name = 'Albert Einstein'
        user_email = '*****@*****.**'
        target_repo._local_merge(merge_message, user_name, user_email,
                                 target_repo._last_fetch_heads())

        target_repo = GitRepository(target_repo.path)
        assert target_repo.commit_ids[-2] == master_commit
        last_commit = target_repo.get_commit(target_repo.head)
        assert last_commit.message.strip() == merge_message
        assert last_commit.author == '%s <%s>' % (user_name, user_email)

        assert not os.path.exists(
            os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))

    def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git):
        target_repo = vcsbackend_git.create_repo(number_of_commits=1)
        vcsbackend_git.ensure_file('README', 'I will conflict with you!!!')

        target_repo._local_fetch(self.repo.path, 'master')
        with pytest.raises(RepositoryError):
            target_repo._local_merge(
                'merge_message', 'user name', '*****@*****.**',
                target_repo._last_fetch_heads())

        # Check we are not left in an intermediate merge state
        assert not os.path.exists(
            os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))

    def test_local_merge_into_empty_repo(self):
        target_repo = self.get_empty_repo()

        # This is required as one cannot do a -ff-only merge in an empty repo.
        target_repo._local_fetch(self.repo.path, 'master')
        with pytest.raises(RepositoryError):
            target_repo._local_merge(
                'merge_message', 'user name', '*****@*****.**',
                target_repo._last_fetch_heads())

    def test_local_merge_in_bare_repo(self):
        with pytest.raises(RepositoryError):
            self.repo._local_merge(
                'merge_message', 'user name', '*****@*****.**', None)

    def test_local_push_non_bare(self):
        target_repo = self.get_empty_repo()

        pushed_branch = 'pushed_branch'
        self.repo._local_push('master', target_repo.path, pushed_branch)
        # Fix the HEAD of the target repo, or otherwise GitRepository won't
        # report any branches.
        with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
            f.write('ref: refs/heads/%s' % pushed_branch)

        target_repo = GitRepository(target_repo.path)

        assert (target_repo.branches[pushed_branch] ==
                self.repo.branches['master'])

    def test_local_push_bare(self):
        target_repo = self.get_empty_repo(bare=True)

        pushed_branch = 'pushed_branch'
        self.repo._local_push('master', target_repo.path, pushed_branch)
        # Fix the HEAD of the target repo, or otherwise GitRepository won't
        # report any branches.
        with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f:
            f.write('ref: refs/heads/%s' % pushed_branch)

        target_repo = GitRepository(target_repo.path)

        assert (target_repo.branches[pushed_branch] ==
                self.repo.branches['master'])

    def test_local_push_non_bare_target_branch_is_checked_out(self):
        target_repo = self.get_clone_repo()

        pushed_branch = 'pushed_branch'
        # Create a new branch in source repo
        new_branch_commit = target_repo.commit_ids[-3]
        target_repo._checkout(new_branch_commit)
        target_repo._checkout(pushed_branch, create=True)

        self.repo._local_push('master', target_repo.path, pushed_branch)

        target_repo = GitRepository(target_repo.path)

        assert (target_repo.branches[pushed_branch] ==
                self.repo.branches['master'])

    def test_local_push_raises_exception_on_conflict(self, vcsbackend_git):
        target_repo = vcsbackend_git.create_repo(number_of_commits=1)
        with pytest.raises(RepositoryError):
            self.repo._local_push('master', target_repo.path, 'master')

    def test_hooks_can_be_enabled_via_env_variable_for_local_push(self):
        target_repo = self.get_empty_repo(bare=True)

        with mock.patch.object(self.repo, 'run_git_command') as run_mock:
            self.repo._local_push(
                'master', target_repo.path, 'master', enable_hooks=True)
        env = run_mock.call_args[1]['extra_env']
        assert 'RC_SKIP_HOOKS' not in env

    def _add_failing_hook(self, repo_path, hook_name, bare=False):
        path_components = (
            ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name])
        hook_path = os.path.join(repo_path, *path_components)
        with open(hook_path, 'w') as f:
            script_lines = [
                '#!%s' % sys.executable,
                'import os',
                'import sys',
                'if os.environ.get("RC_SKIP_HOOKS"):',
                '    sys.exit(0)',
                'sys.exit(1)',
            ]
            f.write('\n'.join(script_lines))
        os.chmod(hook_path, 0755)

    def test_local_push_does_not_execute_hook(self):
        target_repo = self.get_empty_repo()

        pushed_branch = 'pushed_branch'
        self._add_failing_hook(target_repo.path, 'pre-receive')
        self.repo._local_push('master', target_repo.path, pushed_branch)
        # Fix the HEAD of the target repo, or otherwise GitRepository won't
        # report any branches.
        with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
            f.write('ref: refs/heads/%s' % pushed_branch)

        target_repo = GitRepository(target_repo.path)

        assert (target_repo.branches[pushed_branch] ==
                self.repo.branches['master'])

    def test_local_push_executes_hook(self):
        target_repo = self.get_empty_repo(bare=True)
        self._add_failing_hook(target_repo.path, 'pre-receive', bare=True)
        with pytest.raises(RepositoryError):
            self.repo._local_push(
                'master', target_repo.path, 'master', enable_hooks=True)

    def test_maybe_prepare_merge_workspace(self):
        workspace = self.repo._maybe_prepare_merge_workspace(
            'pr2', Reference('branch', 'master', 'unused'))

        assert os.path.isdir(workspace)
        workspace_repo = GitRepository(workspace)
        assert workspace_repo.branches == self.repo.branches

        # Calling it a second time should also succeed
        workspace = self.repo._maybe_prepare_merge_workspace(
            'pr2', Reference('branch', 'master', 'unused'))
        assert os.path.isdir(workspace)

    def test_cleanup_merge_workspace(self):
        workspace = self.repo._maybe_prepare_merge_workspace(
            'pr3', Reference('branch', 'master', 'unused'))
        self.repo.cleanup_merge_workspace('pr3')

        assert not os.path.exists(workspace)

    def test_cleanup_merge_workspace_invalid_workspace_id(self):
        # No assert: because in case of an inexistent workspace this function
        # should still succeed.
        self.repo.cleanup_merge_workspace('pr4')

    def test_set_refs(self):
        test_ref = 'refs/test-refs/abcde'
        test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'

        self.repo.set_refs(test_ref, test_commit_id)
        stdout, _ = self.repo.run_git_command(['show-ref'])
        assert test_ref in stdout
        assert test_commit_id in stdout

    def test_remove_ref(self):
        test_ref = 'refs/test-refs/abcde'
        test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
        self.repo.set_refs(test_ref, test_commit_id)
        stdout, _ = self.repo.run_git_command(['show-ref'])
        assert test_ref in stdout
        assert test_commit_id in stdout

        self.repo.remove_ref(test_ref)
        stdout, _ = self.repo.run_git_command(['show-ref'])
        assert test_ref not in stdout
        assert test_commit_id not in stdout