Documentation
#!/usr/bin/env python3

import os
import re

import git
import pytest
from helpers import TempGitRepositoryWorktree, funcname, grm, shell


@pytest.mark.parametrize("pull", [True, False])
@pytest.mark.parametrize("rebase", [True, False])
@pytest.mark.parametrize("ffable", [True, False])
@pytest.mark.parametrize("has_changes", [True, False])
@pytest.mark.parametrize("stash", [True, False])
def test_worktree_rebase(pull, rebase, ffable, has_changes, stash):
    with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _root_commit):
        with open(os.path.join(base_dir, "grm.toml"), "w") as f:
            f.write('persistent_branches = ["mybasebranch"]')

        repo = git.Repo(f"{base_dir}/.git-main-working-tree")

        grm(
            ["wt", "add", "mybasebranch", "--track", "origin/mybasebranch"],
            cwd=base_dir,
        )

        shell(
            f"""
            cd {base_dir}/mybasebranch
            echo change > mychange-root
            git add mychange-root
            git commit -m "commit-root"
            echo change > mychange-base-local
            git add mychange-base-local
            git commit -m "commit-in-base-local"
            git push origin mybasebranch
        """
        )

        grm(
            ["wt", "add", "myfeatbranch", "--track", "origin/myfeatbranch"],
            cwd=base_dir,
        )
        shell(
            f"""
            cd {base_dir}/myfeatbranch
            git reset --hard mybasebranch^ # root
            echo change > mychange-feat-local
            git add mychange-feat-local
            git commit -m "commit-in-feat-local"
            git push origin HEAD:myfeatbranch
        """
        )

        grm(["wt", "add", "tmp"], cwd=base_dir)
        shell(
            f"""
            cd {base_dir}/tmp
            git reset --hard mybasebranch
            echo change > mychange-base-remote
            git add mychange-base-remote
            git commit -m "commit-in-base-remote"
            git push origin HEAD:mybasebranch

            git reset --hard myfeatbranch
            echo change > mychange-feat-remote
            git add mychange-feat-remote
            git commit -m "commit-in-feat-remote"
            git push origin HEAD:myfeatbranch
        """
        )

        if not ffable:
            shell(
                f"""
                cd {base_dir}/mybasebranch
                echo change > mychange-base-no-ff
                git add mychange-base-no-ff
                git commit -m "commit-in-base-local-no-ff"

                cd {base_dir}/myfeatbranch
                echo change > mychange-feat-no-ff
                git add mychange-feat-no-ff
                git commit -m "commit-in-feat-local-no-ff"
            """
            )

        if has_changes:
            shell(
                f"""
                cd {base_dir}/myfeatbranch
                echo uncommitedchange > uncommitedchange
            """
            )

        grm(["wt", "delete", "--force", "tmp"], cwd=base_dir)

        repo = git.Repo(f"{base_dir}/.git-main-working-tree")
        if ffable:
            assert repo.commit("mybasebranch~1").message.strip() == "commit-root"
            assert (
                repo.refs.mybasebranch.commit.message.strip() == "commit-in-base-local"
            )
            assert (
                repo.remote("origin").refs.mybasebranch.commit.message.strip()
                == "commit-in-base-remote"
            )
            assert (
                repo.refs.myfeatbranch.commit.message.strip() == "commit-in-feat-local"
            )
            assert (
                repo.remote("origin").refs.myfeatbranch.commit.message.strip()
                == "commit-in-feat-remote"
            )
        else:
            assert (
                repo.commit("mybasebranch").message.strip()
                == "commit-in-base-local-no-ff"
            )
            assert (
                repo.commit("mybasebranch~1").message.strip() == "commit-in-base-local"
            )
            assert repo.commit("mybasebranch~2").message.strip() == "commit-root"
            assert (
                repo.commit("myfeatbranch").message.strip()
                == "commit-in-feat-local-no-ff"
            )
            assert (
                repo.commit("myfeatbranch~1").message.strip() == "commit-in-feat-local"
            )
            assert repo.commit("myfeatbranch~2").message.strip() == "commit-root"
            assert (
                repo.remote("origin").refs.mybasebranch.commit.message.strip()
                == "commit-in-base-remote"
            )
            assert (
                repo.remote("origin").refs.myfeatbranch.commit.message.strip()
                == "commit-in-feat-remote"
            )

        args = ["wt", "rebase"]
        if pull:
            args += ["--pull"]
        if rebase:
            args += ["--rebase"]
        if stash:
            args += ["--stash"]
        cmd = grm(args, cwd=base_dir)

        if rebase and not pull:
            assert cmd.returncode != 0
            assert len(cmd.stderr) != 0
        elif has_changes and not stash:
            assert cmd.returncode != 0
            assert re.search(r".*myfeatbranch.*contains changes.*", cmd.stderr)
        else:
            repo = git.Repo(f"{base_dir}/myfeatbranch")
            if has_changes:
                assert ["uncommitedchange"] == repo.untracked_files
            if pull:
                if rebase:
                    assert cmd.returncode == 0
                    if ffable:
                        assert (
                            repo.commit("HEAD").message.strip()
                            == "commit-in-feat-remote"
                        )
                        assert (
                            repo.commit("HEAD~1").message.strip()
                            == "commit-in-feat-local"
                        )
                        assert (
                            repo.commit("HEAD~2").message.strip()
                            == "commit-in-base-remote"
                        )
                        assert (
                            repo.commit("HEAD~3").message.strip()
                            == "commit-in-base-local"
                        )
                        assert repo.commit("HEAD~4").message.strip() == "commit-root"
                    else:
                        assert (
                            repo.commit("HEAD").message.strip()
                            == "commit-in-feat-local-no-ff"
                        )
                        assert (
                            repo.commit("HEAD~1").message.strip()
                            == "commit-in-feat-remote"
                        )
                        assert (
                            repo.commit("HEAD~2").message.strip()
                            == "commit-in-feat-local"
                        )
                        assert (
                            repo.commit("HEAD~3").message.strip()
                            == "commit-in-base-local-no-ff"
                        )
                        assert (
                            repo.commit("HEAD~4").message.strip()
                            == "commit-in-base-remote"
                        )
                        assert (
                            repo.commit("HEAD~5").message.strip()
                            == "commit-in-base-local"
                        )
                        assert repo.commit("HEAD~6").message.strip() == "commit-root"
                else:
                    if ffable:
                        assert cmd.returncode == 0
                        assert (
                            repo.commit("HEAD").message.strip()
                            == "commit-in-feat-remote"
                        )
                        assert (
                            repo.commit("HEAD~1").message.strip()
                            == "commit-in-feat-local"
                        )
                        assert (
                            repo.commit("HEAD~2").message.strip()
                            == "commit-in-base-remote"
                        )
                        assert (
                            repo.commit("HEAD~3").message.strip()
                            == "commit-in-base-local"
                        )
                        assert repo.commit("HEAD~4").message.strip() == "commit-root"
                    else:
                        assert cmd.returncode != 0
                        assert (
                            repo.commit("HEAD").message.strip()
                            == "commit-in-feat-local-no-ff"
                        )
                        assert (
                            repo.commit("HEAD~1").message.strip()
                            == "commit-in-feat-local"
                        )
                        assert (
                            repo.commit("HEAD~2").message.strip()
                            == "commit-in-base-local-no-ff"
                        )
                        assert (
                            repo.commit("HEAD~3").message.strip()
                            == "commit-in-base-local"
                        )
                        assert repo.commit("HEAD~4").message.strip() == "commit-root"
            else:
                assert cmd.returncode == 0
                if ffable:
                    assert repo.commit("HEAD").message.strip() == "commit-in-feat-local"
                    assert (
                        repo.commit("HEAD~1").message.strip() == "commit-in-base-local"
                    )
                    assert repo.commit("HEAD~2").message.strip() == "commit-root"
                else:
                    assert (
                        repo.commit("HEAD").message.strip()
                        == "commit-in-feat-local-no-ff"
                    )
                    assert (
                        repo.commit("HEAD~1").message.strip() == "commit-in-feat-local"
                    )
                    assert (
                        repo.commit("HEAD~2").message.strip()
                        == "commit-in-base-local-no-ff"
                    )
                    assert (
                        repo.commit("HEAD~3").message.strip() == "commit-in-base-local"
                    )
                    assert repo.commit("HEAD~4").message.strip() == "commit-root"