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
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()
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")
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
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()
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()
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
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
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
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]