Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
FROM alpine:3.23.4

ARG TARGETARCH
ARG HUB_VERSION=2.14.2

# Copy all needed files
COPY entrypoint.sh /
COPY scripts/ /scripts/
Expand All @@ -14,22 +11,7 @@ SHELL ["/bin/sh", "-euxo", "pipefail", "-c"]
RUN set -eux; \
xargs -r apk add --no-cache < /tmp/alpine-packages.txt; \
chmod +x /entrypoint.sh /scripts/replace-template-diff.sh /scripts/split_content_bytes.py; \
targetarch="${TARGETARCH:-}"; \
if [ -z "${targetarch}" ]; then \
case "$(uname -m)" in \
x86_64) targetarch="amd64" ;; \
aarch64|arm64) targetarch="arm64" ;; \
*) echo "Unsupported host architecture: $(uname -m)"; exit 1 ;; \
esac; \
fi; \
case "${targetarch}" in amd64|arm64) ;; *) echo "Unsupported TARGETARCH: ${targetarch}"; exit 1 ;; esac; \
hub_archive="hub-linux-${targetarch}-${HUB_VERSION}.tgz"; \
hub_url="https://github.com/mislav/hub/releases/download/v${HUB_VERSION}/${hub_archive}"; \
curl -fsSL "${hub_url}" -o /tmp/hub.tgz; \
tar -xzf /tmp/hub.tgz -C /tmp; \
install -m 0755 "/tmp/hub-linux-${targetarch}-${HUB_VERSION}/bin/hub" /usr/bin/hub; \
gh --version; \
test -x /usr/bin/hub; \
git --version; \
jq --version; \
python3 --version; \
Expand Down
81 changes: 54 additions & 27 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ CHUNK_COUNT=0
MANAGED_COMMENT_START="<!-- action-pull-request:managed-diff-chunk:start -->"
MANAGED_COMMENT_END="<!-- action-pull-request:managed-diff-chunk:end -->"
TARGET_REPOSITORY=""
TARGET_OWNER=""
REPOSITORY_PATH=""
WORKSPACE_DIR=""
REPO_DIR=""
Expand Down Expand Up @@ -152,6 +153,33 @@ print(pathlib.Path(sys.argv[1]).resolve(strict=False))
PY
}

trim_whitespace() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "${value}"
}

append_csv_arg() {
local flag="$1"
local csv_value="$2"
local -n args_ref="$3"
local raw_value trimmed_value
local -a parsed_values

if [[ -z "${csv_value}" ]]; then
return 0
fi

IFS=',' read -r -a parsed_values <<< "${csv_value}"
for raw_value in "${parsed_values[@]}"; do
trimmed_value="$(trim_whitespace "${raw_value}")"
if [[ -n "${trimmed_value}" ]]; then
args_ref+=("${flag}" "${trimmed_value}")
fi
done
}

get_managed_comment_ids() {
local pr_number="$1"
local output_file="$2"
Expand Down Expand Up @@ -289,6 +317,7 @@ if [[ ! "${TARGET_REPOSITORY}" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,38})/[A-Za-z0-9._-
echo -e "\n[ERROR] Input 'repository' must use owner/name format. Got: ${TARGET_REPOSITORY}" >&2
exit 1
fi
TARGET_OWNER="${TARGET_REPOSITORY%%/*}"

REPOSITORY_PATH="${INPUT_REPOSITORY_PATH:-.}"
if [[ -z "${REPOSITORY_PATH}" ]]; then
Expand Down Expand Up @@ -329,8 +358,6 @@ echo -e "\nSetting GitHub credentials..."
git -C "${REPO_DIR}" remote set-url origin "https://${GITHUB_ACTOR}:${INPUT_GITHUB_TOKEN}@github.com/${TARGET_REPOSITORY}"
git -C "${REPO_DIR}" config user.name "${GITHUB_ACTOR}"
git -C "${REPO_DIR}" config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
# Needed for hub binary
export GITHUB_USER="${GITHUB_ACTOR}"
echo "Repository: ${TARGET_REPOSITORY}"
echo "Repository path: ${REPO_DIR}"

Expand Down Expand Up @@ -379,7 +406,7 @@ else
fi

echo -e "\nSetting template..."
PR_NUMBER=$(hub pr list --base "${TARGET_BRANCH}" --head "${SOURCE_BRANCH}" --format '%I')
PR_NUMBER="$(gh pr list --repo "${TARGET_REPOSITORY}" --state open --base "${TARGET_BRANCH}" --head "${TARGET_OWNER}:${SOURCE_BRANCH}" --json number --jq '.[0].number // empty')"
if [[ -z "${PR_NUMBER}" ]]; then
if [[ -n "${INPUT_TEMPLATE}" ]]; then
echo "Template source: input template file"
Expand All @@ -401,7 +428,7 @@ else
TEMPLATE="${INPUT_BODY}"
else
echo "Template source: existing pull request body"
TEMPLATE=$(hub api --method GET "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}" | jq -r '.body')
TEMPLATE="$(gh api --method GET "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')"
fi
fi

Expand Down Expand Up @@ -480,22 +507,6 @@ if [[ -z "${PR_NUMBER}" ]]; then
else
TITLE=$(git log -1 --pretty=%s | head -1)
fi
ARG_LIST=("-F" "/tmp/template")
if [[ -n "${INPUT_REVIEWER}" ]]; then
ARG_LIST+=("-r" "${INPUT_REVIEWER}")
fi
if [[ -n "${INPUT_ASSIGNEE}" ]]; then
ARG_LIST+=("-a" "${INPUT_ASSIGNEE}")
fi
if [[ -n "${INPUT_LABEL}" ]]; then
ARG_LIST+=("-l" "${INPUT_LABEL}")
fi
if [[ -n "${INPUT_MILESTONE}" ]]; then
ARG_LIST+=("-M" "${INPUT_MILESTONE}")
fi
if [[ "${INPUT_DRAFT}" == "true" ]]; then
ARG_LIST+=("-d")
fi
else
echo -e "${TEMPLATE}" > /tmp/template
fi
Expand All @@ -510,23 +521,39 @@ echo "Final main body size (bytes): ${FINAL_BODY_BYTES}"
echo "Managed overflow chunks: ${CHUNK_COUNT}"

if [[ -z "${PR_NUMBER}" ]]; then
GH_CREATE_ARGS=(
--repo "${TARGET_REPOSITORY}"
--base "${TARGET_BRANCH}"
--head "${TARGET_OWNER}:${SOURCE_BRANCH}"
--title "${TITLE}"
--body-file /tmp/template
)
append_csv_arg --reviewer "${INPUT_REVIEWER}" GH_CREATE_ARGS
append_csv_arg --assignee "${INPUT_ASSIGNEE}" GH_CREATE_ARGS
append_csv_arg --label "${INPUT_LABEL}" GH_CREATE_ARGS
milestone_value="$(trim_whitespace "${INPUT_MILESTONE}")"
if [[ -n "${milestone_value}" ]]; then
GH_CREATE_ARGS+=(--milestone "${milestone_value}")
fi
if [[ "${INPUT_DRAFT}" == "true" ]]; then
GH_CREATE_ARGS+=(--draft)
fi

echo -e "\nCreating pull request"
echo -e "${TITLE}" > /tmp/template
echo -e "\n${TEMPLATE}" >> /tmp/template
echo -e "\nTemplate:"
cat /tmp/template
echo -e "\nRunning: hub pull-request -b ${TARGET_BRANCH} -h ${SOURCE_BRANCH} --no-edit ..."
URL=$(hub pull-request -b "${TARGET_BRANCH}" -h "${SOURCE_BRANCH}" --no-edit "${ARG_LIST[@]}")
echo -e "\nRunning: gh pr create --repo ${TARGET_REPOSITORY} --base ${TARGET_BRANCH} --head ${TARGET_OWNER}:${SOURCE_BRANCH} ..."
URL="$(gh pr create "${GH_CREATE_ARGS[@]}")"
# shellcheck disable=SC2181
if [[ "$?" != "0" ]]; then RET_CODE=1; fi
PR_NUMBER=$(gh pr view --json number -q .number "${URL}")
PR_NUMBER="$(gh pr view "${URL}" --repo "${TARGET_REPOSITORY}" --json number --jq '.number')"
if (( CHUNK_COUNT > 0 )); then
reconcile_managed_comments "${PR_NUMBER}" "${CHUNK_COUNT}"
fi
else
echo -e "\nUpdating pull request"
echo -e "Running: hub api --method PATCH repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER} --field body=@/tmp/template"
URL=$(hub api --method PATCH "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}" --field "body=@/tmp/template" | jq -r '.html_url')
echo -e "Running: gh api --method PATCH repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER} --field body=@/tmp/template"
URL="$(gh api --method PATCH "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}" --field "body=@/tmp/template" --jq '.html_url')"
# shellcheck disable=SC2181
if [[ "$?" != "0" ]]; then RET_CODE=1; fi
if (( CHUNK_COUNT > 0 )); then
Expand Down
2 changes: 1 addition & 1 deletion tests/docker/local-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ commandTests:
command: bash
args:
- -lc
- command -v bash >/dev/null 2>&1 && command -v git >/dev/null 2>&1 && command -v gh >/dev/null 2>&1 && command -v hub >/dev/null 2>&1 && command -v jq >/dev/null 2>&1 && command -v curl >/dev/null 2>&1
- command -v bash >/dev/null 2>&1 && command -v git >/dev/null 2>&1 && command -v gh >/dev/null 2>&1 && command -v jq >/dev/null 2>&1 && command -v curl >/dev/null 2>&1

- name: Temporary and APK cache cleaned
command: bash
Expand Down
210 changes: 210 additions & 0 deletions tests/unit/test_pr_create_with_gh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#!/usr/bin/env bash

set -Eeuo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
SCRIPT_PATH="${SCRIPT_DIR}/../../entrypoint.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT

assert_contains() {
local file_path="$1"
local expected="$2"
if ! grep -Fq -- "${expected}" "${file_path}"; then
echo "Assertion failed. Expected to find: ${expected}" >&2
echo "----- FILE CONTENT -----" >&2
cat "${file_path}" >&2
exit 1
fi
}

mkdir -p "${TMP_DIR}/bin"
mkdir -p "${TMP_DIR}/repo"

cat > "${TMP_DIR}/bin/git" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail

args=("$@")
if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "-C" ]]; then
args=("${args[@]:2}")
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "config" && "${args[1]}" == "--global" ]]; then
exit 0
fi

if [[ "${#args[@]}" -ge 1 && "${args[0]}" == "config" ]]; then
exit 0
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "remote" && "${args[1]}" == "set-url" ]]; then
exit 0
fi

if [[ "${#args[@]}" -ge 1 && "${args[0]}" == "fetch" ]]; then
exit 0
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "rev-parse" && "${args[1]}" == "--is-inside-work-tree" ]]; then
echo "true"
exit 0
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "show-ref" ]]; then
last_arg="${args[$((${#args[@]} - 1))]}"
if [[ "${last_arg}" == "refs/remotes/origin/develop" || "${last_arg}" == "refs/remotes/origin/release/MAPL-v3" ]]; then
exit 0
fi
exit 1
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "rev-parse" ]]; then
last_arg="${args[$((${#args[@]} - 1))]}"
if [[ "${last_arg}" == "origin/develop" ]]; then
echo "bbb222"
exit 0
fi
if [[ "${last_arg}" == "origin/release/MAPL-v3" ]]; then
echo "aaa111"
exit 0
fi
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "diff" && "${args[1]}" == "--quiet" ]]; then
exit 1
fi

if [[ "${#args[@]}" -ge 1 && "${args[0]}" == "diff" ]]; then
echo "M README.md"
exit 0
fi

if [[ "${#args[@]}" -ge 1 && "${args[0]}" == "log" ]]; then
echo "stub log"
exit 0
fi

if [[ "${#args[@]}" -ge 2 && "${args[0]}" == "symbolic-ref" ]]; then
echo "develop"
exit 0
fi

echo "Unsupported git call: $*" >&2
exit 1
EOF

cat > "${TMP_DIR}/bin/gh" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail

cmd="$*"

if [[ "$#" -ge 2 && "$1" == "pr" && "$2" == "list" ]]; then
exit 0
fi

if [[ "$#" -ge 2 && "$1" == "pr" && "$2" == "create" ]]; then
if [[ "${cmd}" != *"--repo owner/repo"* ]]; then
echo "Missing --repo" >&2
exit 1
fi
if [[ "${cmd}" != *"--base release/MAPL-v3"* ]]; then
echo "Missing --base" >&2
exit 1
fi
if [[ "${cmd}" != *"--head owner:develop"* ]]; then
echo "Missing --head" >&2
exit 1
fi
if [[ "${cmd}" != *"--title My PR title"* ]]; then
echo "Missing --title" >&2
exit 1
fi
if [[ "${cmd}" != *"--body-file /tmp/template"* ]]; then
echo "Missing --body-file" >&2
exit 1
fi
if [[ "${cmd}" != *"--reviewer alice"* || "${cmd}" != *"--reviewer bob"* ]]; then
echo "Missing reviewers" >&2
exit 1
fi
if [[ "${cmd}" != *"--assignee assignee1"* || "${cmd}" != *"--assignee assignee2"* ]]; then
echo "Missing assignees" >&2
exit 1
fi
if [[ "${cmd}" != *"--label bug"* || "${cmd}" != *"--label chore"* ]]; then
echo "Missing labels" >&2
exit 1
fi
if [[ "${cmd}" != *"--milestone Milestone-1"* ]]; then
echo "Missing milestone" >&2
exit 1
fi
if [[ "${cmd}" != *"--draft"* ]]; then
echo "Missing draft flag" >&2
exit 1
fi

echo "https://example.test/pr/456"
exit 0
fi

if [[ "$#" -ge 2 && "$1" == "pr" && "$2" == "view" ]]; then
echo "456"
exit 0
fi

echo "Unsupported gh call: $*" >&2
exit 1
EOF

cat > "${TMP_DIR}/template.md" <<'EOF'
## Template body from file
EOF

chmod +x "${TMP_DIR}/bin/git" "${TMP_DIR}/bin/gh"

LOG_FILE="${TMP_DIR}/run.log"
set +e
PATH="${TMP_DIR}/bin:${PATH}" \
GITHUB_ACTOR="ci-user" \
GITHUB_TOKEN="token" \
GITHUB_REPOSITORY="owner/repo" \
GITHUB_WORKSPACE="${TMP_DIR}" \
GITHUB_OUTPUT="${TMP_DIR}/output.txt" \
INPUT_GITHUB_TOKEN="token" \
INPUT_REPOSITORY_PATH="repo" \
INPUT_SOURCE_BRANCH="develop" \
INPUT_TARGET_BRANCH="release/MAPL-v3" \
INPUT_TITLE="My PR title" \
INPUT_TEMPLATE="${TMP_DIR}/template.md" \
INPUT_BODY="" \
INPUT_REVIEWER="alice,bob" \
INPUT_ASSIGNEE="assignee1,assignee2" \
INPUT_LABEL="bug,chore" \
INPUT_MILESTONE="Milestone-1" \
INPUT_DRAFT="true" \
INPUT_GET_DIFF="false" \
INPUT_OLD_STRING="" \
INPUT_NEW_STRING="" \
INPUT_IGNORE_USERS="dependabot" \
INPUT_ALLOW_NO_DIFF="false" \
INPUT_MAX_BODY_BYTES="65000" \
INPUT_MAX_DIFF_LINES="0" \
bash "${SCRIPT_PATH}" >"${LOG_FILE}" 2>&1
STATUS="$?"
set -e

if [[ "${STATUS}" != "0" ]]; then
echo "Expected successful execution in create mode" >&2
cat "${LOG_FILE}" >&2
exit 1
fi

assert_contains "${LOG_FILE}" "Creating pull request"
assert_contains "${LOG_FILE}" "Running: gh pr create --repo owner/repo --base release/MAPL-v3 --head owner:develop"
assert_contains "${TMP_DIR}/output.txt" "url=https://example.test/pr/456"
assert_contains "${TMP_DIR}/output.txt" "pr_number=456"

echo "GH create flow test passed."
Loading
Loading