def _do_test_fetch(self, remote, rw_repo, remote_repo, **kwargs):
        """Specialized fetch testing to de-clutter the main test."""
        self._do_test_fetch_info(rw_repo)

        def fetch_and_test(remote, **kwargs):
            progress = TestRemoteProgress()
            kwargs["progress"] = progress
            res = remote.fetch(**kwargs)
            progress.make_assertion()
            self._do_test_fetch_result(res, remote)
            return res

        # END fetch and check

        def get_info(res, remote, name):
            return res["%s/%s" % (remote, name)]

        # Put remote head to master as it is guaranteed to exist.
        remote_repo.head.reference = remote_repo.heads.master

        res = fetch_and_test(remote, **kwargs)
        # All up to date.
        for info in res:
            self.assertTrue(info.flags & info.HEAD_UPTODATE)

        # Rewind remote head to trigger rejection.
        # index must be false as remote is a bare repo.
        rhead = remote_repo.head
        remote_commit = rhead.commit
        rhead.reset("HEAD~2", index=False)
        res = fetch_and_test(remote)
        mkey = "%s/%s" % (remote, "master")
        master_info = res[mkey]
        self.assertTrue(master_info.flags & FetchInfo.FORCED_UPDATE)
        self.assertIsNotNone(master_info.note)

        # Normal fast forward - set head back to previous one.
        rhead.commit = remote_commit
        res = fetch_and_test(remote)
        self.assertTrue(res[mkey].flags & FetchInfo.FAST_FORWARD)

        # New remote branch.
        new_remote_branch = Head.create(remote_repo, "new_branch")
        res = fetch_and_test(remote)
        new_branch_info = get_info(res, remote, new_remote_branch)
        self.assertTrue(new_branch_info.flags & FetchInfo.NEW_HEAD)

        # Remote branch rename (causes creation of a new one locally).
        new_remote_branch.rename("other_branch_name")
        res = fetch_and_test(remote)
        other_branch_info = get_info(res, remote, new_remote_branch)
        self.assertEqual(other_branch_info.ref.commit, new_branch_info.ref.commit)

        # Remove new branch.
        Head.delete(new_remote_branch.repo, new_remote_branch)
        res = fetch_and_test(remote)
        # Deleted remote will not be fetched.
        self.assertRaises(IndexError, get_info, res, remote, new_remote_branch)

        # Prune stale tracking branches.
        stale_refs = remote.stale_refs
        self.assertEqual(len(stale_refs), 2)
        self.assertIsInstance(stale_refs[0], RemoteReference)
        RemoteReference.delete(rw_repo, *stale_refs)

        # Test single branch fetch with refspec including target remote.
        res = fetch_and_test(remote, refspec="master:refs/remotes/%s/master" % remote)
        self.assertEqual(len(res), 1)
        self.assertTrue(get_info(res, remote, "master"))

        # ...with respec and no target.
        res = fetch_and_test(remote, refspec="master")
        self.assertEqual(len(res), 1)

        # ...multiple refspecs...works, but git command returns with error if one ref is
        # wrong without doing anything. This is new in later binaries.
        # res = fetch_and_test(remote, refspec=['master', 'fred'])
        # self.assertEqual(len(res), 1)

        # Add new tag reference.
        rtag = TagReference.create(remote_repo, "1.0-RV_hello.there")
        res = fetch_and_test(remote, tags=True)
        tinfo = res[str(rtag)]
        self.assertIsInstance(tinfo.ref, TagReference)
        self.assertEqual(tinfo.ref.commit, rtag.commit)
        self.assertTrue(tinfo.flags & tinfo.NEW_TAG)

        # Adjust the local tag commit.
        Reference.set_object(rtag, rhead.commit.parents[0].parents[0])

        # As of git 2.20 one cannot clobber local tags that have changed without
        # specifying --force, and the test assumes you can clobber, so...
        force = None
        if rw_repo.git.version_info[:2] >= (2, 20):
            force = True
        res = fetch_and_test(remote, tags=True, force=force)
        tinfo = res[str(rtag)]
        self.assertEqual(tinfo.commit, rtag.commit)
        self.assertTrue(tinfo.flags & tinfo.TAG_UPDATE)

        # Delete remote tag - local one will stay.
        TagReference.delete(remote_repo, rtag)
        res = fetch_and_test(remote, tags=True)
        self.assertRaises(IndexError, get_info, res, remote, str(rtag))

        # Provoke to receive actual objects to see what kind of output we have to
        # expect. For that we need a remote transport protocol.
        # Create a new UN-shared repo and fetch into it after we pushed a change to the
        # shared repo.
        other_repo_dir = tempfile.mktemp("other_repo")
        # Must clone with a local path for the repo implementation not to freak out as
        # it wants local paths only (which I can understand).
        other_repo = remote_repo.clone(other_repo_dir, shared=False)
        remote_repo_url = osp.basename(remote_repo.git_dir)  # git-daemon runs with appropriate `--base-path`.
        remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, remote_repo_url))

        # Put origin to git-url.
        other_origin = other_repo.remotes.origin
        with other_origin.config_writer as cw:
            cw.set("url", remote_repo_url)
        # It automatically creates alternates as remote_repo is shared as well.
        # It will use the transport though and ignore alternates when fetching.
        # assert not other_repo.alternates  # this would fail

        # Ensure we are in the right state.
        rw_repo.head.reset(remote.refs.master, working_tree=True)
        try:
            self._commit_random_file(rw_repo)
            remote.push(rw_repo.head.reference)

            # Here I would expect to see remote-information about packing objects and so
            # on. Unfortunately, this does not happen if we are redirecting the output -
            # git explicitly checks for this and only provides progress information to
            # ttys.
            res = fetch_and_test(other_origin)
        finally:
            rmtree(other_repo_dir)
        # END test and cleanup
Specialized fetch testing to de-clutter the main test.
test/test_remote.py