def test_update_remote_patches_branch_with_dev_mode(mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WITH dev_mode set to true
    WHEN update_remote_patches_branch is called
    THEN tag.delete is not called
    AND git.push is called with -n argument for dry-run
    """
    mock_repo.branch.cherry_on_head_only.return_value = True

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "2019",
                      dev_mode=True,
                      release='2.1')
    rebaser.update_remote_patches_branch()

    # Tag not deleted, and pushed with -n for dry-run
    assert mock_repo.tag.delete.called is False
    assert mock_repo.git.push.called is True

    expected = [(("-n", "my_remote", "private-rebaser-2.1-2019-previous"), ),
                (("-nf", "--follow-tags", "my_remote", "my_branch"), )]
    assert mock_repo.git.push.call_args_list == expected
Пример #2
0
def test_rebase_doesnt_push_in_dev_mode(local_repo, patches_repo_root):
    """
    GIVEN a local repository initialized with origin and upstream remotes
    WHEN running Rebaser.rebase_and_update_remote
    WITH dev_mode set to True
    THEN the local repo is rebased
    AND the origin remote is not updated with the rebase results
    """
    patches_repo = GitRepo(patches_repo_root)
    commit_to_rebase_to = "61a18a2a"

    # confirm we are the same as the patches repo
    orig_local_repo_head = local_repo.repo.head.object.hexsha
    assert patches_repo.repo.head.object.hexsha == orig_local_repo_head

    # Rebase with dev_mode on
    rebaser = Rebaser(local_repo, "master", commit_to_rebase_to, "origin",
                      "0000", True)
    rebaser.rebase_and_update_remote()

    # assert local repo was updated
    assert local_repo.repo.head.object.hexsha != orig_local_repo_head

    # assert remote repo was not updated
    assert patches_repo.repo.head.object.hexsha == orig_local_repo_head
def test_rebase_and_update_remote_success_after_retry(mock_repo, monkeypatch):
    """
    GIVEN Rebaser initialized correctly
    WHEN rebase_and_update_remote is called
    AND the remote changes once during the rebase
    THEN the tag gets created
    AND the previous tag gets deleted and re-created during the retry
    AND git.push is called
    """
    monkeypatch.setattr(time, 'sleep', lambda s: None)

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)

    mock_repo.commit.same.side_effect = [False, True]
    rebaser.rebase_and_update_remote()

    assert mock_repo.tag.create.call_count == 2
    assert mock_repo.remote.fetch.call_count == 4
    mock_repo.tag.delete.assert_called_once()

    mock_repo.git.push.assert_called()
def test_rebase_and_update_remote_fails_next_rebase(mock_repo, monkeypatch):
    """
    GIVEN Rebaser initialized correctly
    WHEN rebase_and_update_remote is called
    AND the remote changed once during the rebase
    AND the rebase fails with RebaseException during the second rebase
    THEN a RebaseException is raised
    AND git.push is not called
    """
    monkeypatch.setattr(time, 'sleep', lambda s: None)

    mock_repo.branch.rebase_to_hash.side_effect = [
        None, exceptions.RebaseException
    ]
    mock_repo.commit.same.side_effect = [False, True]

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)
    with pytest.raises(exceptions.RebaseException):
        rebaser.rebase_and_update_remote()

    assert mock_repo.remote.fetch.call_count == 3

    assert mock_repo.tag.create.call_count == 2
    mock_repo.tag.delete.assert_called_once()
    mock_repo.git.push.assert_not_called()
def test_rebase_and_update_remote(mock_repo, monkeypatch):
    """
    GIVEN Rebaser initialized correctly
    WHEN rebase_and_update_remote is called
    THEN a tag is created
    AND remote.fetch is called twice to catch remote updates during the rebase
    AND git.push is called
    """
    monkeypatch.setattr(time, 'sleep', lambda s: None)

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "000",
                      dev_mode=True,
                      release='15.0')

    mock_repo.commit.same.return_value = True
    rebaser.rebase_and_update_remote()

    assert mock_repo.tag.create.call_count == 1
    assert mock_repo.remote.fetch.call_count == 2

    expected = [(("-n", "my_remote", "private-rebaser-15.0-000-previous"), ),
                (("-nf", "--follow-tags", "my_remote", "my_branch"), )]
    assert mock_repo.git.push.call_args_list == expected
    def retry(max_retries):
        rebaser = Rebaser(mock_repo, "my_branch", "my_commit", "my_remote",
                          "my_tstamp", True, max_retries)
        rebaser.rebase_and_update_remote()

        assert mock_repo.tag.create.call_count == 1 + max_retries
        assert mock_repo.remote.fetch.call_count == 2 + 2 * max_retries
        assert mock_repo.tag.delete.call_count == max_retries
        mock_repo.git.push.assert_not_called()

        mock_repo.reset_mock()
Пример #7
0
def test_rebase_avoid_pushing_unnecessary_tags(local_repo, patches_repo_root):
    """
    GIVEN a local repository initialized with origin and upstream remotes
    WHEN running Rebaser.rebase_and_update_remote
    AND the rebase history doesn't change
    THEN the newly created tag doesn't get pushed to the remote
    """
    patches_repo = GitRepo(patches_repo_root)
    commit_to_rebase_to = "61a18a2a"

    # Rebase
    rebaser = Rebaser(local_repo, "master", commit_to_rebase_to, "origin",
                      "0000", False)
    rebaser.rebase_and_update_remote()

    # Assert remote repo was updated with the rebase result
    rebased_head = local_repo.repo.head.commit.hexsha
    assert patches_repo.repo.head.commit.hexsha == rebased_head

    # Run another rebase - no changes but new timestamp
    rebaser = Rebaser(local_repo, "master", commit_to_rebase_to, "origin",
                      "0001", False)
    rebaser.rebase_and_update_remote()

    # Check the first tag was pushed, but not the second one
    patches_repo.commit.describe("private-rebaser-0000-previous")
    try:
        patches_repo.commit.describe("private-rebaser-0001-previous")
        pytest.fail("Tag was pushed when it shouldn't have")
    except gw_exceptions.ReferenceNotFoundException:
        pass
def test_perform_rebase(mock_repo):
    """
    GIVEN Rebaser initialized correctly including branch and commit
    WHEN perform_rebase is called
    THEN branch.rebase_to_hash is called
    WITH the same branch and commit
    """
    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)
    rebaser.perform_rebase()

    mock_repo.branch.rebase_to_hash.assert_called()
    mock_repo.branch.rebase_to_hash.assert_called_with("my_branch",
                                                       "my_commit")
Пример #9
0
def test_rebase(local_repo, patches_repo_root):
    """
    GIVEN a local repository initialized with origin and upstream remotes
    WHEN running Rebaser.rebase_and_update_remote
    THEN the local repo is rebased to contain both upstream and origin commits
    AND the origin remote is updated with same
    """
    patches_repo = GitRepo(patches_repo_root)
    commit_to_rebase_to = "61a18a2a"

    # confirm we are the same as the patches repo
    local_repo_head = local_repo.repo.head.object.hexsha
    assert patches_repo.repo.head.object.hexsha == local_repo_head

    # confirm we have incoming patches from upstream
    assert len(
        local_repo.branch.cherry_on_head_only("master",
                                              commit_to_rebase_to)) == 2

    # confirm we have additional patches compared to upstream
    assert len(
        local_repo.branch.cherry_on_head_only(commit_to_rebase_to,
                                              "master")) == 1

    # Rebase with dev_mode off
    rebaser = Rebaser(local_repo, "master", commit_to_rebase_to, "origin",
                      "0000", False)
    rebaser.rebase_and_update_remote()

    # confirm no more incoming patches from upstream
    assert len(
        local_repo.branch.cherry_on_head_only("master",
                                              commit_to_rebase_to)) == 0

    # confirm we still have our additional patch
    assert len(
        local_repo.branch.cherry_on_head_only(commit_to_rebase_to,
                                              "master")) == 1

    # assert remote repo was updated as well
    local_repo_head = local_repo.repo.head.object.hexsha
    assert patches_repo.repo.head.object.hexsha == local_repo_head
Пример #10
0
def test_rebase_exception_gitreview(mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WHEN perform_rebase asserts with RebaseException
    THEN Rebaser calls the try_automated_rebase_fix method
    """
    mock_repo.branch.rebase_to_hash.side_effect = [
        exceptions.RebaseException('.gitreview failed to rebase'), True
    ]
    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)

    rebaser.try_automated_rebase_fix = Mock()
    rebaser.try_automated_rebase_fix.side_effect = [True]
    rebaser.perform_rebase()
    rebaser.try_automated_rebase_fix.assert_called()
Пример #11
0
def test_perform_rebase_aborts_on_failure(mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WHEN rebase_to_hash fails with RebaseException
    THEN perform_rebase also raises RebaseException
    AND abort_rebase is called
    """
    mock_repo.branch.rebase_to_hash.side_effect = exceptions.RebaseException

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)

    with pytest.raises(exceptions.RebaseException):
        rebaser.perform_rebase()

    mock_repo.branch.abort_rebase.assert_called()
Пример #12
0
def test_update_remote_patches_branch_no_changes_with_remote(mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WHEN update_remote_patches_branch is called
    AND cherry_on_head_only returns false (indicating the local and remote
        branches have no differences)
    THEN tag.delete is called
    AND git.push is not called
    """
    mock_repo.branch.cherry_on_head_only.return_value = False

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)
    rebaser.update_remote_patches_branch()

    mock_repo.tag.delete.assert_called_once()
    assert mock_repo.git.push.called is False
Пример #13
0
def test_rebase_exception_not_gitreview(mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WHEN perform_rebase asserts with RebaseException
    AND the exception message does not contain .gitreview
    THEN Rebaser calls the repo.branch.abort_rebase method
    """
    mock_repo.branch.rebase_to_hash.side_effect = [
        exceptions.RebaseException('Whatever other exception'), True
    ]
    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)

    rebaser.repo.branch.abort_rebase = Mock()
    with pytest.raises(exceptions.RebaseException):
        rebaser.perform_rebase()
    rebaser.repo.branch.abort_rebase.assert_called()
def test_update_remote_patches_branch_no_changes_and_commit_present(mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WHEN update_remote_patches_branch is called
    AND cherry_on_head_only returns false (indicating the local and remote
        branches have no differences)
    AND branch.remote_contains returns true
    THEN git.push is not called
    """
    mock_repo.branch.cherry_on_head_only.return_value = False
    mock_repo.branch.remote_contains.return_value = True

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)
    rebaser.update_remote_patches_branch()

    assert mock_repo.git.push.called is False
Пример #15
0
def test_try_automated_rebase_fix(reb_mock, mock_repo):
    """
    GIVEN Rebaser initialized correctly
    WHEN perform_rebase asserts with RebaseException
    AND the exception contains .gitreview in the exception message
    AND Rebaser calls the try_automated_rebase_fix method
    THEN try_automated_rebase_fix detects .gitreview in the exception message
    AND calls git.rebase('--skip') and _rebuild_gitreview
    """

    rebaser = Rebaser(mock_repo,
                      "my_branch",
                      "my_commit",
                      "my_remote",
                      "my_tstamp",
                      dev_mode=True)

    exception = exceptions.RebaseException('.gitreview failed to rebase')
    output = rebaser.try_automated_rebase_fix(exception)

    mock_repo.git.rebase.assert_called_with('--skip')
    reb_mock.assert_called()
    assert output is True
Пример #16
0
def test_rebase_retry_logic(local_repo, patches_repo_root, datadir,
                            monkeypatch):
    """
    GIVEN a local repository initialized with origin and upstream remotes
    WHEN running Rebaser.rebase_and_update_remote
    AND the remote branch changes during the rebase
    THEN the rebase operation gets run again
    AND the local repo is rebased and contains both upstream commits and all
        commits from the remote branch, including the new change
    AND the remote branch is updated with the rebase result
    """
    commit_to_rebase_to = "61a18a2acd05b2f37bd75164b8ddfdb71011fe68"
    orig_head = local_repo.repo.head.commit.hexsha

    # Mock sleep to avoid unnecessary waiting
    monkeypatch.setattr(time, 'sleep', lambda s: None)

    # Mock rebaser perform_rebase function so we can create a change
    # during that window
    rebaser = Rebaser(local_repo, "master", commit_to_rebase_to, "origin",
                      "0000", False)
    orig_perform_rebase = rebaser.perform_rebase

    def modified_perform_rebase():
        apply_inflight_patch()
        orig_perform_rebase()

    def apply_inflight_patch():
        tmp_repo_root = "/tmp/test_retry"
        if os.path.exists(tmp_repo_root):
            # We already applied the inflight patch
            return
        else:
            tmp_repo = GitRepo.clone(patches_repo_root, tmp_repo_root)
            tmp_repo.branch.apply_patch("master", (datadir / "inflight.patch"))
            tmp_repo.git.push("origin", "master")

    # Confirm we have one local-only patch
    assert len(
        local_repo.branch.cherry_on_head_only(commit_to_rebase_to,
                                              "master")) == 1

    # Test rebase with one inflight patch
    with patch.object(Rebaser, 'perform_rebase') as patched_rebase:
        patched_rebase.side_effect = modified_perform_rebase
        rebaser.rebase_and_update_remote()

    assert patched_rebase.call_count == 2

    # Now we have two local-only patches
    assert len(
        local_repo.branch.cherry_on_head_only(commit_to_rebase_to,
                                              "master")) == 2

    # Check we have both local-only patches + upstream commits
    log = local_repo.branch.log_diff(orig_head, "master", "$hash $summary")
    assert "INFLIGHT" in log[0]
    assert "LOCAL-ONLY" in log[1]
    assert commit_to_rebase_to in log[2]

    patches_repo = GitRepo(patches_repo_root)
    assert patches_repo.repo.head.commit.hexsha in log[0]