diff --git a/dist/cpr/cpr/common.py b/dist/cpr/cpr/common.py
new file mode 100644
index 0000000..99f3f7c
--- /dev/null
+++ b/dist/cpr/cpr/common.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+import random
+import re
+import string
+
+
+def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
+    return "".join(random.choice(chars) for _ in range(length))
+
+
+def parse_github_repository(url):
+    # Parse the protocol and github repository from a URL
+    # e.g. HTTPS, peter-evans/create-pull-request
+    https_pattern = re.compile(r"^https://github.com/(.+/.+)$")
+    ssh_pattern = re.compile(r"^git@github.com:(.+/.+).git$")
+
+    match = https_pattern.match(url)
+    if match is not None:
+        return "HTTPS", match.group(1)
+
+    match = ssh_pattern.match(url)
+    if match is not None:
+        return "SSH", match.group(1)
+
+    raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
+
+
+def parse_display_name_email(display_name_email):
+    # Parse the name and email address from a string in the following format
+    # Display Name <email@address.com>
+    pattern = re.compile(r"^([^<]+)\s*<([^>]+)>$")
+
+    # Check we have a match
+    match = pattern.match(display_name_email)
+    if match is None:
+        raise ValueError(
+            f"The format of '{display_name_email}' is not a valid email address with display name"
+        )
+
+    # Check that name and email are not just whitespace
+    name = match.group(1).strip()
+    email = match.group(2).strip()
+    if len(name) == 0 or len(email) == 0:
+        raise ValueError(
+            f"The format of '{display_name_email}' is not a valid email address with display name"
+        )
+
+    return name, email
diff --git a/dist/cpr/cpr/create_or_update_branch.py b/dist/cpr/cpr/create_or_update_branch.py
new file mode 100644
index 0000000..2cb24d1
--- /dev/null
+++ b/dist/cpr/cpr/create_or_update_branch.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+""" Create or Update Branch """
+import common as cmn
+from git import Repo, GitCommandError
+import os
+
+
+CHERRYPICK_EMPTY = (
+    "The previous cherry-pick is now empty, possibly due to conflict resolution."
+)
+
+
+def fetch_successful(repo, repo_url, branch):
+    try:
+        repo.git.fetch(repo_url, f"{branch}:refs/remotes/origin/{branch}")
+    except GitCommandError:
+        return False
+    return True
+
+
+def is_ahead(repo, branch_1, branch_2):
+    # Return true if branch_2 is ahead of branch_1
+    return (
+        int(repo.git.rev_list("--right-only", "--count", f"{branch_1}...{branch_2}"))
+        > 0
+    )
+
+
+def is_behind(repo, branch_1, branch_2):
+    # Return true if branch_2 is behind branch_1
+    return (
+        int(repo.git.rev_list("--left-only", "--count", f"{branch_1}...{branch_2}")) > 0
+    )
+
+
+def is_even(repo, branch_1, branch_2):
+    # Return true if branch_2 is even with branch_1
+    return not is_ahead(repo, branch_1, branch_2) and not is_behind(
+        repo, branch_1, branch_2
+    )
+
+
+def has_diff(repo, branch_1, branch_2):
+    diff = repo.git.diff(f"{branch_1}..{branch_2}")
+    return len(diff) > 0
+
+
+def create_or_update_branch(repo, repo_url, commit_message, base, branch):
+    # Set the default return values
+    action = "none"
+    diff = False
+
+    # Get the working base. This may or may not be the actual base.
+    working_base = repo.git.symbolic_ref("HEAD", "--short")
+    # If the base is not specified it is assumed to be the working base
+    if base is None:
+        base = working_base
+
+    # Save the working base changes to a temporary branch
+    temp_branch = cmn.get_random_string(length=20)
+    repo.git.checkout("HEAD", b=temp_branch)
+    # Commit any uncomitted changes
+    if repo.is_dirty(untracked_files=True):
+        print(f"Uncommitted changes found. Adding a commit.")
+        repo.git.add("-A")
+        repo.git.commit(m=commit_message)
+
+    # Perform fetch and reset the working base
+    # Commits made during the workflow will be removed
+    repo.git.fetch("--force", repo_url, f"{working_base}:{working_base}")
+
+    # If the working base is not the base, rebase the temp branch commits
+    if working_base != base:
+        print(
+            f"Rebasing commits made to branch '{working_base}' on to base branch '{base}'"
+        )
+        # Checkout the actual base
+        repo.git.fetch("--force", repo_url, f"{base}:{base}")
+        repo.git.checkout(base)
+        # Cherrypick commits from the temporary branch starting from the working base
+        commits = repo.git.rev_list("--reverse", f"{working_base}..{temp_branch}", ".")
+        for commit in commits.splitlines():
+            try:
+                repo.git.cherry_pick(
+                    "--strategy",
+                    "recursive",
+                    "--strategy-option",
+                    "theirs",
+                    f"{commit}",
+                )
+            except GitCommandError as e:
+                if CHERRYPICK_EMPTY not in e.stderr:
+                    print("Unexpected error: ", e)
+                    raise
+        # Reset the temp branch to the working index
+        repo.git.checkout("-B", temp_branch, "HEAD")
+        # Reset the base
+        repo.git.fetch("--force", repo_url, f"{base}:{base}")
+
+    # Try to fetch the pull request branch
+    if not fetch_successful(repo, repo_url, branch):
+        # The pull request branch does not exist
+        print(f"Pull request branch '{branch}' does not exist yet")
+        # Create the pull request branch
+        repo.git.checkout("HEAD", b=branch)
+        # Check if the pull request branch is ahead of the base
+        diff = is_ahead(repo, base, branch)
+        if diff:
+            action = "created"
+            print(f"Created branch '{branch}'")
+        else:
+            print(
+                f"Branch '{branch}' is not ahead of base '{base}' and will not be created"
+            )
+    else:
+        # The pull request branch exists
+        print(
+            f"Pull request branch '{branch}' already exists as remote branch 'origin/{branch}'"
+        )
+        # Checkout the pull request branch
+        repo.git.checkout(branch)
+
+        if has_diff(repo, branch, temp_branch):
+            # If the branch differs from the recreated temp version then the branch is reset
+            # For changes on base this action is similar to a rebase of the pull request branch
+            print(f"Resetting '{branch}'")
+            repo.git.checkout("-B", branch, temp_branch)
+            # repo.git.switch("-C", branch, temp_branch)
+
+        # Check if the pull request branch has been updated
+        # If the branch was reset or updated it will be ahead
+        # It may be behind if a reset now results in no diff with the base
+        if not is_even(repo, f"origin/{branch}", branch):
+            action = "updated"
+            print(f"Updated branch '{branch}'")
+        else:
+            print(f"Branch '{branch}' is even with its remote and will not be updated")
+
+        # Check if the pull request branch is ahead of the base
+        diff = is_ahead(repo, base, branch)
+
+    # Delete the temporary branch
+    repo.git.branch("--delete", "--force", temp_branch)
+
+    return {"action": action, "diff": diff, "base": base}
diff --git a/dist/cpr/cpr/create_or_update_pull_request.py b/dist/cpr/cpr/create_or_update_pull_request.py
new file mode 100644
index 0000000..a34b090
--- /dev/null
+++ b/dist/cpr/cpr/create_or_update_pull_request.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+""" Create or Update Pull Request """
+from github import Github, GithubException
+import os
+
+
+def string_to_bool(str):
+    if str is None:
+        return False
+    else:
+        return str.lower() in [
+            "true",
+            "1",
+            "t",
+            "y",
+            "yes",
+            "on",
+        ]
+
+
+def cs_string_to_list(str):
+    # Split the comma separated string into a list
+    l = [i.strip() for i in str.split(",")]
+    # Remove empty strings
+    return list(filter(None, l))
+
+
+def create_project_card(github_repo, project_name, project_column_name, pull_request):
+    # Locate the project by name
+    project = None
+    for project_item in github_repo.get_projects("all"):
+        if project_item.name == project_name:
+            project = project_item
+            break
+
+    if not project:
+        print("::error::Project not found. Unable to create project card.")
+        return
+
+    # Locate the column by name
+    column = None
+    for column_item in project.get_columns():
+        if column_item.name == project_column_name:
+            column = column_item
+            break
+
+    if not column:
+        print("::error::Project column not found. Unable to create project card.")
+        return
+
+    # Create a project card for the pull request
+    column.create_card(content_id=pull_request.id, content_type="PullRequest")
+    print(
+        "Added pull request #%d to project '%s' under column '%s'"
+        % (pull_request.number, project.name, column.name)
+    )
+
+
+def create_or_update_pull_request(
+    github_token,
+    github_repository,
+    branch,
+    base,
+    title,
+    body,
+    labels,
+    assignees,
+    milestone,
+    reviewers,
+    team_reviewers,
+    project_name,
+    project_column_name,
+    draft,
+    request_to_parent,
+):
+    github_repo = head_repo = Github(github_token).get_repo(github_repository)
+    if string_to_bool(request_to_parent):
+        github_repo = github_repo.parent
+        if github_repo is None:
+            raise ValueError(
+                "The checked out repository is not a fork. Input 'request-to-parent' should be set to false."
+            )
+
+    head_branch = f"{head_repo.owner.login}:{branch}"
+
+    # Create the pull request
+    try:
+        pull_request = github_repo.create_pull(
+            title=title,
+            body=body,
+            base=base,
+            head=head_branch,
+            draft=string_to_bool(draft),
+        )
+        print(
+            f"Created pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})"
+        )
+    except GithubException as e:
+        if e.status == 422:
+            # A pull request exists for this branch and base
+            # Get the pull request
+            pull_request = github_repo.get_pulls(
+                state="open", base=base, head=head_branch
+            )[0]
+            # Update title and body
+            pull_request.as_issue().edit(title=title, body=body)
+            print(
+                f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})"
+            )
+        else:
+            print(str(e))
+            raise
+
+    # Set the output variables
+    os.system(f"echo ::set-env name=PULL_REQUEST_NUMBER::{pull_request.number}")
+    os.system(f"echo ::set-output name=pull-request-number::{pull_request.number}")
+    # 'pr_number' is deprecated
+    os.system(f"echo ::set-output name=pr_number::{pull_request.number}")
+
+    # Set labels, assignees and milestone
+    if labels is not None:
+        print(f"Applying labels '{labels}'")
+        pull_request.as_issue().edit(labels=cs_string_to_list(labels))
+    if assignees is not None:
+        print(f"Applying assignees '{assignees}'")
+        pull_request.as_issue().edit(assignees=cs_string_to_list(assignees))
+    if milestone is not None:
+        print(f"Applying milestone '{milestone}'")
+        milestone = github_repo.get_milestone(int(milestone))
+        pull_request.as_issue().edit(milestone=milestone)
+
+    # Set pull request reviewers
+    if reviewers is not None:
+        print(f"Requesting reviewers '{reviewers}'")
+        try:
+            pull_request.create_review_request(reviewers=cs_string_to_list(reviewers))
+        except GithubException as e:
+            # Likely caused by "Review cannot be requested from pull request author."
+            if e.status == 422:
+                print("Request reviewers failed - {}".format(e.data["message"]))
+
+    # Set pull request team reviewers
+    if team_reviewers is not None:
+        print(f"Requesting team reviewers '{team_reviewers}'")
+        pull_request.create_review_request(
+            team_reviewers=cs_string_to_list(team_reviewers)
+        )
+
+    # Create a project card for the pull request
+    if project_name is not None and project_column_name is not None:
+        try:
+            create_project_card(
+                github_repo, project_name, project_column_name, pull_request
+            )
+        except GithubException as e:
+            # Likely caused by "Project already has the associated issue."
+            if e.status == 422:
+                print(
+                    "Create project card failed - {}".format(
+                        e.data["errors"][0]["message"]
+                    )
+                )
diff --git a/dist/cpr/cpr/create_pull_request.py b/dist/cpr/cpr/create_pull_request.py
new file mode 100644
index 0000000..ead7f02
--- /dev/null
+++ b/dist/cpr/cpr/create_pull_request.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python3
+""" Create Pull Request """
+import base64
+import common as cmn
+import create_or_update_branch as coub
+import create_or_update_pull_request as coupr
+from git import Repo, GitCommandError
+import json
+import os
+import sys
+import time
+
+
+# Default the committer and author to the GitHub Actions bot
+DEFAULT_COMMITTER = "GitHub <noreply@github.com>"
+DEFAULT_AUTHOR = (
+    "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
+)
+DEFAULT_COMMIT_MESSAGE = "[create-pull-request] automated change"
+DEFAULT_TITLE = "Changes by create-pull-request action"
+DEFAULT_BODY = (
+    "Automated changes by "
+    + "[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action"
+)
+DEFAULT_BRANCH = "create-pull-request/patch"
+
+
+def get_git_config_value(repo, name):
+    try:
+        return repo.git.config("--get", name)
+    except GitCommandError:
+        return None
+
+
+def get_repository_detail(repo):
+    remote_origin_url = get_git_config_value(repo, "remote.origin.url")
+    if remote_origin_url is None:
+        raise ValueError("Failed to fetch 'remote.origin.url' from git config")
+    protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
+    return remote_origin_url, protocol, github_repository
+
+
+def git_user_config_is_set(repo):
+    name = get_git_config_value(repo, "user.name")
+    email = get_git_config_value(repo, "user.email")
+
+    if name is not None and email is not None:
+        print(f"Git user already configured as '{name} <{email}>'")
+        return True
+
+    committer_name = get_git_config_value(repo, "committer.name")
+    committer_email = get_git_config_value(repo, "committer.email")
+    author_name = get_git_config_value(repo, "author.name")
+    author_email = get_git_config_value(repo, "author.email")
+
+    if (
+        committer_name is not None
+        and committer_email is not None
+        and author_name is not None
+        and author_email is not None
+    ):
+        print(
+            f"Git committer already configured as '{committer_name} <{committer_email}>'"
+        )
+        print(f"Git author already configured as '{author_name} <{author_email}>'")
+        return True
+
+    return False
+
+
+def set_committer_author(repo, committer, author):
+    # If either committer or author is supplied they will be cross used
+    if committer is None and author is not None:
+        print("Supplied author will also be used as the committer.")
+        committer = author
+    if author is None and committer is not None:
+        print("Supplied committer will also be used as the author.")
+        author = committer
+
+    # If no committer/author has been supplied but user configuration already
+    # exists in git config we can exit and use the existing config as-is.
+    if committer is None and author is None:
+        if git_user_config_is_set(repo):
+            return
+
+    # Set defaults if no committer/author has been supplied
+    if committer is None and author is None:
+        committer = DEFAULT_COMMITTER
+        author = DEFAULT_AUTHOR
+
+    # Set git environment. This will not persist after the action completes.
+    committer_name, committer_email = cmn.parse_display_name_email(committer)
+    author_name, author_email = cmn.parse_display_name_email(author)
+    repo.git.update_environment(
+        GIT_COMMITTER_NAME=committer_name,
+        GIT_COMMITTER_EMAIL=committer_email,
+        GIT_AUTHOR_NAME=author_name,
+        GIT_AUTHOR_EMAIL=author_email,
+    )
+    print(f"Configured git committer as '{committer_name} <{committer_email}>'")
+    print(f"Configured git author as '{author_name} <{author_email}>'")
+
+
+# Get required environment variables
+github_token = os.environ["GITHUB_TOKEN"]
+# Get environment variables with defaults
+path = os.getenv("CPR_PATH", os.getcwd())
+branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
+commit_message = os.getenv("CPR_COMMIT_MESSAGE", DEFAULT_COMMIT_MESSAGE)
+# Get environment variables with a default of 'None'
+committer = os.environ.get("CPR_COMMITTER")
+author = os.environ.get("CPR_AUTHOR")
+base = os.environ.get("CPR_BASE")
+
+# Set the repo path
+repo = Repo(path)
+
+# Determine the GitHub repository from git config
+# This will be the target repository for the pull request
+repo_url, protocol, github_repository = get_repository_detail(repo)
+print(f"Target repository set to {github_repository}")
+
+if protocol == "HTTPS":
+    print(f"::debug::Using HTTPS protocol")
+    # Encode and configure the basic credential for HTTPS access
+    basic_credential = base64.b64encode(
+        f"x-access-token:{github_token}".encode("utf-8")
+    ).decode("utf-8")
+    # Mask the basic credential in logs and debug output
+    print(f"::add-mask::{basic_credential}")
+    repo.git.set_persistent_git_options(
+        c=f"http.https://github.com/.extraheader=AUTHORIZATION: basic {basic_credential}"
+    )
+
+# Determine if the checked out ref is a valid base for a pull request
+# The action needs the checked out HEAD ref to be a branch
+# This check will fail in the following cases:
+# - HEAD is detached
+# - HEAD is a merge commit (pull_request events)
+# - HEAD is a tag
+try:
+    working_base = repo.git.symbolic_ref("HEAD", "--short")
+except GitCommandError as e:
+    print(f"::debug::{e.stderr}")
+    print(
+        f"::error::The checked out ref is not a valid base for a pull request. "
+        + "Unable to continue. Exiting."
+    )
+    sys.exit(1)
+
+# Exit if the working base is a PR branch created by this action.
+# This may occur when using a PAT instead of GITHUB_TOKEN because
+# a PAT allows workflow actions to trigger further events.
+if working_base.startswith(branch):
+    print(
+        f"::error::Working base branch '{working_base}' was created by this action. "
+        + "Unable to continue. Exiting."
+    )
+    sys.exit(1)
+
+# Fetch an optional environment variable to determine the branch suffix
+branch_suffix = os.environ.get("CPR_BRANCH_SUFFIX")
+if branch_suffix is not None:
+    if branch_suffix == "short-commit-hash":
+        # Suffix with the short SHA1 hash
+        branch = "{}-{}".format(branch, repo.git.rev_parse("--short", "HEAD"))
+    elif branch_suffix == "timestamp":
+        # Suffix with the current timestamp
+        branch = "{}-{}".format(branch, int(time.time()))
+    elif branch_suffix == "random":
+        # Suffix with a 7 character random string
+        branch = "{}-{}".format(branch, cmn.get_random_string())
+    else:
+        print(
+            f"::error::Branch suffix '{branch_suffix}' is not a valid value. "
+            + "Unable to continue. Exiting."
+        )
+        sys.exit(1)
+
+# Output head branch
+print(f"Pull request branch to create or update set to '{branch}'")
+
+# Set the committer and author
+try:
+    set_committer_author(repo, committer, author)
+except ValueError as e:
+    print(f"::error::{e} " + "Unable to continue. Exiting.")
+    sys.exit(1)
+
+# Create or update the pull request branch
+result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
+
+if result["action"] in ["created", "updated"]:
+    # The branch was created or updated
+    print(f"Pushing pull request branch to 'origin/{branch}'")
+    repo.git.push("--force", repo_url, f"HEAD:refs/heads/{branch}")
+
+    # Set the base. It would have been 'None' if not specified as an input
+    base = result["base"]
+
+    # If there is no longer a diff with the base delete the branch and exit
+    if not result["diff"]:
+        print(f"Branch '{branch}' no longer differs from base branch '{base}'")
+        print(f"Closing pull request and deleting branch '{branch}'")
+        repo.git.push("--delete", "--force", repo_url, f"refs/heads/{branch}")
+        sys.exit()
+
+    # Fetch optional environment variables with default values
+    title = os.getenv("CPR_TITLE", DEFAULT_TITLE)
+    body = os.getenv("CPR_BODY", DEFAULT_BODY)
+
+    # Create or update the pull request
+    coupr.create_or_update_pull_request(
+        github_token,
+        github_repository,
+        branch,
+        base,
+        title,
+        body,
+        os.environ.get("CPR_LABELS"),
+        os.environ.get("CPR_ASSIGNEES"),
+        os.environ.get("CPR_MILESTONE"),
+        os.environ.get("CPR_REVIEWERS"),
+        os.environ.get("CPR_TEAM_REVIEWERS"),
+        os.environ.get("CPR_PROJECT_NAME"),
+        os.environ.get("CPR_PROJECT_COLUMN_NAME"),
+        os.environ.get("CPR_DRAFT"),
+        os.environ.get("CPR_REQUEST_TO_PARENT"),
+    )
diff --git a/dist/cpr/cpr/requirements.txt b/dist/cpr/cpr/requirements.txt
new file mode 100644
index 0000000..7baf745
--- /dev/null
+++ b/dist/cpr/cpr/requirements.txt
@@ -0,0 +1,4 @@
+setuptools==46.2.0
+wheel==0.34.2
+GitPython==3.1.2
+PyGithub==1.51
diff --git a/dist/cpr/cpr/test_common.py b/dist/cpr/cpr/test_common.py
new file mode 100644
index 0000000..bd8419a
--- /dev/null
+++ b/dist/cpr/cpr/test_common.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+""" Test Common """
+import common as cmn
+import pytest
+
+
+def test_get_random_string():
+    assert len(cmn.get_random_string()) == 7
+    assert len(cmn.get_random_string(length=20)) == 20
+
+
+def test_parse_github_repository_success():
+    protocol, repository = cmn.parse_github_repository(
+        "https://github.com/peter-evans/create-pull-request"
+    )
+    assert protocol == "HTTPS"
+    assert repository == "peter-evans/create-pull-request"
+
+    protocol, repository = cmn.parse_github_repository(
+        "git@github.com:peter-evans/create-pull-request.git"
+    )
+    assert protocol == "SSH"
+    assert repository == "peter-evans/create-pull-request"
+
+
+def test_parse_github_repository_failure():
+    url = "https://github.com/peter-evans"
+    with pytest.raises(ValueError) as e_info:
+        cmn.parse_github_repository(url)
+    assert (
+        e_info.value.args[0]
+        == f"The format of '{url}' is not a valid GitHub repository URL"
+    )
+
+
+def test_parse_display_name_email_success():
+    name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
+    assert name == "abc def"
+    assert email == "abc@def.com"
+
+    name, email = cmn.parse_display_name_email(
+        "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
+    )
+    assert name == "github-actions[bot]"
+    assert email == "41898282+github-actions[bot]@users.noreply.github.com"
+
+
+def test_parse_display_name_email_failure():
+    display_name_email = "abc@def.com"
+    with pytest.raises(ValueError) as e_info:
+        cmn.parse_display_name_email(display_name_email)
+    assert (
+        e_info.value.args[0]
+        == f"The format of '{display_name_email}' is not a valid email address with display name"
+    )
+
+    display_name_email = " < >"
+    with pytest.raises(ValueError) as e_info:
+        cmn.parse_display_name_email(display_name_email)
+    assert (
+        e_info.value.args[0]
+        == f"The format of '{display_name_email}' is not a valid email address with display name"
+    )
diff --git a/dist/cpr/cpr/test_create_or_update_branch.py b/dist/cpr/cpr/test_create_or_update_branch.py
new file mode 100644
index 0000000..a0f400c
--- /dev/null
+++ b/dist/cpr/cpr/test_create_or_update_branch.py
@@ -0,0 +1,757 @@
+#!/usr/bin/env python3
+""" Test Create or Update Branch """
+import create_or_update_branch as coub
+from git import Repo
+import os
+import pytest
+import sys
+import time
+
+
+# Set git repo
+REPO_PATH = os.getenv("COUB_REPO_PATH", os.getcwd())
+repo = Repo(REPO_PATH)
+
+# Set git environment
+author_name = "github-actions[bot]"
+author_email = "41898282+github-actions[bot]@users.noreply.github.com"
+committer_name = "GitHub"
+committer_email = "noreply@github.com"
+repo.git.update_environment(
+    GIT_AUTHOR_NAME=author_name,
+    GIT_AUTHOR_EMAIL=author_email,
+    GIT_COMMITTER_NAME=committer_name,
+    GIT_COMMITTER_EMAIL=committer_email,
+)
+
+REPO_URL = repo.git.config("--get", "remote.origin.url")
+
+TRACKED_FILE = "tracked-file.txt"
+UNTRACKED_FILE = "untracked-file.txt"
+
+DEFAULT_BRANCH = "tests/master"
+NOT_BASE_BRANCH = "tests/branch-that-is-not-the-base"
+NOT_EXIST_BRANCH = "tests/branch-that-does-not-exist"
+
+COMMIT_MESSAGE = "[create-pull-request] automated change"
+BRANCH = "tests/create-pull-request/patch"
+BASE = DEFAULT_BRANCH
+
+
+def create_tracked_change(content=None):
+    if content is None:
+        content = str(time.time())
+    # Create a tracked file change
+    with open(os.path.join(REPO_PATH, TRACKED_FILE), "w") as f:
+        f.write(content)
+    return content
+
+
+def create_untracked_change(content=None):
+    if content is None:
+        content = str(time.time())
+    # Create an untracked file change
+    with open(os.path.join(REPO_PATH, UNTRACKED_FILE), "w") as f:
+        f.write(content)
+    return content
+
+
+def get_tracked_content():
+    # Read the content of the tracked file
+    with open(os.path.join(REPO_PATH, TRACKED_FILE), "r") as f:
+        return f.read()
+
+
+def get_untracked_content():
+    # Read the content of the untracked file
+    with open(os.path.join(REPO_PATH, UNTRACKED_FILE), "r") as f:
+        return f.read()
+
+
+def create_changes(tracked_content=None, untracked_content=None):
+    tracked_content = create_tracked_change(tracked_content)
+    untracked_content = create_untracked_change(untracked_content)
+    return tracked_content, untracked_content
+
+
+def create_commits(number=2, final_tracked_content=None, final_untracked_content=None):
+    for i in range(number):
+        commit_number = i + 1
+        if commit_number == number:
+            tracked_content, untracked_content = create_changes(
+                final_tracked_content, final_untracked_content
+            )
+        else:
+            tracked_content, untracked_content = create_changes()
+        repo.git.add("-A")
+        repo.git.commit(m=f"Commit {commit_number}")
+    return tracked_content, untracked_content
+
+
+@pytest.fixture(scope="module", autouse=True)
+def before_after_all():
+    print("Before all tests")
+    # Check there are no local changes that might be
+    # destroyed by running these tests
+    assert not repo.is_dirty(untracked_files=True)
+
+    # Create a new default branch for the test run
+    repo.remotes.origin.fetch()
+    repo.git.checkout("master")
+    repo.git.checkout("HEAD", b=NOT_BASE_BRANCH)
+    create_tracked_change()
+    repo.git.add("-A")
+    repo.git.commit(m="This commit should not appear in pr branches")
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{NOT_BASE_BRANCH}")
+    # Create a new default branch for the test run
+    repo.git.checkout("master")
+    repo.git.checkout("HEAD", b=DEFAULT_BRANCH)
+    create_tracked_change()
+    repo.git.add("-A")
+    repo.git.commit(m="Add file to be a tracked file for tests")
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+
+    yield
+
+    print("After all tests")
+    repo.git.checkout("master")
+    # Delete the "not base branch" created for the test run
+    repo.git.branch("--delete", "--force", NOT_BASE_BRANCH)
+    repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{NOT_BASE_BRANCH}")
+    # Delete the default branch created for the test run
+    repo.git.branch("--delete", "--force", DEFAULT_BRANCH)
+    repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{DEFAULT_BRANCH}")
+
+
+def before_test():
+    print("Before test")
+    # Checkout the default branch
+    repo.git.checkout(DEFAULT_BRANCH)
+
+
+def after_test(delete_remote=True):
+    print("After test")
+    # Output git log
+    print(repo.git.log("-5", pretty="oneline"))
+    # Delete the pull request branch if it exists
+    repo.git.checkout(DEFAULT_BRANCH)
+    print(f"Deleting {BRANCH}")
+    for branch in repo.branches:
+        if branch.name == BRANCH:
+            repo.git.branch("--delete", "--force", BRANCH)
+            break
+    if delete_remote:
+        print(f"Deleting origin/{BRANCH}")
+        for ref in repo.remotes.origin.refs:
+            if ref.name == f"origin/{BRANCH}":
+                repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{BRANCH}")
+                repo.remotes.origin.fetch("--prune")
+                break
+
+
+@pytest.fixture(autouse=True)
+def before_after_tests():
+    before_test()
+    yield
+    after_test()
+
+
+# Tests if a branch exists and can be fetched
+def coub_fetch_successful():
+    assert coub.fetch_successful(repo, REPO_URL, NOT_BASE_BRANCH)
+    assert not coub.fetch_successful(repo, REPO_URL, NOT_EXIST_BRANCH)
+
+
+# Tests no changes resulting in no new branch being created
+def coub_no_changes_on_create():
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "none"
+
+
+# Tests create and update with a tracked file change
+def coub_tracked_changes():
+    # Create a tracked file change
+    tracked_content = create_tracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create a tracked file change
+    tracked_content = create_tracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+
+
+# Tests create and update with an untracked file change
+def coub_untracked_changes():
+    # Create an untracked file change
+    untracked_content = create_untracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create an untracked file change
+    untracked_content = create_untracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_untracked_content() == untracked_content
+
+
+# Tests create and update with identical changes
+# The pull request branch will not be updated
+def coub_identical_changes():
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create identical tracked and untracked file changes
+    create_changes(tracked_content, untracked_content)
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "none"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Tests create and update with commits on the base inbetween
+def coub_commits_on_base():
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the base
+    create_commits()
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+    repo.remotes.origin.fetch()
+
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Tests create and then an update with no changes
+# This effectively reverts the branch back to match the base and results in no diff
+def coub_changes_no_diff():
+    # Save the default branch tracked content
+    default_tracked_content = get_tracked_content()
+
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Running with no update effectively reverts the branch back to match the base
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"] == False
+    assert get_tracked_content() == default_tracked_content
+
+
+# Tests create and update with commits on the base inbetween
+# The changes on base effectively revert the branch back to match the base and results in no diff
+def coub_commits_on_base_no_diff():
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the base
+    tracked_content, untracked_content = create_commits()
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+    repo.remotes.origin.fetch()
+
+    # Create the same tracked and untracked file changes that were made to the base
+    create_changes(tracked_content, untracked_content)
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"] == False
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Tests create and update with commits on the working base (during the workflow)
+def coub_commits_on_working_base():
+    # Create commits on the working base
+    tracked_content, untracked_content = create_commits()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the working base
+    tracked_content, untracked_content = create_commits()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Tests create and update with changes and commits on the working base (during the workflow)
+def coub_changes_and_commits_on_working_base():
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Tests create and update with changes and commits on the working base (during the workflow)
+# with commits on the base inbetween
+def coub_changes_and_commits_on_base_and_working_base():
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the base
+    create_commits()
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+    repo.remotes.origin.fetch()
+
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests no changes resulting in no new branch being created
+def coub_wbnb_no_changes_on_create():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "none"
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with a tracked file change
+def coub_wbnb_tracked_changes():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create a tracked file change
+    tracked_content = create_tracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create a tracked file change
+    tracked_content = create_tracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with an untracked file change
+def coub_wbnb_untracked_changes():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create an untracked file change
+    untracked_content = create_untracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create an untracked file change
+    untracked_content = create_untracked_change()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with identical changes
+# The pull request branch will not be updated
+def coub_wbnb_identical_changes():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create identical tracked and untracked file changes
+    create_changes(tracked_content, untracked_content)
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "none"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with commits on the base inbetween
+def coub_wbnb_commits_on_base():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the base
+    create_commits()
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+    repo.remotes.origin.fetch()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and then an update with no changes
+# This effectively reverts the branch back to match the base and results in no diff
+def coub_wbnb_changes_no_diff():
+    # Save the default branch tracked content
+    default_tracked_content = get_tracked_content()
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Running with no update effectively reverts the branch back to match the base
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"] == False
+    assert get_tracked_content() == default_tracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with commits on the base inbetween
+# The changes on base effectively revert the branch back to match the base and results in no diff
+# This scenario will cause cherrypick to fail due to an empty commit.
+# The commit is empty because the changes now exist on the base.
+def coub_wbnb_commits_on_base_no_diff():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the base
+    tracked_content, untracked_content = create_commits()
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+    repo.remotes.origin.fetch()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create the same tracked and untracked file changes that were made to the base
+    create_changes(tracked_content, untracked_content)
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"] == False
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with commits on the working base (during the workflow)
+def coub_wbnb_commits_on_working_base():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create commits on the working base
+    tracked_content, untracked_content = create_commits()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create commits on the working base
+    tracked_content, untracked_content = create_commits()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with changes and commits on the working base (during the workflow)
+def coub_wbnb_changes_and_commits_on_working_base():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# Working Base is Not Base (WBNB)
+# Tests create and update with changes and commits on the working base (during the workflow)
+# with commits on the base inbetween
+def coub_wbnb_changes_and_commits_on_base_and_working_base():
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "created"
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+    # Push pull request branch to remote
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
+    repo.remotes.origin.fetch()
+
+    after_test(delete_remote=False)
+    before_test()
+
+    # Create commits on the base
+    create_commits()
+    repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
+    repo.remotes.origin.fetch()
+
+    # Set the working base to a branch that is not the pull request base
+    repo.git.checkout(NOT_BASE_BRANCH)
+    # Create commits on the working base
+    create_commits()
+    # Create tracked and untracked file changes
+    tracked_content, untracked_content = create_changes()
+    result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
+    assert result["action"] == "updated"
+    assert result["diff"]
+    assert get_tracked_content() == tracked_content
+    assert get_untracked_content() == untracked_content
+
+
+# pytest -v -s ~/git/create-pull-request/src
+
+test_coub_fetch_successful = coub_fetch_successful
+
+test_coub_no_changes_on_create = coub_no_changes_on_create
+test_coub_tracked_changes = coub_tracked_changes
+test_coub_untracked_changes = coub_untracked_changes
+test_coub_identical_changes = coub_identical_changes
+test_coub_commits_on_base = coub_commits_on_base
+
+test_coub_changes_no_diff = coub_changes_no_diff
+test_coub_commits_on_base_no_diff = coub_commits_on_base_no_diff
+
+test_coub_commits_on_working_base = coub_commits_on_working_base
+test_coub_changes_and_commits_on_working_base = coub_changes_and_commits_on_working_base
+test_coub_changes_and_commits_on_base_and_working_base = (
+    coub_changes_and_commits_on_base_and_working_base
+)
+
+# WBNB
+test_coub_wbnb_no_changes_on_create = coub_wbnb_no_changes_on_create
+test_coub_wbnb_tracked_changes = coub_wbnb_tracked_changes
+test_coub_wbnb_untracked_changes = coub_wbnb_untracked_changes
+test_coub_wbnb_identical_changes = coub_wbnb_identical_changes
+test_coub_wbnb_commits_on_base = coub_wbnb_commits_on_base
+
+test_coub_wbnb_changes_no_diff = coub_wbnb_changes_no_diff
+test_coub_wbnb_commits_on_base_no_diff = coub_wbnb_commits_on_base_no_diff
+
+test_coub_wbnb_commits_on_working_base = coub_wbnb_commits_on_working_base
+test_coub_wbnb_changes_and_commits_on_working_base = (
+    coub_wbnb_changes_and_commits_on_working_base
+)
+test_coub_wbnb_changes_and_commits_on_base_and_working_base = (
+    coub_wbnb_changes_and_commits_on_base_and_working_base
+)
diff --git a/dist/index.js b/dist/index.js
index 2a70501..f4223ce 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -34,7 +34,7 @@ module.exports =
 /******/ 	// the startup function
 /******/ 	function startup() {
 /******/ 		// Load entry module and return exports
-/******/ 		return __webpack_require__(676);
+/******/ 		return __webpack_require__(198);
 /******/ 	};
 /******/
 /******/ 	// run startup
@@ -952,6 +952,58 @@ class ExecState extends events.EventEmitter {
 
 module.exports = require("tls");
 
+/***/ }),
+
+/***/ 57:
+/***/ (function(__unusedmodule, exports, __webpack_require__) {
+
+"use strict";
+
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.isDocker = void 0;
+const fs = __importStar(__webpack_require__(747));
+function hasDockerEnv() {
+    try {
+        fs.statSync('/.dockerenv');
+        return true;
+    }
+    catch (_) {
+        return false;
+    }
+}
+function hasDockerCGroup() {
+    try {
+        return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
+    }
+    catch (_) {
+        return false;
+    }
+}
+function isDocker() {
+    return hasDockerEnv() || hasDockerCGroup();
+}
+exports.isDocker = isDocker;
+
+
 /***/ }),
 
 /***/ 87:
@@ -959,65 +1011,6 @@ module.exports = require("tls");
 
 module.exports = require("os");
 
-/***/ }),
-
-/***/ 104:
-/***/ (function(module, __unusedexports, __webpack_require__) {
-
-const core = __webpack_require__(470);
-const tc = __webpack_require__(533);
-const path = __webpack_require__(622);
-const semver = __webpack_require__(280);
-
-/**
- * Setup for Python from the GitHub Actions tool cache
- * Converted from https://github.com/actions/setup-python
- *
- * @param {string} versionSpec version of Python
- * @param {string} arch architecture (x64|x32)
- */
-let setupPython = function(versionSpec, arch) {
-  return new Promise((resolve, reject) => {
-    const IS_WINDOWS = process.platform === "win32";
-
-    // Find the version of Python we want in the tool cache
-    const installDir = tc.find("Python", versionSpec, arch);
-    core.debug(`installDir: ${installDir}`);
-
-    // Set paths
-    core.exportVariable("pythonLocation", installDir);
-    core.addPath(installDir);
-    if (IS_WINDOWS) {
-      core.addPath(path.join(installDir, "Scripts"));
-    } else {
-      core.addPath(path.join(installDir, "bin"));
-    }
-
-    if (IS_WINDOWS) {
-      // Add --user directory
-      // `installDir` from tool cache should look like $AGENT_TOOLSDIRECTORY/Python/<semantic version>/x64/
-      // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
-      const version = path.basename(path.dirname(installDir));
-      const major = semver.major(version);
-      const minor = semver.minor(version);
-
-      const userScriptsDir = path.join(
-        process.env["APPDATA"] || "",
-        "Python",
-        `Python${major}${minor}`,
-        "Scripts"
-      );
-      core.addPath(userScriptsDir);
-    }
-    // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
-
-    resolve();
-  });
-};
-
-module.exports = setupPython;
-
-
 /***/ }),
 
 /***/ 129:
@@ -1314,39 +1307,165 @@ exports.debug = debug; // for test
 
 /***/ }),
 
-/***/ 160:
-/***/ (function(module, __unusedexports, __webpack_require__) {
+/***/ 198:
+/***/ (function(__unusedmodule, exports, __webpack_require__) {
 
 "use strict";
 
-const fs = __webpack_require__(747);
-
-let isDocker;
-
-function hasDockerEnv() {
-	try {
-		fs.statSync('/.dockerenv');
-		return true;
-	} catch (_) {
-		return false;
-	}
-}
-
-function hasDockerCGroup() {
-	try {
-		return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
-	} catch (_) {
-		return false;
-	}
-}
-
-module.exports = () => {
-	if (isDocker === undefined) {
-		isDocker = hasDockerEnv() || hasDockerCGroup();
-	}
-
-	return isDocker;
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
 };
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const core = __importStar(__webpack_require__(470));
+const exec = __importStar(__webpack_require__(986));
+const isDocker_1 = __webpack_require__(57);
+const setupPython_1 = __webpack_require__(953);
+const git_1 = __webpack_require__(453);
+const util_1 = __webpack_require__(669);
+const EXTRAHEADER_OPTION = 'http.https://github.com/.extraheader';
+const EXTRAHEADER_VALUE_REGEX = '^AUTHORIZATION:';
+function run() {
+    return __awaiter(this, void 0, void 0, function* () {
+        let repoPath;
+        let extraHeaderOption = new git_1.ConfigOption();
+        try {
+            // Python assets
+            const cpr = `${__dirname}/cpr`;
+            core.debug(`cpr: ${cpr}`);
+            // Determine how to access python and pip
+            const { pip, python } = (function () {
+                if (isDocker_1.isDocker()) {
+                    core.info('Running inside a Docker container');
+                    // Python 3 assumed to be installed and on the PATH
+                    return {
+                        pip: 'pip3',
+                        python: 'python3'
+                    };
+                }
+                else {
+                    // Setup Python from the tool cache
+                    setupPython_1.setupPython('3.x', 'x64');
+                    return {
+                        pip: 'pip',
+                        python: 'python'
+                    };
+                }
+            })();
+            // Install requirements
+            yield exec.exec(pip, [
+                'install',
+                '--requirement',
+                `${cpr}/requirements.txt`,
+                '--no-index',
+                `--find-links=${__dirname}/vendor`
+            ]);
+            // Fetch action inputs
+            const inputs = {
+                token: core.getInput('token'),
+                path: core.getInput('path'),
+                commitMessage: core.getInput('commit-message'),
+                committer: core.getInput('committer'),
+                author: core.getInput('author'),
+                title: core.getInput('title'),
+                body: core.getInput('body'),
+                labels: core.getInput('labels'),
+                assignees: core.getInput('assignees'),
+                reviewers: core.getInput('reviewers'),
+                teamReviewers: core.getInput('team-reviewers'),
+                milestone: core.getInput('milestone'),
+                project: core.getInput('project'),
+                projectColumn: core.getInput('project-column'),
+                draft: core.getInput('draft'),
+                branch: core.getInput('branch'),
+                requestToParent: core.getInput('request-to-parent'),
+                base: core.getInput('base'),
+                branchSuffix: core.getInput('branch-suffix')
+            };
+            core.debug(`Inputs: ${util_1.inspect(inputs)}`);
+            // Set environment variables from inputs.
+            if (inputs.token)
+                process.env.GITHUB_TOKEN = inputs.token;
+            if (inputs.path)
+                process.env.CPR_PATH = inputs.path;
+            if (inputs.commitMessage)
+                process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
+            if (inputs.committer)
+                process.env.CPR_COMMITTER = inputs.committer;
+            if (inputs.author)
+                process.env.CPR_AUTHOR = inputs.author;
+            if (inputs.title)
+                process.env.CPR_TITLE = inputs.title;
+            if (inputs.body)
+                process.env.CPR_BODY = inputs.body;
+            if (inputs.labels)
+                process.env.CPR_LABELS = inputs.labels;
+            if (inputs.assignees)
+                process.env.CPR_ASSIGNEES = inputs.assignees;
+            if (inputs.reviewers)
+                process.env.CPR_REVIEWERS = inputs.reviewers;
+            if (inputs.teamReviewers)
+                process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
+            if (inputs.milestone)
+                process.env.CPR_MILESTONE = inputs.milestone;
+            if (inputs.project)
+                process.env.CPR_PROJECT_NAME = inputs.project;
+            if (inputs.projectColumn)
+                process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
+            if (inputs.draft)
+                process.env.CPR_DRAFT = inputs.draft;
+            if (inputs.branch)
+                process.env.CPR_BRANCH = inputs.branch;
+            if (inputs.requestToParent)
+                process.env.CPR_REQUEST_TO_PARENT = inputs.requestToParent;
+            if (inputs.base)
+                process.env.CPR_BASE = inputs.base;
+            if (inputs.branchSuffix)
+                process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
+            // Get the repository path
+            repoPath = git_1.getRepoPath(inputs.path);
+            // Get the extraheader config option if it exists
+            extraHeaderOption = yield git_1.getAndUnsetConfigOption(repoPath, EXTRAHEADER_OPTION, EXTRAHEADER_VALUE_REGEX);
+            // Execute create pull request
+            yield exec.exec(python, [`${cpr}/create_pull_request.py`]);
+        }
+        catch (error) {
+            core.setFailed(error.message);
+        }
+        finally {
+            // Restore the extraheader config option
+            if (extraHeaderOption.value != '') {
+                if (yield git_1.addConfigOption(repoPath, EXTRAHEADER_OPTION, extraHeaderOption.value))
+                    core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);
+            }
+        }
+    });
+}
+run();
 
 
 /***/ }),
@@ -3080,6 +3199,138 @@ function escapeProperty(s) {
 }
 //# sourceMappingURL=command.js.map
 
+/***/ }),
+
+/***/ 453:
+/***/ (function(__unusedmodule, exports, __webpack_require__) {
+
+"use strict";
+
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.getAndUnsetConfigOption = exports.getConfigOption = exports.configOptionExists = exports.unsetConfigOption = exports.addConfigOption = exports.execGit = exports.getRepoPath = exports.ConfigOption = void 0;
+const core = __importStar(__webpack_require__(470));
+const exec = __importStar(__webpack_require__(986));
+const path = __importStar(__webpack_require__(622));
+class GitOutput {
+    constructor() {
+        this.stdout = '';
+        this.exitCode = 0;
+    }
+}
+class ConfigOption {
+    constructor() {
+        this.name = '';
+        this.value = '';
+    }
+}
+exports.ConfigOption = ConfigOption;
+function getRepoPath(relativePath) {
+    let githubWorkspacePath = process.env['GITHUB_WORKSPACE'];
+    if (!githubWorkspacePath) {
+        throw new Error('GITHUB_WORKSPACE not defined');
+    }
+    githubWorkspacePath = path.resolve(githubWorkspacePath);
+    core.debug(`githubWorkspacePath: ${githubWorkspacePath}`);
+    let repoPath = githubWorkspacePath;
+    if (relativePath)
+        repoPath = path.resolve(repoPath, relativePath);
+    core.debug(`repoPath: ${repoPath}`);
+    return repoPath;
+}
+exports.getRepoPath = getRepoPath;
+function execGit(repoPath, args, ignoreReturnCode = false) {
+    return __awaiter(this, void 0, void 0, function* () {
+        const result = new GitOutput();
+        const stdout = [];
+        const options = {
+            cwd: repoPath,
+            ignoreReturnCode: ignoreReturnCode,
+            listeners: {
+                stdout: (data) => {
+                    stdout.push(data.toString());
+                }
+            }
+        };
+        result.exitCode = yield exec.exec('git', args, options);
+        result.stdout = stdout.join('');
+        return result;
+    });
+}
+exports.execGit = execGit;
+function addConfigOption(repoPath, name, value) {
+    return __awaiter(this, void 0, void 0, function* () {
+        const result = yield execGit(repoPath, ['config', '--local', '--add', name, value], true);
+        return result.exitCode === 0;
+    });
+}
+exports.addConfigOption = addConfigOption;
+function unsetConfigOption(repoPath, name, valueRegex = '.') {
+    return __awaiter(this, void 0, void 0, function* () {
+        const result = yield execGit(repoPath, ['config', '--local', '--unset', name, valueRegex], true);
+        return result.exitCode === 0;
+    });
+}
+exports.unsetConfigOption = unsetConfigOption;
+function configOptionExists(repoPath, name, valueRegex = '.') {
+    return __awaiter(this, void 0, void 0, function* () {
+        const result = yield execGit(repoPath, ['config', '--local', '--name-only', '--get-regexp', name, valueRegex], true);
+        return result.exitCode === 0;
+    });
+}
+exports.configOptionExists = configOptionExists;
+function getConfigOption(repoPath, name, valueRegex = '.') {
+    return __awaiter(this, void 0, void 0, function* () {
+        const option = new ConfigOption();
+        const result = yield execGit(repoPath, ['config', '--local', '--get-regexp', name, valueRegex], true);
+        option.name = name;
+        option.value = result.stdout.trim().split(`${name} `)[1];
+        return option;
+    });
+}
+exports.getConfigOption = getConfigOption;
+function getAndUnsetConfigOption(repoPath, name, valueRegex = '.') {
+    return __awaiter(this, void 0, void 0, function* () {
+        if (yield configOptionExists(repoPath, name, valueRegex)) {
+            const option = yield getConfigOption(repoPath, name, valueRegex);
+            if (yield unsetConfigOption(repoPath, name, valueRegex)) {
+                core.debug(`Unset config option '${name}'`);
+                return option;
+            }
+        }
+        return new ConfigOption();
+    });
+}
+exports.getAndUnsetConfigOption = getAndUnsetConfigOption;
+
+
 /***/ }),
 
 /***/ 470:
@@ -4592,239 +4843,6 @@ function isUnixExecutable(stats) {
 }
 //# sourceMappingURL=io-util.js.map
 
-/***/ }),
-
-/***/ 676:
-/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
-
-const { inspect } = __webpack_require__(669);
-const isDocker = __webpack_require__(160);
-const core = __webpack_require__(470);
-const exec = __webpack_require__(986);
-const setupPython = __webpack_require__(104);
-const {
-  getRepoPath,
-  getAndUnsetConfigOption,
-  addConfigOption
-} = __webpack_require__(718);
-
-const EXTRAHEADER_OPTION = "http.https://github.com/.extraheader";
-const EXTRAHEADER_VALUE_REGEX = "^AUTHORIZATION:";
-
-async function run() {
-  try {
-    // Allows ncc to find assets to be included in the distribution
-    const cpr = __webpack_require__.ab + "cpr";
-    core.debug(`cpr: ${cpr}`);
-
-    // Determine how to access python and pip
-    const { pip, python } = (function() {
-      if (isDocker()) {
-        core.info("Running inside a Docker container");
-        // Python 3 assumed to be installed and on the PATH
-        return {
-          pip: "pip3",
-          python: "python3"
-        };
-      } else {
-        // Setup Python from the tool cache
-        setupPython("3.x", "x64");
-        return {
-          pip: "pip",
-          python: "python"
-        };
-      }
-    })();
-
-    // Install requirements
-    await exec.exec(pip, [
-      "install",
-      "--requirement",
-      `${cpr}/requirements.txt`,
-      "--no-index",
-      `--find-links=${__dirname}/vendor`
-    ]);
-
-    // Fetch action inputs
-    const inputs = {
-      token: core.getInput("token"),
-      path: core.getInput("path"),
-      commitMessage: core.getInput("commit-message"),
-      committer: core.getInput("committer"),
-      author: core.getInput("author"),
-      title: core.getInput("title"),
-      body: core.getInput("body"),
-      labels: core.getInput("labels"),
-      assignees: core.getInput("assignees"),
-      reviewers: core.getInput("reviewers"),
-      teamReviewers: core.getInput("team-reviewers"),
-      milestone: core.getInput("milestone"),
-      project: core.getInput("project"),
-      projectColumn: core.getInput("project-column"),
-      draft: core.getInput("draft"),
-      branch: core.getInput("branch"),
-      requestToParent: core.getInput("request-to-parent"),
-      base: core.getInput("base"),
-      branchSuffix: core.getInput("branch-suffix")
-    };
-    core.debug(`Inputs: ${inspect(inputs)}`);
-
-    // Set environment variables from inputs.
-    if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
-    if (inputs.path) process.env.CPR_PATH = inputs.path;
-    if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
-    if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
-    if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
-    if (inputs.title) process.env.CPR_TITLE = inputs.title;
-    if (inputs.body) process.env.CPR_BODY = inputs.body;
-    if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
-    if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
-    if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
-    if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
-    if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
-    if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
-    if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
-    if (inputs.draft) process.env.CPR_DRAFT = inputs.draft;
-    if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
-    if (inputs.requestToParent) process.env.CPR_REQUEST_TO_PARENT = inputs.requestToParent;
-    if (inputs.base) process.env.CPR_BASE = inputs.base;
-    if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
-
-    // Get the repository path
-    var repoPath = getRepoPath(inputs.path);
-    // Get the extraheader config option if it exists
-    var extraHeaderOption = await getAndUnsetConfigOption(
-      repoPath,
-      EXTRAHEADER_OPTION,
-      EXTRAHEADER_VALUE_REGEX
-    );
-
-    // Execute create pull request
-    await exec.exec(python, [`${cpr}/create_pull_request.py`]);
-  } catch (error) {
-    core.setFailed(error.message);
-  } finally {
-    // Restore the extraheader config option
-    if (extraHeaderOption) {
-      if (
-        await addConfigOption(
-          repoPath,
-          EXTRAHEADER_OPTION,
-          extraHeaderOption.value
-        )
-      )
-        core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);
-    }
-  }
-}
-
-run();
-
-
-/***/ }),
-
-/***/ 718:
-/***/ (function(module, __unusedexports, __webpack_require__) {
-
-const core = __webpack_require__(470);
-const exec = __webpack_require__(986);
-const path = __webpack_require__(622);
-
-function getRepoPath(relativePath) {
-  let githubWorkspacePath = process.env["GITHUB_WORKSPACE"];
-  if (!githubWorkspacePath) {
-    throw new Error("GITHUB_WORKSPACE not defined");
-  }
-  githubWorkspacePath = path.resolve(githubWorkspacePath);
-  core.debug(`githubWorkspacePath: ${githubWorkspacePath}`);
-
-  repoPath = githubWorkspacePath;
-  if (relativePath) repoPath = path.resolve(repoPath, relativePath);
-
-  core.debug(`repoPath: ${repoPath}`);
-  return repoPath;
-}
-
-async function execGit(repoPath, args, ignoreReturnCode = false) {
-  const stdout = [];
-  const options = {
-    cwd: repoPath,
-    ignoreReturnCode: ignoreReturnCode,
-    listeners: {
-      stdout: data => {
-        stdout.push(data.toString());
-      }
-    }
-  };
-
-  var result = {};
-  result.exitCode = await exec.exec("git", args, options);
-  result.stdout = stdout.join("");
-  return result;
-}
-
-async function addConfigOption(repoPath, name, value) {
-  const result = await execGit(
-    repoPath,
-    ["config", "--local", "--add", name, value],
-    true
-  );
-  return result.exitCode === 0;
-}
-
-async function unsetConfigOption(repoPath, name, valueRegex=".") {
-  const result = await execGit(
-    repoPath,
-    ["config", "--local", "--unset", name, valueRegex],
-    true
-  );
-  return result.exitCode === 0;
-}
-
-async function configOptionExists(repoPath, name, valueRegex=".") {
-  const result = await execGit(
-    repoPath,
-    ["config", "--local", "--name-only", "--get-regexp", name, valueRegex],
-    true
-  );
-  return result.exitCode === 0;
-}
-
-async function getConfigOption(repoPath, name, valueRegex=".") {
-  const result = await execGit(
-    repoPath,
-    ["config", "--local", "--get-regexp", name, valueRegex],
-    true
-  );
-  const option = result.stdout.trim().split(`${name} `);
-  return {
-    name: name,
-    value: option[1]
-  }
-}
-
-async function getAndUnsetConfigOption(repoPath, name, valueRegex=".") {
-  if (await configOptionExists(repoPath, name, valueRegex)) {
-    const option = await getConfigOption(repoPath, name, valueRegex);
-    if (await unsetConfigOption(repoPath, name, valueRegex)) {
-      core.debug(`Unset config option '${name}'`);
-      return option;
-    }
-  }
-  return null;
-}
-
-module.exports = {
-  getRepoPath,
-  execGit,
-  addConfigOption,
-  unsetConfigOption,
-  configOptionExists,
-  getConfigOption,
-  getAndUnsetConfigOption
-};
-
-
 /***/ }),
 
 /***/ 722:
@@ -4979,6 +4997,77 @@ function checkBypass(reqUrl) {
 exports.checkBypass = checkBypass;
 
 
+/***/ }),
+
+/***/ 953:
+/***/ (function(__unusedmodule, exports, __webpack_require__) {
+
+"use strict";
+
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.setupPython = void 0;
+const core = __importStar(__webpack_require__(470));
+const tc = __importStar(__webpack_require__(533));
+const path = __importStar(__webpack_require__(622));
+const semver = __importStar(__webpack_require__(280));
+/**
+ * Setup for Python from the GitHub Actions tool cache
+ * Converted from https://github.com/actions/setup-python
+ *
+ * @param {string} versionSpec version of Python
+ * @param {string} arch architecture (x64|x32)
+ */
+function setupPython(versionSpec, arch) {
+    return new Promise(resolve => {
+        const IS_WINDOWS = process.platform === 'win32';
+        // Find the version of Python we want in the tool cache
+        const installDir = tc.find('Python', versionSpec, arch);
+        core.debug(`installDir: ${installDir}`);
+        // Set paths
+        core.exportVariable('pythonLocation', installDir);
+        core.addPath(installDir);
+        if (IS_WINDOWS) {
+            core.addPath(path.join(installDir, 'Scripts'));
+        }
+        else {
+            core.addPath(path.join(installDir, 'bin'));
+        }
+        if (IS_WINDOWS) {
+            // Add --user directory
+            // `installDir` from tool cache should look like $AGENT_TOOLSDIRECTORY/Python/<semantic version>/x64/
+            // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
+            const version = path.basename(path.dirname(installDir));
+            const major = semver.major(version);
+            const minor = semver.minor(version);
+            const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts');
+            core.addPath(userScriptsDir);
+        }
+        // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
+        resolve();
+    });
+}
+exports.setupPython = setupPython;
+
+
 /***/ }),
 
 /***/ 979:
diff --git a/dist/vendor/Deprecated-1.2.10.tar.gz b/dist/vendor/Deprecated-1.2.10.tar.gz
new file mode 100644
index 0000000..77715a5
Binary files /dev/null and b/dist/vendor/Deprecated-1.2.10.tar.gz differ
diff --git a/report.txt b/report.txt
new file mode 100644
index 0000000..7b0787f
--- /dev/null
+++ b/report.txt
@@ -0,0 +1 @@
+1589701145