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_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_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_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(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(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_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]