forked from peter-evans/create-pull-request
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreate_pull_request.py
More file actions
executable file
·205 lines (175 loc) · 7.23 KB
/
create_pull_request.py
File metadata and controls
executable file
·205 lines (175 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/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, GitCommandError
import json
import os
import sys
import time
# Default the committer and author to the GitHub Actions bot
DEFAULT_COMMITTER = "GitHub <noreply@github.com>"
DEFAULT_AUTHOR = (
"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
)
DEFAULT_COMMIT_MESSAGE = "[create-pull-request] automated change"
DEFAULT_TITLE = "Changes by create-pull-request action"
DEFAULT_BODY = (
"Automated changes by "
+ "[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action"
)
DEFAULT_BRANCH = "create-pull-request/patch"
def get_git_config_value(repo, name):
try:
return repo.git.config("--get", name)
except GitCommandError:
return None
def git_user_config_is_set(repo):
name = get_git_config_value(repo, "user.name")
email = get_git_config_value(repo, "user.email")
if name is not None and email is not None:
print(f"Git user already configured as '{name} <{email}>'")
return True
committer_name = get_git_config_value(repo, "committer.name")
committer_email = get_git_config_value(repo, "committer.email")
author_name = get_git_config_value(repo, "author.name")
author_email = get_git_config_value(repo, "author.email")
if (
committer_name is not None
and committer_email is not None
and author_name is not None
and author_email is not None
):
print(
f"Git committer already configured as '{committer_name} <{committer_email}>'"
)
print(f"Git author already configured as '{author_name} <{author_email}>'")
return True
return False
def set_committer_author(repo, committer, author):
# If either committer or author is supplied they will be cross used
if committer is None and author is not None:
print("Supplied author will also be used as the committer.")
committer = author
if author is None and committer is not None:
print("Supplied committer will also be used as the author.")
author = committer
# If no committer/author has been supplied but user configuration already
# exists in git config we can exit and use the existing config as-is.
if committer is None and author is None:
if git_user_config_is_set(repo):
return
# Set defaults if no committer/author has been supplied
if committer is None and author is None:
committer = DEFAULT_COMMITTER
author = DEFAULT_AUTHOR
# Set git environment. This will not persist after the action completes.
committer_name, committer_email = cmn.parse_display_name_email(committer)
author_name, author_email = cmn.parse_display_name_email(author)
repo.git.update_environment(
GIT_COMMITTER_NAME=committer_name,
GIT_COMMITTER_EMAIL=committer_email,
GIT_AUTHOR_NAME=author_name,
GIT_AUTHOR_EMAIL=author_email,
)
print(f"Configured git committer as '{committer_name} <{committer_email}>'")
print(f"Configured git author as '{author_name} <{author_email}>'")
# Get required environment variables
github_token = os.environ["GITHUB_TOKEN"]
github_repository = os.environ["GITHUB_REPOSITORY_OVERRIDE"]
# Get environment variables with defaults
path = os.getenv("CPR_PATH", os.getcwd())
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
commit_message = os.getenv("CPR_COMMIT_MESSAGE", DEFAULT_COMMIT_MESSAGE)
# Get environment variables with a default of 'None'
committer = os.environ.get("CPR_COMMITTER")
author = os.environ.get("CPR_AUTHOR")
base = os.environ.get("CPR_BASE")
# Set the repo path
repo = Repo(path)
# Determine if the checked out ref is a valid base for a pull request
# The action needs the checked out HEAD ref to be a branch
# This check will fail in the following cases:
# - HEAD is detached
# - HEAD is a merge commit (pull_request events)
# - HEAD is a tag
try:
working_base = repo.git.symbolic_ref("HEAD", "--short")
except GitCommandError as e:
print(f"::debug::{e.stderr}")
print(
f"::error::The checked out ref is not a valid base for a pull request. "
+ "Unable to continue. Exiting."
)
sys.exit(1)
# Exit if the working base is a PR branch created by this action.
# This may occur when using a PAT instead of GITHUB_TOKEN because
# a PAT allows workflow actions to trigger further events.
if working_base.startswith(branch):
print(
f"::error::Working base branch '{working_base}' was created by this action. "
+ "Unable to continue. Exiting."
)
sys.exit(1)
# Fetch an optional environment variable to determine the branch suffix
branch_suffix = os.environ.get("CPR_BRANCH_SUFFIX")
if branch_suffix is not None:
if branch_suffix == "short-commit-hash":
# Suffix with the short SHA1 hash
branch = "{}-{}".format(branch, repo.git.rev_parse("--short", "HEAD"))
elif branch_suffix == "timestamp":
# Suffix with the current timestamp
branch = "{}-{}".format(branch, int(time.time()))
elif branch_suffix == "random":
# Suffix with a 7 character random string
branch = "{}-{}".format(branch, cmn.get_random_string())
else:
print(
f"::error::Branch suffix '{branch_suffix}' is not a valid value. "
+ "Unable to continue. Exiting."
)
sys.exit(1)
# Output head branch
print(f"Pull request branch to create or update set to '{branch}'")
# Set the committer and author
try:
set_committer_author(repo, committer, author)
except ValueError as e:
print(f"::error::{e} " + "Unable to continue. Exiting.")
sys.exit(1)
# 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"]
# If there is no longer a diff with the base delete the branch and exit
if not result["diff"]:
print(f"Branch '{branch}' no longer differs from base branch '{base}'")
print(f"Closing pull request and deleting branch '{branch}'")
repo.git.push("--delete", "--force", repo_url, f"refs/heads/{branch}")
sys.exit()
# Fetch optional environment variables with default values
title = os.getenv("CPR_TITLE", DEFAULT_TITLE)
body = os.getenv("CPR_BODY", DEFAULT_BODY)
# Create or update the pull request
coupr.create_or_update_pull_request(
github_token,
github_repository,
branch,
base,
title,
body,
os.environ.get("CPR_LABELS"),
os.environ.get("GITHUB_ACTOR"),
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"),
)