def clean_up(self, clean_up_remote=False):
        tags = [tag for tag in self.local_repo.tags if tag.name.startswith(self.MIGRATION_TAG_PREFIX)]

        # delete the local tags
        TagReference.delete(self.local_repo, *tags)

        # delete the remote tags
        if clean_up_remote:
            tags_to_delete = [":" + tag_name for tag_name in self.remote_migration_tags]
            self.remote_repo.push(refspec=tags_to_delete)
Ejemplo n.º 2
0
    def delete_last_tag(self):
        """Delete the last commit tag.

        """
        entry = self.last_tag_entry
        tag = entry['tag']
        name = entry['name']
        logger.info('deleting: {}'.format(name))
        if not self.dry_run:
            TagReference.delete(self.repo, tag)
Ejemplo n.º 3
0
    def recreate_last_tag(self):
        """Delete the last tag and create a new one on the latest commit.

        """
        entry = self.last_tag_entry
        tag = entry['tag']
        name = entry['name']
        msg = entry['message']
        logger.info('deleting: {}'.format(name))
        if not self.dry_run:
            TagReference.delete(self.repo, tag)
        logger.info('creating {} with commit <{}>'.format(name, msg))
        if not self.dry_run:
            TagReference.create(self.repo, name, message=msg)
Ejemplo n.º 4
0
    def create(self):
        """Create a new tag on the latest commit.

        """
        entry = self.last_tag_entry
        if entry is None:
            ver = Version.from_string('v0.0.0')
        else:
            ver = entry['ver']
        ver.increment('debug')
        new_tag_name = str(ver)
        logger.info('creating {} with commit <{}>'.format(
            new_tag_name, self.message))
        if not self.dry_run:
            TagReference.create(self.repo, new_tag_name, message=self.message)
Ejemplo n.º 5
0
    def _setup_git_mocks(self):
        self.git_repo = mock.patch("git.Repo")
        self.addCleanup(self.git_repo.stop)

        self.mock_git_repo = self.git_repo.start()
        repo_instance = self.mock_git_repo.return_value

        repo_instance.git.pull = mock.MagicMock(
            "git.cmd.Git", return_value="Already up to date.")
        repo_instance.git.fetch = mock.MagicMock("git.cmd.Git")
        repo_instance.git.add = mock.MagicMock("git.cmd.Git")
        repo_instance.git.checkout = mock.MagicMock(
            "git.cmd.Git", "Already on \"master\"\n"
            "Your branch is up to date with \"origin/master\".")
        repo_instance.git.commit = mock.MagicMock("git.cmd.Git")
        repo_instance.git.commit = mock.MagicMock("git.cmd.Git")
        repo_instance.remotes["origin"].push = mock.MagicMock("git.cmd.Git")

        repo_instance.create_tag = mock.MagicMock(
            return_value=TagReference(repo_instance, "refs/tags/1.2.3"))
        tag1 = mock.MagicMock("git.tag.TagReference")
        tag1.tag = mock.MagicMock("git.tag.TagObject")
        tag1.tag.tag = "1.2.0"
        tag2 = mock.MagicMock("git.tag.TagReference")
        tag2.tag = mock.MagicMock("git.tag.TagObject")
        tag2.tag.tag = "1.2.1"
        tag3 = mock.MagicMock("git.tag.TagReference")
        tag3.tag = mock.MagicMock("git.tag.TagObject")
        tag3.tag.tag = "1.2.2"
        repo_instance.tags = [tag1, tag2, tag3]
Ejemplo n.º 6
0
 def __tag_repo(self, data, repository):
     """
     Tag the current repository.
     :param data: a dictionary containing the data about the experiment
     :type data: dict
     """
     assert self.__tag_name not in [t.name for t in repository.tags]
     return TagReference.create(repository, self.__tag_name, message=json.dumps(data))
Ejemplo n.º 7
0
 def record_results(self, results):
     """
     Record the results of this experiment, by updating the tag.
     :param results: A dictionary containing the results of the experiment.
     :type results: dict
     """
     repository = Repo(self.__repository_directory, search_parent_directories=True)
     for tag in repository.tags:
         if tag.name == self.__tag_name:
             tag_object = tag
             break
     else:
         raise Exception("Experiment tag has been deleted since experiment started")
     data = json.loads(tag_object.tag.message)
     data["results"] = results
     TagReference.create(repository, self.__tag_name, message=json.dumps(data),
                         ref=tag_object.tag.object, force=True)
     self.__results_recorded = True
Ejemplo n.º 8
0
 def __tag_repo(self, data, repository):
     """
     Tag the current repository.
     :param data: a dictionary containing the data about the experiment
     :type data: dict
     """
     assert self.__tag_name not in [t.name for t in repository.tags]
     return TagReference.create(repository,
                                self.__tag_name,
                                message=json.dumps(data))
Ejemplo n.º 9
0
 def record_results(self, results):
     """
     Record the results of this experiment, by updating the tag.
     :param results: A dictionary containing the results of the experiment.
     :type results: dict
     """
     repository = Repo(self.__repository_directory,
                       search_parent_directories=True)
     for tag in repository.tags:
         if tag.name == self.__tag_name:
             tag_object = tag
             break
     else:
         raise Exception(
             "Experiment tag has been deleted since experiment started")
     data = json.loads(tag_object.tag.message)
     data["results"] = results
     TagReference.create(repository,
                         self.__tag_name,
                         message=json.dumps(data),
                         ref=tag_object.tag.object,
                         force=True)
     self.__results_recorded = True
Ejemplo n.º 10
0
    def test_from_path(self):
        # should be able to create any reference directly
        for ref_type in (Reference, Head, TagReference, RemoteReference):
            for name in ('rela_name', 'path/rela_name'):
                full_path = ref_type.to_full_path(name)
                instance = ref_type.from_path(self.rorepo, full_path)
                assert isinstance(instance, ref_type)
            # END for each name
        # END for each type

        # invalid path
        self.failUnlessRaises(ValueError, TagReference, self.rorepo, "refs/invalid/tag")
        # works without path check
        TagReference(self.rorepo, "refs/invalid/tag", check_path=False)
Ejemplo n.º 11
0
    def _assert_push_and_pull(self, remote, rw_repo, remote_repo):
        # push our changes
        lhead = rw_repo.head
        # assure we are on master and it is checked out where the remote is
        try:
            lhead.reference = rw_repo.heads.master
        except AttributeError:
            # if the author is on a non-master branch, the clones might not have
            # a local master yet. We simply create it
            lhead.reference = rw_repo.create_head('master')
        # END master handling
        lhead.reset(remote.refs.master, working_tree=True)

        # push without spec should fail ( without further configuration )
        # well, works nicely
        # self.failUnlessRaises(GitCommandError, remote.push)

        # simple file push
        self._commit_random_file(rw_repo)
        progress = TestRemoteProgress()
        res = remote.push(lhead.reference, progress)
        assert isinstance(res, IterableList)
        self._do_test_push_result(res, remote)
        progress.make_assertion()

        # rejected - undo last commit
        lhead.reset("HEAD~1")
        res = remote.push(lhead.reference)
        assert res[0].flags & PushInfo.ERROR
        assert res[0].flags & PushInfo.REJECTED
        self._do_test_push_result(res, remote)

        # force rejected pull
        res = remote.push('+%s' % lhead.reference)
        assert res[0].flags & PushInfo.ERROR == 0
        assert res[0].flags & PushInfo.FORCED_UPDATE
        self._do_test_push_result(res, remote)

        # invalid refspec
        self.failUnlessRaises(GitCommandError, remote.push, "hellothere")

        # push new tags
        progress = TestRemoteProgress()
        to_be_updated = "my_tag.1.0RV"
        new_tag = TagReference.create(rw_repo, to_be_updated)
        other_tag = TagReference.create(rw_repo,
                                        "my_obj_tag.2.1aRV",
                                        message="my message")
        res = remote.push(progress=progress, tags=True)
        assert res[-1].flags & PushInfo.NEW_TAG
        progress.make_assertion()
        self._do_test_push_result(res, remote)

        # update push new tags
        # Rejection is default
        new_tag = TagReference.create(rw_repo,
                                      to_be_updated,
                                      ref='HEAD~1',
                                      force=True)
        res = remote.push(tags=True)
        self._do_test_push_result(res, remote)
        assert res[-1].flags & PushInfo.REJECTED and res[
            -1].flags & PushInfo.ERROR

        # push force this tag
        res = remote.push("+%s" % new_tag.path)
        assert res[-1].flags & PushInfo.ERROR == 0 and res[
            -1].flags & PushInfo.FORCED_UPDATE

        # delete tag - have to do it using refspec
        res = remote.push(":%s" % new_tag.path)
        self._do_test_push_result(res, remote)
        assert res[0].flags & PushInfo.DELETED
        # Currently progress is not properly transferred, especially not using
        # the git daemon
        # progress.assert_received_message()

        # push new branch
        new_head = Head.create(rw_repo, "my_new_branch")
        progress = TestRemoteProgress()
        res = remote.push(new_head, progress)
        assert len(res) > 0
        assert res[0].flags & PushInfo.NEW_HEAD
        progress.make_assertion()
        self._do_test_push_result(res, remote)

        # delete new branch on the remote end and locally
        res = remote.push(":%s" % new_head.path)
        self._do_test_push_result(res, remote)
        Head.delete(rw_repo, new_head)
        assert res[-1].flags & PushInfo.DELETED

        # --all
        res = remote.push(all=True)
        self._do_test_push_result(res, remote)

        remote.pull('master')

        # cleanup - delete created tags and branches as we are in an innerloop on
        # the same repository
        TagReference.delete(rw_repo, new_tag, other_tag)
        remote.push(":%s" % other_tag.path)
Ejemplo n.º 12
0
    def _do_test_fetch(self, remote, rw_repo, remote_repo):
        # 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)
        # all up to date
        for info in res:
            assert 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]
        assert master_info.flags & FetchInfo.FORCED_UPDATE and master_info.note is not None

        # normal fast forward - set head back to previous one
        rhead.commit = remote_commit
        res = fetch_and_test(remote)
        assert 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)
        assert 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)
        assert 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.failUnlessRaises(IndexError, get_info, res, remote,
                              new_remote_branch)

        # prune stale tracking branches
        stale_refs = remote.stale_refs
        assert len(stale_refs) == 2 and isinstance(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)
        assert len(res) == 1 and get_info(res, remote, 'master')

        # ... with respec and no target
        res = fetch_and_test(remote, refspec='master')
        assert 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'])
        # assert 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)]
        assert isinstance(tinfo.ref,
                          TagReference) and tinfo.ref.commit == rtag.commit
        assert tinfo.flags & tinfo.NEW_TAG

        # adjust tag commit
        Reference.set_object(rtag, rhead.commit.parents[0].parents[0])
        res = fetch_and_test(remote, tags=True)
        tinfo = res[str(rtag)]
        assert tinfo.commit == rtag.commit
        assert 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.failUnlessRaises(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 = "git://localhost:%s%s" % (GIT_DAEMON_PORT,
                                                    remote_repo.git_dir)

        # put origin to git-url
        other_origin = other_repo.remotes.origin
        other_origin.config_writer.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

        # assure 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:
            shutil.rmtree(other_repo_dir)
Ejemplo n.º 13
0
    def _assert_push_and_pull(self, remote, rw_repo, remote_repo):
        # push our changes
        lhead = rw_repo.head
        # assure we are on master and it is checked out where the remote is
        try:
            lhead.reference = rw_repo.heads.master
        except AttributeError:
            # if the author is on a non-master branch, the clones might not have
            # a local master yet. We simply create it
            lhead.reference = rw_repo.create_head('master')
        # END master handling
        lhead.reset(remote.refs.master, working_tree=True)

        # push without spec should fail ( without further configuration )
        # well, works nicely
        # self.failUnlessRaises(GitCommandError, remote.push)

        # simple file push
        self._commit_random_file(rw_repo)
        progress = TestRemoteProgress()
        res = remote.push(lhead.reference, progress)
        assert isinstance(res, IterableList)
        self._do_test_push_result(res, remote)
        progress.make_assertion()

        # rejected - undo last commit
        lhead.reset("HEAD~1")
        res = remote.push(lhead.reference)
        assert res[0].flags & PushInfo.ERROR
        assert res[0].flags & PushInfo.REJECTED
        self._do_test_push_result(res, remote)

        # force rejected pull
        res = remote.push('+%s' % lhead.reference)
        assert res[0].flags & PushInfo.ERROR == 0
        assert res[0].flags & PushInfo.FORCED_UPDATE
        self._do_test_push_result(res, remote)

        # invalid refspec
        self.failUnlessRaises(GitCommandError, remote.push, "hellothere")

        # push new tags
        progress = TestRemoteProgress()
        to_be_updated = "my_tag.1.0RV"
        new_tag = TagReference.create(rw_repo, to_be_updated)
        other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", message="my message")
        res = remote.push(progress=progress, tags=True)
        assert res[-1].flags & PushInfo.NEW_TAG
        progress.make_assertion()
        self._do_test_push_result(res, remote)

        # update push new tags
        # Rejection is default
        new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True)
        res = remote.push(tags=True)
        self._do_test_push_result(res, remote)
        assert res[-1].flags & PushInfo.REJECTED and res[-1].flags & PushInfo.ERROR

        # push force this tag
        res = remote.push("+%s" % new_tag.path)
        assert res[-1].flags & PushInfo.ERROR == 0 and res[-1].flags & PushInfo.FORCED_UPDATE

        # delete tag - have to do it using refspec
        res = remote.push(":%s" % new_tag.path)
        self._do_test_push_result(res, remote)
        assert res[0].flags & PushInfo.DELETED
        # Currently progress is not properly transferred, especially not using
        # the git daemon
        # progress.assert_received_message()

        # push new branch
        new_head = Head.create(rw_repo, "my_new_branch")
        progress = TestRemoteProgress()
        res = remote.push(new_head, progress)
        assert len(res) > 0
        assert res[0].flags & PushInfo.NEW_HEAD
        progress.make_assertion()
        self._do_test_push_result(res, remote)

        # delete new branch on the remote end and locally
        res = remote.push(":%s" % new_head.path)
        self._do_test_push_result(res, remote)
        Head.delete(rw_repo, new_head)
        assert res[-1].flags & PushInfo.DELETED

        # --all
        res = remote.push(all=True)
        self._do_test_push_result(res, remote)

        remote.pull('master')

        # cleanup - delete created tags and branches as we are in an innerloop on
        # the same repository
        TagReference.delete(rw_repo, new_tag, other_tag)
        remote.push(":%s" % other_tag.path)
Ejemplo n.º 14
0
    def _do_test_fetch(self, remote, rw_repo, remote_repo):
        # 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)
        # all up to date
        for info in res:
            assert 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]
        assert master_info.flags & FetchInfo.FORCED_UPDATE and master_info.note is not None

        # normal fast forward - set head back to previous one
        rhead.commit = remote_commit
        res = fetch_and_test(remote)
        assert 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)
        assert 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)
        assert 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.failUnlessRaises(IndexError, get_info, res, remote, new_remote_branch)

        # prune stale tracking branches
        stale_refs = remote.stale_refs
        assert len(stale_refs) == 2 and isinstance(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)
        assert len(res) == 1 and get_info(res, remote, 'master')

        # ... with respec and no target
        res = fetch_and_test(remote, refspec='master')
        assert 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'])
        # assert 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)]
        assert isinstance(tinfo.ref, TagReference) and tinfo.ref.commit == rtag.commit
        assert tinfo.flags & tinfo.NEW_TAG

        # adjust tag commit
        Reference.set_object(rtag, rhead.commit.parents[0].parents[0])
        res = fetch_and_test(remote, tags=True)
        tinfo = res[str(rtag)]
        assert tinfo.commit == rtag.commit
        assert 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.failUnlessRaises(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 = "git://localhost:%s%s" % (GIT_DAEMON_PORT, remote_repo.git_dir)

        # put origin to git-url
        other_origin = other_repo.remotes.origin
        other_origin.config_writer.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

        # assure 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:
            shutil.rmtree(other_repo_dir)
Ejemplo n.º 15
0
    def test_head_reset(self, rw_repo):
        cur_head = rw_repo.head
        old_head_commit = cur_head.commit
        new_head_commit = cur_head.ref.commit.parents[0]
        cur_head.reset(new_head_commit, index=True)  # index only
        assert cur_head.reference.commit == new_head_commit

        self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True)
        new_head_commit = new_head_commit.parents[0]
        cur_head.reset(new_head_commit, index=True, working_tree=True)  # index + wt
        assert cur_head.reference.commit == new_head_commit

        # paths - make sure we have something to do
        rw_repo.index.reset(old_head_commit.parents[0])
        cur_head.reset(cur_head, paths="test")
        cur_head.reset(new_head_commit, paths="lib")
        # hard resets with paths don't work, its all or nothing
        self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths="lib")

        # we can do a mixed reset, and then checkout from the index though
        cur_head.reset(new_head_commit)
        rw_repo.index.checkout(["lib"], force=True)

        # now that we have a write write repo, change the HEAD reference - its
        # like git-reset --soft
        heads = rw_repo.heads
        assert heads
        for head in heads:
            cur_head.reference = head
            assert cur_head.reference == head
            assert isinstance(cur_head.reference, Head)
            assert cur_head.commit == head.commit
            assert not cur_head.is_detached
        # END for each head

        # detach
        active_head = heads[0]
        curhead_commit = active_head.commit
        cur_head.reference = curhead_commit
        assert cur_head.commit == curhead_commit
        assert cur_head.is_detached
        self.failUnlessRaises(TypeError, getattr, cur_head, "reference")

        # tags are references, hence we can point to them
        some_tag = rw_repo.tags[0]
        cur_head.reference = some_tag
        assert not cur_head.is_detached
        assert cur_head.commit == some_tag.commit
        assert isinstance(cur_head.reference, TagReference)

        # put HEAD back to a real head, otherwise everything else fails
        cur_head.reference = active_head

        # type check
        self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that")

        # head handling
        commit = 'HEAD'
        prev_head_commit = cur_head.commit
        for count, new_name in enumerate(("my_new_head", "feature/feature1")):
            actual_commit = commit + "^" * count
            new_head = Head.create(rw_repo, new_name, actual_commit)
            assert new_head.is_detached
            assert cur_head.commit == prev_head_commit
            assert isinstance(new_head, Head)
            # already exists, but has the same value, so its fine
            Head.create(rw_repo, new_name, new_head.commit)

            # its not fine with a different value
            self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0])

            # force it
            new_head = Head.create(rw_repo, new_name, actual_commit, force=True)
            old_path = new_head.path
            old_name = new_head.name

            assert new_head.rename("hello").name == "hello"
            assert new_head.rename("hello/world").name == "hello/world"
            assert new_head.rename(old_name).name == old_name and new_head.path == old_path

            # rename with force
            tmp_head = Head.create(rw_repo, "tmphead")
            self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head)
            tmp_head.rename(new_head, force=True)
            assert tmp_head == new_head and tmp_head.object == new_head.object

            logfile = RefLog.path(tmp_head)
            assert os.path.isfile(logfile)
            Head.delete(rw_repo, tmp_head)
            # deletion removes the log as well
            assert not os.path.isfile(logfile)
            heads = rw_repo.heads
            assert tmp_head not in heads and new_head not in heads
            # force on deletion testing would be missing here, code looks okay though ;)
        # END for each new head name
        self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name")

        # tag ref
        tag_name = "5.0.2"
        TagReference.create(rw_repo, tag_name)
        self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name)
        light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True)
        assert isinstance(light_tag, TagReference)
        assert light_tag.name == tag_name
        assert light_tag.commit == cur_head.commit.parents[0]
        assert light_tag.tag is None

        # tag with tag object
        other_tag_name = "releases/1.0.2RC"
        msg = "my mighty tag\nsecond line"
        obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg)
        assert isinstance(obj_tag, TagReference)
        assert obj_tag.name == other_tag_name
        assert obj_tag.commit == cur_head.commit
        assert obj_tag.tag is not None

        TagReference.delete(rw_repo, light_tag, obj_tag)
        tags = rw_repo.tags
        assert light_tag not in tags and obj_tag not in tags

        # remote deletion
        remote_refs_so_far = 0
        remotes = rw_repo.remotes
        assert remotes
        for remote in remotes:
            refs = remote.refs

            # If a HEAD exists, it must be deleted first. Otherwise it might
            # end up pointing to an invalid ref it the ref was deleted before.
            remote_head_name = "HEAD"
            if remote_head_name in refs:
                RemoteReference.delete(rw_repo, refs[remote_head_name])
                del(refs[remote_head_name])
            # END handle HEAD deletion

            RemoteReference.delete(rw_repo, *refs)
            remote_refs_so_far += len(refs)
            for ref in refs:
                assert ref.remote_name == remote.name
        # END for each ref to delete
        assert remote_refs_so_far

        for remote in remotes:
            # remotes without references should produce an empty list
            self.assertEqual(remote.refs, [])
        # END for each remote

        # change where the active head points to
        if cur_head.is_detached:
            cur_head.reference = rw_repo.heads[0]

        head = cur_head.reference
        old_commit = head.commit
        head.commit = old_commit.parents[0]
        assert head.commit == old_commit.parents[0]
        assert head.commit == cur_head.commit
        head.commit = old_commit

        # setting a non-commit as commit fails, but succeeds as object
        head_tree = head.commit.tree
        self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree)
        assert head.commit == old_commit        # and the ref did not change
        # we allow heds to point to any object
        head.object = head_tree
        assert head.object == head_tree
        # cannot query tree as commit
        self.failUnlessRaises(TypeError, getattr, head, 'commit')

        # set the commit directly using the head. This would never detach the head
        assert not cur_head.is_detached
        head.object = old_commit
        cur_head.reference = head.commit
        assert cur_head.is_detached
        parent_commit = head.commit.parents[0]
        assert cur_head.is_detached
        cur_head.commit = parent_commit
        assert cur_head.is_detached and cur_head.commit == parent_commit

        cur_head.reference = head
        assert not cur_head.is_detached
        cur_head.commit = parent_commit
        assert not cur_head.is_detached
        assert head.commit == parent_commit

        # test checkout
        active_branch = rw_repo.active_branch
        for head in rw_repo.heads:
            checked_out_head = head.checkout()
            assert checked_out_head == head
        # END for each head to checkout

        # checkout with branch creation
        new_head = active_branch.checkout(b="new_head")
        assert active_branch != rw_repo.active_branch
        assert new_head == rw_repo.active_branch

        # checkout  with force as we have a changed a file
        # clear file
        open(new_head.commit.tree.blobs[-1].abspath, 'w').close()
        assert len(new_head.commit.diff(None))

        # create a new branch that is likely to touch the file we changed
        far_away_head = rw_repo.create_head("far_head", 'HEAD~100')
        self.failUnlessRaises(GitCommandError, far_away_head.checkout)
        assert active_branch == active_branch.checkout(force=True)
        assert rw_repo.head.reference != far_away_head

        # test reference creation
        partial_ref = 'sub/ref'
        full_ref = 'refs/%s' % partial_ref
        ref = Reference.create(rw_repo, partial_ref)
        assert ref.path == full_ref
        assert ref.object == rw_repo.head.commit

        self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20')
        # it works if it is at the same spot though and points to the same reference
        assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref
        Reference.delete(rw_repo, full_ref)

        # recreate the reference using a full_ref
        ref = Reference.create(rw_repo, full_ref)
        assert ref.path == full_ref
        assert ref.object == rw_repo.head.commit

        # recreate using force
        ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True)
        assert ref.path == full_ref
        assert ref.object == rw_repo.head.commit.parents[0]

        # rename it
        orig_obj = ref.object
        for name in ('refs/absname', 'rela_name', 'feature/rela_name'):
            ref_new_name = ref.rename(name)
            assert isinstance(ref_new_name, Reference)
            assert name in ref_new_name.path
            assert ref_new_name.object == orig_obj
            assert ref_new_name == ref
        # END for each name type

        # References that don't exist trigger an error if we want to access them
        self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit')

        # exists, fail unless we force
        ex_ref_path = far_away_head.path
        self.failUnlessRaises(OSError, ref.rename, ex_ref_path)
        # if it points to the same commit it works
        far_away_head.commit = ref.commit
        ref.rename(ex_ref_path)
        assert ref.path == ex_ref_path and ref.object == orig_obj
        assert ref.rename(ref.path).path == ex_ref_path  # rename to same name

        # create symbolic refs
        symref_path = "symrefs/sym"
        symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)
        assert symref.path == symref_path
        assert symref.reference == cur_head.reference

        self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit)
        # it works if the new ref points to the same reference
        SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path  # @NoEffect
        SymbolicReference.delete(rw_repo, symref)
        # would raise if the symref wouldn't have been deletedpbl
        symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)

        # test symbolic references which are not at default locations like HEAD
        # or FETCH_HEAD - they may also be at spots in refs of course
        symbol_ref_path = "refs/symbol_ref"
        symref = SymbolicReference(rw_repo, symbol_ref_path)
        assert symref.path == symbol_ref_path
        symbol_ref_abspath = os.path.join(rw_repo.git_dir, symref.path)

        # set it
        symref.reference = new_head
        assert symref.reference == new_head
        assert os.path.isfile(symbol_ref_abspath)
        assert symref.commit == new_head.commit

        for name in ('absname', 'folder/rela_name'):
            symref_new_name = symref.rename(name)
            assert isinstance(symref_new_name, SymbolicReference)
            assert name in symref_new_name.path
            assert symref_new_name.reference == new_head
            assert symref_new_name == symref
            assert not symref.is_detached
        # END for each ref

        # create a new non-head ref just to be sure we handle it even if packed
        Reference.create(rw_repo, full_ref)

        # test ref listing - assure we have packed refs
        rw_repo.git.pack_refs(all=True, prune=True)
        heads = rw_repo.heads
        assert heads
        assert new_head in heads
        assert active_branch in heads
        assert rw_repo.tags

        # we should be able to iterate all symbolic refs as well - in that case
        # we should expect only symbolic references to be returned
        for symref in SymbolicReference.iter_items(rw_repo):
            assert not symref.is_detached

        # when iterating references, we can get references and symrefs
        # when deleting all refs, I'd expect them to be gone ! Even from
        # the packed ones
        # For this to work, we must not be on any branch
        rw_repo.head.reference = rw_repo.head.commit
        deleted_refs = set()
        for ref in Reference.iter_items(rw_repo):
            if ref.is_detached:
                ref.delete(rw_repo, ref)
                deleted_refs.add(ref)
            # END delete ref
        # END for each ref to iterate and to delete
        assert deleted_refs

        for ref in Reference.iter_items(rw_repo):
            if ref.is_detached:
                assert ref not in deleted_refs
        # END for each ref

        # reattach head - head will not be returned if it is not a symbolic
        # ref
        rw_repo.head.reference = Head.create(rw_repo, "master")

        # At least the head should still exist
        assert os.path.isfile(os.path.join(rw_repo.git_dir, 'HEAD'))
        refs = list(SymbolicReference.iter_items(rw_repo))
        assert len(refs) == 1

        # test creation of new refs from scratch
        for path in ("basename", "dir/somename", "dir2/subdir/basename"):
            # REFERENCES
            ############
            fpath = Reference.to_full_path(path)
            ref_fp = Reference.from_path(rw_repo, fpath)
            assert not ref_fp.is_valid()
            ref = Reference(rw_repo, fpath)
            assert ref == ref_fp

            # can be created by assigning a commit
            ref.commit = rw_repo.head.commit
            assert ref.is_valid()

            # if the assignment raises, the ref doesn't exist
            Reference.delete(ref.repo, ref.path)
            assert not ref.is_valid()
            self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense")
            assert not ref.is_valid()

            # I am sure I had my reason to make it a class method at first, but
            # now it doesn't make so much sense anymore, want an instance method as well
            # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27
            Reference.delete(ref.repo, ref.path)
            assert not ref.is_valid()

            ref.object = rw_repo.head.commit
            assert ref.is_valid()

            Reference.delete(ref.repo, ref.path)
            assert not ref.is_valid()
            self.failUnlessRaises(ValueError, setattr, ref, 'object', "nonsense")
            assert not ref.is_valid()
Ejemplo n.º 16
0
    def test_head_reset(self, rw_repo):
        cur_head = rw_repo.head
        old_head_commit = cur_head.commit
        new_head_commit = cur_head.ref.commit.parents[0]
        cur_head.reset(new_head_commit, index=True)  # index only
        assert cur_head.reference.commit == new_head_commit

        self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True)
        new_head_commit = new_head_commit.parents[0]
        cur_head.reset(new_head_commit, index=True, working_tree=True)  # index + wt
        assert cur_head.reference.commit == new_head_commit

        # paths - make sure we have something to do
        rw_repo.index.reset(old_head_commit.parents[0])
        cur_head.reset(cur_head, paths="test")
        cur_head.reset(new_head_commit, paths="lib")
        # hard resets with paths don't work, its all or nothing
        self.failUnlessRaises(GitCommandError, cur_head.reset, new_head_commit, working_tree=True, paths="lib")

        # we can do a mixed reset, and then checkout from the index though
        cur_head.reset(new_head_commit)
        rw_repo.index.checkout(["lib"], force=True)

        # now that we have a write write repo, change the HEAD reference - its
        # like git-reset --soft
        heads = rw_repo.heads
        assert heads
        for head in heads:
            cur_head.reference = head
            assert cur_head.reference == head
            assert isinstance(cur_head.reference, Head)
            assert cur_head.commit == head.commit
            assert not cur_head.is_detached
        # END for each head

        # detach
        active_head = heads[0]
        curhead_commit = active_head.commit
        cur_head.reference = curhead_commit
        assert cur_head.commit == curhead_commit
        assert cur_head.is_detached
        self.failUnlessRaises(TypeError, getattr, cur_head, "reference")

        # tags are references, hence we can point to them
        some_tag = rw_repo.tags[0]
        cur_head.reference = some_tag
        assert not cur_head.is_detached
        assert cur_head.commit == some_tag.commit
        assert isinstance(cur_head.reference, TagReference)

        # put HEAD back to a real head, otherwise everything else fails
        cur_head.reference = active_head

        # type check
        self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that")

        # head handling
        commit = 'HEAD'
        prev_head_commit = cur_head.commit
        for count, new_name in enumerate(("my_new_head", "feature/feature1")):
            actual_commit = commit + "^" * count
            new_head = Head.create(rw_repo, new_name, actual_commit)
            assert new_head.is_detached
            assert cur_head.commit == prev_head_commit
            assert isinstance(new_head, Head)
            # already exists, but has the same value, so its fine
            Head.create(rw_repo, new_name, new_head.commit)

            # its not fine with a different value
            self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0])

            # force it
            new_head = Head.create(rw_repo, new_name, actual_commit, force=True)
            old_path = new_head.path
            old_name = new_head.name

            assert new_head.rename("hello").name == "hello"
            assert new_head.rename("hello/world").name == "hello/world"
            assert new_head.rename(old_name).name == old_name and new_head.path == old_path

            # rename with force
            tmp_head = Head.create(rw_repo, "tmphead")
            self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head)
            tmp_head.rename(new_head, force=True)
            assert tmp_head == new_head and tmp_head.object == new_head.object

            logfile = RefLog.path(tmp_head)
            assert osp.isfile(logfile)
            Head.delete(rw_repo, tmp_head)
            # deletion removes the log as well
            assert not osp.isfile(logfile)
            heads = rw_repo.heads
            assert tmp_head not in heads and new_head not in heads
            # force on deletion testing would be missing here, code looks okay though ;)
        # END for each new head name
        self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name")

        # tag ref
        tag_name = "5.0.2"
        TagReference.create(rw_repo, tag_name)
        self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name)
        light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True)
        assert isinstance(light_tag, TagReference)
        assert light_tag.name == tag_name
        assert light_tag.commit == cur_head.commit.parents[0]
        assert light_tag.tag is None

        # tag with tag object
        other_tag_name = "releases/1.0.2RC"
        msg = "my mighty tag\nsecond line"
        obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg)
        assert isinstance(obj_tag, TagReference)
        assert obj_tag.name == other_tag_name
        assert obj_tag.commit == cur_head.commit
        assert obj_tag.tag is not None

        TagReference.delete(rw_repo, light_tag, obj_tag)
        tags = rw_repo.tags
        assert light_tag not in tags and obj_tag not in tags

        # remote deletion
        remote_refs_so_far = 0
        remotes = rw_repo.remotes
        assert remotes
        for remote in remotes:
            refs = remote.refs

            # If a HEAD exists, it must be deleted first. Otherwise it might
            # end up pointing to an invalid ref it the ref was deleted before.
            remote_head_name = "HEAD"
            if remote_head_name in refs:
                RemoteReference.delete(rw_repo, refs[remote_head_name])
                del(refs[remote_head_name])
            # END handle HEAD deletion

            RemoteReference.delete(rw_repo, *refs)
            remote_refs_so_far += len(refs)
            for ref in refs:
                assert ref.remote_name == remote.name
        # END for each ref to delete
        assert remote_refs_so_far

        for remote in remotes:
            # remotes without references should produce an empty list
            self.assertEqual(remote.refs, [])
        # END for each remote

        # change where the active head points to
        if cur_head.is_detached:
            cur_head.reference = rw_repo.heads[0]

        head = cur_head.reference
        old_commit = head.commit
        head.commit = old_commit.parents[0]
        assert head.commit == old_commit.parents[0]
        assert head.commit == cur_head.commit
        head.commit = old_commit

        # setting a non-commit as commit fails, but succeeds as object
        head_tree = head.commit.tree
        self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree)
        assert head.commit == old_commit        # and the ref did not change
        # we allow heds to point to any object
        head.object = head_tree
        assert head.object == head_tree
        # cannot query tree as commit
        self.failUnlessRaises(TypeError, getattr, head, 'commit')

        # set the commit directly using the head. This would never detach the head
        assert not cur_head.is_detached
        head.object = old_commit
        cur_head.reference = head.commit
        assert cur_head.is_detached
        parent_commit = head.commit.parents[0]
        assert cur_head.is_detached
        cur_head.commit = parent_commit
        assert cur_head.is_detached and cur_head.commit == parent_commit

        cur_head.reference = head
        assert not cur_head.is_detached
        cur_head.commit = parent_commit
        assert not cur_head.is_detached
        assert head.commit == parent_commit

        # test checkout
        active_branch = rw_repo.active_branch
        for head in rw_repo.heads:
            checked_out_head = head.checkout()
            assert checked_out_head == head
        # END for each head to checkout

        # checkout with branch creation
        new_head = active_branch.checkout(b="new_head")
        assert active_branch != rw_repo.active_branch
        assert new_head == rw_repo.active_branch

        # checkout  with force as we have a changed a file
        # clear file
        open(new_head.commit.tree.blobs[-1].abspath, 'w').close()
        assert len(new_head.commit.diff(None))

        # create a new branch that is likely to touch the file we changed
        far_away_head = rw_repo.create_head("far_head", 'HEAD~100')
        self.failUnlessRaises(GitCommandError, far_away_head.checkout)
        assert active_branch == active_branch.checkout(force=True)
        assert rw_repo.head.reference != far_away_head

        # test reference creation
        partial_ref = 'sub/ref'
        full_ref = 'refs/%s' % partial_ref
        ref = Reference.create(rw_repo, partial_ref)
        assert ref.path == full_ref
        assert ref.object == rw_repo.head.commit

        self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20')
        # it works if it is at the same spot though and points to the same reference
        assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref
        Reference.delete(rw_repo, full_ref)

        # recreate the reference using a full_ref
        ref = Reference.create(rw_repo, full_ref)
        assert ref.path == full_ref
        assert ref.object == rw_repo.head.commit

        # recreate using force
        ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True)
        assert ref.path == full_ref
        assert ref.object == rw_repo.head.commit.parents[0]

        # rename it
        orig_obj = ref.object
        for name in ('refs/absname', 'rela_name', 'feature/rela_name'):
            ref_new_name = ref.rename(name)
            assert isinstance(ref_new_name, Reference)
            assert name in ref_new_name.path
            assert ref_new_name.object == orig_obj
            assert ref_new_name == ref
        # END for each name type

        # References that don't exist trigger an error if we want to access them
        self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit')

        # exists, fail unless we force
        ex_ref_path = far_away_head.path
        self.failUnlessRaises(OSError, ref.rename, ex_ref_path)
        # if it points to the same commit it works
        far_away_head.commit = ref.commit
        ref.rename(ex_ref_path)
        assert ref.path == ex_ref_path and ref.object == orig_obj
        assert ref.rename(ref.path).path == ex_ref_path  # rename to same name

        # create symbolic refs
        symref_path = "symrefs/sym"
        symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)
        assert symref.path == symref_path
        assert symref.reference == cur_head.reference

        self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit)
        # it works if the new ref points to the same reference
        SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path  # @NoEffect
        SymbolicReference.delete(rw_repo, symref)
        # would raise if the symref wouldn't have been deletedpbl
        symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)

        # test symbolic references which are not at default locations like HEAD
        # or FETCH_HEAD - they may also be at spots in refs of course
        symbol_ref_path = "refs/symbol_ref"
        symref = SymbolicReference(rw_repo, symbol_ref_path)
        assert symref.path == symbol_ref_path
        symbol_ref_abspath = osp.join(rw_repo.git_dir, symref.path)

        # set it
        symref.reference = new_head
        assert symref.reference == new_head
        assert osp.isfile(symbol_ref_abspath)
        assert symref.commit == new_head.commit

        for name in ('absname', 'folder/rela_name'):
            symref_new_name = symref.rename(name)
            assert isinstance(symref_new_name, SymbolicReference)
            assert name in symref_new_name.path
            assert symref_new_name.reference == new_head
            assert symref_new_name == symref
            assert not symref.is_detached
        # END for each ref

        # create a new non-head ref just to be sure we handle it even if packed
        Reference.create(rw_repo, full_ref)

        # test ref listing - assure we have packed refs
        rw_repo.git.pack_refs(all=True, prune=True)
        heads = rw_repo.heads
        assert heads
        assert new_head in heads
        assert active_branch in heads
        assert rw_repo.tags

        # we should be able to iterate all symbolic refs as well - in that case
        # we should expect only symbolic references to be returned
        for symref in SymbolicReference.iter_items(rw_repo):
            assert not symref.is_detached

        # when iterating references, we can get references and symrefs
        # when deleting all refs, I'd expect them to be gone ! Even from
        # the packed ones
        # For this to work, we must not be on any branch
        rw_repo.head.reference = rw_repo.head.commit
        deleted_refs = set()
        for ref in Reference.iter_items(rw_repo):
            if ref.is_detached:
                ref.delete(rw_repo, ref)
                deleted_refs.add(ref)
            # END delete ref
        # END for each ref to iterate and to delete
        assert deleted_refs

        for ref in Reference.iter_items(rw_repo):
            if ref.is_detached:
                assert ref not in deleted_refs
        # END for each ref

        # reattach head - head will not be returned if it is not a symbolic
        # ref
        rw_repo.head.reference = Head.create(rw_repo, "master")

        # At least the head should still exist
        assert osp.isfile(osp.join(rw_repo.git_dir, 'HEAD'))
        refs = list(SymbolicReference.iter_items(rw_repo))
        assert len(refs) == 1

        # test creation of new refs from scratch
        for path in ("basename", "dir/somename", "dir2/subdir/basename"):
            # REFERENCES
            ############
            fpath = Reference.to_full_path(path)
            ref_fp = Reference.from_path(rw_repo, fpath)
            assert not ref_fp.is_valid()
            ref = Reference(rw_repo, fpath)
            assert ref == ref_fp

            # can be created by assigning a commit
            ref.commit = rw_repo.head.commit
            assert ref.is_valid()

            # if the assignment raises, the ref doesn't exist
            Reference.delete(ref.repo, ref.path)
            assert not ref.is_valid()
            self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense")
            assert not ref.is_valid()

            # I am sure I had my reason to make it a class method at first, but
            # now it doesn't make so much sense anymore, want an instance method as well
            # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27
            Reference.delete(ref.repo, ref.path)
            assert not ref.is_valid()

            ref.object = rw_repo.head.commit
            assert ref.is_valid()

            Reference.delete(ref.repo, ref.path)
            assert not ref.is_valid()
            self.failUnlessRaises(ValueError, setattr, ref, 'object', "nonsense")
            assert not ref.is_valid()