Add v2 alpha
This commit is contained in:
parent
ec6352b0a0
commit
b7565b81a7
|
@ -1,2 +1,3 @@
|
||||||
|
__pycache__
|
||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
14
action.yml
14
action.yml
|
@ -6,14 +6,10 @@ inputs:
|
||||||
required: true
|
required: true
|
||||||
commit-message:
|
commit-message:
|
||||||
description: 'The message to use when committing changes.'
|
description: 'The message to use when committing changes.'
|
||||||
author-name:
|
committer:
|
||||||
description: 'The name of the commit author.'
|
description: 'The committer name and email address.'
|
||||||
author-email:
|
author:
|
||||||
description: 'The email address of the commit author.'
|
description: 'The author name and email address.'
|
||||||
committer-name:
|
|
||||||
description: 'The name of the committer.'
|
|
||||||
committer-email:
|
|
||||||
description: 'The email address of the committer.'
|
|
||||||
title:
|
title:
|
||||||
description: 'The title of the pull request.'
|
description: 'The title of the pull request.'
|
||||||
body:
|
body:
|
||||||
|
@ -35,7 +31,7 @@ inputs:
|
||||||
branch:
|
branch:
|
||||||
description: 'The pull request branch name.'
|
description: 'The pull request branch name.'
|
||||||
base:
|
base:
|
||||||
description: 'Sets the pull request base branch.'
|
description: 'The pull request base branch.'
|
||||||
branch-suffix:
|
branch-suffix:
|
||||||
description: 'The branch suffix type.'
|
description: 'The branch suffix type.'
|
||||||
outputs:
|
outputs:
|
||||||
|
|
|
@ -978,10 +978,8 @@ async function run() {
|
||||||
const inputs = {
|
const inputs = {
|
||||||
token: core.getInput("token"),
|
token: core.getInput("token"),
|
||||||
commitMessage: core.getInput("commit-message"),
|
commitMessage: core.getInput("commit-message"),
|
||||||
commitAuthorName: core.getInput("author-name"),
|
committer: core.getInput("committer"),
|
||||||
commitAuthorEmail: core.getInput("author-email"),
|
author: core.getInput("author"),
|
||||||
committerName: core.getInput("committer-name"),
|
|
||||||
committerEmail: core.getInput("committer-email"),
|
|
||||||
title: core.getInput("title"),
|
title: core.getInput("title"),
|
||||||
body: core.getInput("body"),
|
body: core.getInput("body"),
|
||||||
labels: core.getInput("labels"),
|
labels: core.getInput("labels"),
|
||||||
|
@ -994,33 +992,29 @@ async function run() {
|
||||||
branch: core.getInput("branch"),
|
branch: core.getInput("branch"),
|
||||||
base: core.getInput("base"),
|
base: core.getInput("base"),
|
||||||
branchSuffix: core.getInput("branch-suffix"),
|
branchSuffix: core.getInput("branch-suffix"),
|
||||||
debugEvent: core.getInput("debug-event")
|
|
||||||
};
|
};
|
||||||
core.debug(`Inputs: ${inspect(inputs)}`);
|
core.debug(`Inputs: ${inspect(inputs)}`);
|
||||||
|
|
||||||
// Set environment variables from inputs.
|
// Set environment variables from inputs.
|
||||||
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
|
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
|
||||||
if (inputs.commitMessage) process.env.COMMIT_MESSAGE = inputs.commitMessage;
|
if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
|
||||||
if (inputs.commitAuthorName) process.env.COMMIT_AUTHOR_NAME = inputs.commitAuthorName;
|
if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
|
||||||
if (inputs.commitAuthorEmail) process.env.COMMIT_AUTHOR_EMAIL = inputs.commitAuthorEmail;
|
if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
|
||||||
if (inputs.committerName) process.env.COMMITTER_NAME = inputs.committerName;
|
if (inputs.title) process.env.CPR_TITLE = inputs.title;
|
||||||
if (inputs.committerEmail) process.env.COMMITTER_EMAIL = inputs.committerEmail;
|
if (inputs.body) process.env.CPR_BODY = inputs.body;
|
||||||
if (inputs.title) process.env.PULL_REQUEST_TITLE = inputs.title;
|
if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
|
||||||
if (inputs.body) process.env.PULL_REQUEST_BODY = inputs.body;
|
if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
|
||||||
if (inputs.labels) process.env.PULL_REQUEST_LABELS = inputs.labels;
|
if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
|
||||||
if (inputs.assignees) process.env.PULL_REQUEST_ASSIGNEES = inputs.assignees;
|
if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
|
||||||
if (inputs.reviewers) process.env.PULL_REQUEST_REVIEWERS = inputs.reviewers;
|
if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
|
||||||
if (inputs.teamReviewers) process.env.PULL_REQUEST_TEAM_REVIEWERS = inputs.teamReviewers;
|
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
|
||||||
if (inputs.milestone) process.env.PULL_REQUEST_MILESTONE = inputs.milestone;
|
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
|
||||||
if (inputs.project) process.env.PROJECT_NAME = inputs.project;
|
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
|
||||||
if (inputs.projectColumn) process.env.PROJECT_COLUMN_NAME = inputs.projectColumn;
|
if (inputs.base) process.env.CPR_BASE = inputs.base;
|
||||||
if (inputs.branch) process.env.PULL_REQUEST_BRANCH = inputs.branch;
|
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
|
||||||
if (inputs.base) process.env.PULL_REQUEST_BASE = inputs.base;
|
|
||||||
if (inputs.branchSuffix) process.env.BRANCH_SUFFIX = inputs.branchSuffix;
|
|
||||||
if (inputs.debugEvent) process.env.DEBUG_EVENT = inputs.debugEvent;
|
|
||||||
|
|
||||||
// Execute python script
|
// Execute python script
|
||||||
await exec.exec("python", [`${src}/create-pull-request.py`]);
|
await exec.exec("python", [`${src}/create_pull_request.py`]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/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_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
|
|
@ -0,0 +1,147 @@
|
||||||
|
#!/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}
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
""" Create or Update Pull Request """
|
||||||
|
from github import Github, GithubException
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
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("::warning::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("::warning::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,
|
||||||
|
):
|
||||||
|
# Create the pull request
|
||||||
|
github_repo = Github(github_token).get_repo(github_repository)
|
||||||
|
try:
|
||||||
|
pull_request = github_repo.create_pull(
|
||||||
|
title=title, body=body, base=base, head=branch
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Created pull request #%d (%s => %s)" % (pull_request.number, branch, base)
|
||||||
|
)
|
||||||
|
except GithubException as e:
|
||||||
|
if e.status == 422:
|
||||||
|
# Format the branch name
|
||||||
|
head_branch = "%s:%s" % (github_repository.split("/")[0], branch)
|
||||||
|
# Get the pull request
|
||||||
|
pull_request = github_repo.get_pulls(
|
||||||
|
state="open", base=base, head=head_branch
|
||||||
|
)[0]
|
||||||
|
print(
|
||||||
|
"Updated pull request #%d (%s => %s)"
|
||||||
|
% (pull_request.number, branch, base)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Set the output variables
|
||||||
|
os.system("echo ::set-env name=PULL_REQUEST_NUMBER::%d" % pull_request.number)
|
||||||
|
os.system("echo ::set-output name=pr_number::%d" % pull_request.number)
|
||||||
|
|
||||||
|
# Set labels, assignees and milestone
|
||||||
|
if labels is not None:
|
||||||
|
print("Applying labels '%s'" % labels)
|
||||||
|
pull_request.as_issue().edit(labels=cs_string_to_list(labels))
|
||||||
|
if assignees is not None:
|
||||||
|
print("Applying assignees '%s'" % assignees)
|
||||||
|
pull_request.as_issue().edit(assignees=cs_string_to_list(assignees))
|
||||||
|
if milestone is not None:
|
||||||
|
print("Applying milestone '%s'" % 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("Requesting reviewers '%s'" % 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("Requesting reviewers failed - %s" % e.data["message"])
|
||||||
|
|
||||||
|
# Set pull request team reviewers
|
||||||
|
if team_reviewers is not None:
|
||||||
|
print("Requesting team reviewers '%s'" % 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 - %s" % e.data["errors"][0]["message"]
|
||||||
|
)
|
|
@ -0,0 +1,166 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
""" Create Pull Request """
|
||||||
|
import common as cmn
|
||||||
|
import create_or_update_branch as coub
|
||||||
|
import create_or_update_pull_request as coupr
|
||||||
|
from git import Repo
|
||||||
|
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>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_committer_author(repo, committer, author):
|
||||||
|
# When the user intends for the committer and author to be the same,
|
||||||
|
# ideally, just the committer should be supplied. When just the author
|
||||||
|
# is supplied, the same user intention is assumed.
|
||||||
|
if committer is None and author is not None:
|
||||||
|
print("Supplied author will also be used as the committer.")
|
||||||
|
committer = author
|
||||||
|
|
||||||
|
# TODO Get committer and author from git config
|
||||||
|
# If just a committer exists, only set committer
|
||||||
|
# If just author exists also use for the committer
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
print(f"Configuring git committer as '{committer_name} <{committer_email}>'")
|
||||||
|
if author is not None:
|
||||||
|
author_name, author_email = cmn.parse_display_name_email(author)
|
||||||
|
print(f"Configuring git author as '{author_name} <{author_email}>'")
|
||||||
|
repo.git.update_environment(
|
||||||
|
GIT_COMMITTER_NAME=committer_name,
|
||||||
|
GIT_COMMITTER_EMAIL=committer_email,
|
||||||
|
GIT_AUTHOR_NAME=author_name,
|
||||||
|
GIT_AUTHOR_EMAIL=author_email,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
repo.git.update_environment(
|
||||||
|
GIT_COMMITTER_NAME=committer_name, GIT_COMMITTER_EMAIL=committer_email,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Get required environment variables
|
||||||
|
github_token = os.environ["GITHUB_TOKEN"]
|
||||||
|
github_repository = os.environ["GITHUB_REPOSITORY"]
|
||||||
|
# Get environment variables with defaults
|
||||||
|
branch = os.getenv("CPR_BRANCH", "create-pull-request/patch")
|
||||||
|
commit_message = os.getenv(
|
||||||
|
"CPR_COMMIT_MESSAGE", "Changes by create-pull-request action"
|
||||||
|
)
|
||||||
|
# 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 to the working directory
|
||||||
|
repo = Repo(os.getcwd())
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
print(f"::debug::{working_base}")
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Set the repository URL
|
||||||
|
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
|
||||||
|
# TODO Figure out what to do when there is no diff with the base anymore
|
||||||
|
# if not result["diff"]:
|
||||||
|
|
||||||
|
# Fetch optional environment variables with default values
|
||||||
|
title = os.getenv("CPR_TITLE", "Auto-generated by create-pull-request action")
|
||||||
|
body = os.getenv(
|
||||||
|
"CPR_BODY",
|
||||||
|
"Auto-generated pull request by "
|
||||||
|
"[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub Action",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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"),
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/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_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"
|
||||||
|
)
|
|
@ -0,0 +1,748 @@
|
||||||
|
#!/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
|
||||||
|
|
||||||
|
|
||||||
|
author_name = "github-actions[bot]"
|
||||||
|
author_email = "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
committer_name = "GitHub"
|
||||||
|
committer_email = "noreply@github.com"
|
||||||
|
|
||||||
|
# Set git environment
|
||||||
|
repo = Repo(os.getcwd())
|
||||||
|
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 = "Changes by create-pull-request action"
|
||||||
|
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(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(UNTRACKED_FILE, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def get_tracked_content():
|
||||||
|
# Read the content of the tracked file
|
||||||
|
with open(TRACKED_FILE, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def get_untracked_content():
|
||||||
|
# Read the content of the untracked file
|
||||||
|
with open(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.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
|
42
index.js
42
index.js
|
@ -23,10 +23,8 @@ async function run() {
|
||||||
const inputs = {
|
const inputs = {
|
||||||
token: core.getInput("token"),
|
token: core.getInput("token"),
|
||||||
commitMessage: core.getInput("commit-message"),
|
commitMessage: core.getInput("commit-message"),
|
||||||
commitAuthorName: core.getInput("author-name"),
|
committer: core.getInput("committer"),
|
||||||
commitAuthorEmail: core.getInput("author-email"),
|
author: core.getInput("author"),
|
||||||
committerName: core.getInput("committer-name"),
|
|
||||||
committerEmail: core.getInput("committer-email"),
|
|
||||||
title: core.getInput("title"),
|
title: core.getInput("title"),
|
||||||
body: core.getInput("body"),
|
body: core.getInput("body"),
|
||||||
labels: core.getInput("labels"),
|
labels: core.getInput("labels"),
|
||||||
|
@ -39,33 +37,29 @@ async function run() {
|
||||||
branch: core.getInput("branch"),
|
branch: core.getInput("branch"),
|
||||||
base: core.getInput("base"),
|
base: core.getInput("base"),
|
||||||
branchSuffix: core.getInput("branch-suffix"),
|
branchSuffix: core.getInput("branch-suffix"),
|
||||||
debugEvent: core.getInput("debug-event")
|
|
||||||
};
|
};
|
||||||
core.debug(`Inputs: ${inspect(inputs)}`);
|
core.debug(`Inputs: ${inspect(inputs)}`);
|
||||||
|
|
||||||
// Set environment variables from inputs.
|
// Set environment variables from inputs.
|
||||||
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
|
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
|
||||||
if (inputs.commitMessage) process.env.COMMIT_MESSAGE = inputs.commitMessage;
|
if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
|
||||||
if (inputs.commitAuthorName) process.env.COMMIT_AUTHOR_NAME = inputs.commitAuthorName;
|
if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
|
||||||
if (inputs.commitAuthorEmail) process.env.COMMIT_AUTHOR_EMAIL = inputs.commitAuthorEmail;
|
if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
|
||||||
if (inputs.committerName) process.env.COMMITTER_NAME = inputs.committerName;
|
if (inputs.title) process.env.CPR_TITLE = inputs.title;
|
||||||
if (inputs.committerEmail) process.env.COMMITTER_EMAIL = inputs.committerEmail;
|
if (inputs.body) process.env.CPR_BODY = inputs.body;
|
||||||
if (inputs.title) process.env.PULL_REQUEST_TITLE = inputs.title;
|
if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
|
||||||
if (inputs.body) process.env.PULL_REQUEST_BODY = inputs.body;
|
if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
|
||||||
if (inputs.labels) process.env.PULL_REQUEST_LABELS = inputs.labels;
|
if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
|
||||||
if (inputs.assignees) process.env.PULL_REQUEST_ASSIGNEES = inputs.assignees;
|
if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
|
||||||
if (inputs.reviewers) process.env.PULL_REQUEST_REVIEWERS = inputs.reviewers;
|
if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
|
||||||
if (inputs.teamReviewers) process.env.PULL_REQUEST_TEAM_REVIEWERS = inputs.teamReviewers;
|
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
|
||||||
if (inputs.milestone) process.env.PULL_REQUEST_MILESTONE = inputs.milestone;
|
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
|
||||||
if (inputs.project) process.env.PROJECT_NAME = inputs.project;
|
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
|
||||||
if (inputs.projectColumn) process.env.PROJECT_COLUMN_NAME = inputs.projectColumn;
|
if (inputs.base) process.env.CPR_BASE = inputs.base;
|
||||||
if (inputs.branch) process.env.PULL_REQUEST_BRANCH = inputs.branch;
|
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
|
||||||
if (inputs.base) process.env.PULL_REQUEST_BASE = inputs.base;
|
|
||||||
if (inputs.branchSuffix) process.env.BRANCH_SUFFIX = inputs.branchSuffix;
|
|
||||||
if (inputs.debugEvent) process.env.DEBUG_EVENT = inputs.debugEvent;
|
|
||||||
|
|
||||||
// Execute python script
|
// Execute python script
|
||||||
await exec.exec("python", [`${src}/create-pull-request.py`]);
|
await exec.exec("python", [`${src}/create_pull_request.py`]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/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_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
|
|
@ -0,0 +1,147 @@
|
||||||
|
#!/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}
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
""" Create or Update Pull Request """
|
||||||
|
from github import Github, GithubException
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
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("::warning::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("::warning::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,
|
||||||
|
):
|
||||||
|
# Create the pull request
|
||||||
|
github_repo = Github(github_token).get_repo(github_repository)
|
||||||
|
try:
|
||||||
|
pull_request = github_repo.create_pull(
|
||||||
|
title=title, body=body, base=base, head=branch
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Created pull request #%d (%s => %s)" % (pull_request.number, branch, base)
|
||||||
|
)
|
||||||
|
except GithubException as e:
|
||||||
|
if e.status == 422:
|
||||||
|
# Format the branch name
|
||||||
|
head_branch = "%s:%s" % (github_repository.split("/")[0], branch)
|
||||||
|
# Get the pull request
|
||||||
|
pull_request = github_repo.get_pulls(
|
||||||
|
state="open", base=base, head=head_branch
|
||||||
|
)[0]
|
||||||
|
print(
|
||||||
|
"Updated pull request #%d (%s => %s)"
|
||||||
|
% (pull_request.number, branch, base)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Set the output variables
|
||||||
|
os.system("echo ::set-env name=PULL_REQUEST_NUMBER::%d" % pull_request.number)
|
||||||
|
os.system("echo ::set-output name=pr_number::%d" % pull_request.number)
|
||||||
|
|
||||||
|
# Set labels, assignees and milestone
|
||||||
|
if labels is not None:
|
||||||
|
print("Applying labels '%s'" % labels)
|
||||||
|
pull_request.as_issue().edit(labels=cs_string_to_list(labels))
|
||||||
|
if assignees is not None:
|
||||||
|
print("Applying assignees '%s'" % assignees)
|
||||||
|
pull_request.as_issue().edit(assignees=cs_string_to_list(assignees))
|
||||||
|
if milestone is not None:
|
||||||
|
print("Applying milestone '%s'" % 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("Requesting reviewers '%s'" % 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("Requesting reviewers failed - %s" % e.data["message"])
|
||||||
|
|
||||||
|
# Set pull request team reviewers
|
||||||
|
if team_reviewers is not None:
|
||||||
|
print("Requesting team reviewers '%s'" % 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 - %s" % e.data["errors"][0]["message"]
|
||||||
|
)
|
|
@ -0,0 +1,166 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
""" Create Pull Request """
|
||||||
|
import common as cmn
|
||||||
|
import create_or_update_branch as coub
|
||||||
|
import create_or_update_pull_request as coupr
|
||||||
|
from git import Repo
|
||||||
|
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>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_committer_author(repo, committer, author):
|
||||||
|
# When the user intends for the committer and author to be the same,
|
||||||
|
# ideally, just the committer should be supplied. When just the author
|
||||||
|
# is supplied, the same user intention is assumed.
|
||||||
|
if committer is None and author is not None:
|
||||||
|
print("Supplied author will also be used as the committer.")
|
||||||
|
committer = author
|
||||||
|
|
||||||
|
# TODO Get committer and author from git config
|
||||||
|
# If just a committer exists, only set committer
|
||||||
|
# If just author exists also use for the committer
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
print(f"Configuring git committer as '{committer_name} <{committer_email}>'")
|
||||||
|
if author is not None:
|
||||||
|
author_name, author_email = cmn.parse_display_name_email(author)
|
||||||
|
print(f"Configuring git author as '{author_name} <{author_email}>'")
|
||||||
|
repo.git.update_environment(
|
||||||
|
GIT_COMMITTER_NAME=committer_name,
|
||||||
|
GIT_COMMITTER_EMAIL=committer_email,
|
||||||
|
GIT_AUTHOR_NAME=author_name,
|
||||||
|
GIT_AUTHOR_EMAIL=author_email,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
repo.git.update_environment(
|
||||||
|
GIT_COMMITTER_NAME=committer_name, GIT_COMMITTER_EMAIL=committer_email,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Get required environment variables
|
||||||
|
github_token = os.environ["GITHUB_TOKEN"]
|
||||||
|
github_repository = os.environ["GITHUB_REPOSITORY"]
|
||||||
|
# Get environment variables with defaults
|
||||||
|
branch = os.getenv("CPR_BRANCH", "create-pull-request/patch")
|
||||||
|
commit_message = os.getenv(
|
||||||
|
"CPR_COMMIT_MESSAGE", "Changes by create-pull-request action"
|
||||||
|
)
|
||||||
|
# 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 to the working directory
|
||||||
|
repo = Repo(os.getcwd())
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
print(f"::debug::{working_base}")
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Set the repository URL
|
||||||
|
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
|
||||||
|
# TODO Figure out what to do when there is no diff with the base anymore
|
||||||
|
# if not result["diff"]:
|
||||||
|
|
||||||
|
# Fetch optional environment variables with default values
|
||||||
|
title = os.getenv("CPR_TITLE", "Auto-generated by create-pull-request action")
|
||||||
|
body = os.getenv(
|
||||||
|
"CPR_BODY",
|
||||||
|
"Auto-generated pull request by "
|
||||||
|
"[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub Action",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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"),
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/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_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"
|
||||||
|
)
|
|
@ -0,0 +1,748 @@
|
||||||
|
#!/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
|
||||||
|
|
||||||
|
|
||||||
|
author_name = "github-actions[bot]"
|
||||||
|
author_email = "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
committer_name = "GitHub"
|
||||||
|
committer_email = "noreply@github.com"
|
||||||
|
|
||||||
|
# Set git environment
|
||||||
|
repo = Repo(os.getcwd())
|
||||||
|
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 = "Changes by create-pull-request action"
|
||||||
|
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(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(UNTRACKED_FILE, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def get_tracked_content():
|
||||||
|
# Read the content of the tracked file
|
||||||
|
with open(TRACKED_FILE, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def get_untracked_content():
|
||||||
|
# Read the content of the untracked file
|
||||||
|
with open(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.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
|
Loading…
Reference in New Issue