Skip to content
Open
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
14 changes: 4 additions & 10 deletions tests/mock_gh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,10 @@
# Mock gh CLI for unit tests.
# Only direct children are queried now (no recursive updates of indirect children).

if [[ "$1" == "pr" && "$2" == "list" ]]; then
# Parse the --base argument to determine which PRs to return
base=""
for ((i=1; i<=$#; i++)); do
if [[ "${!i}" == "--base" ]]; then
next=$((i+1))
base="${!next}"
fi
done

if [[ "$1" == "api" && "$2" == repos/*"/pulls?base="* ]]; then
# Open PRs based on a branch (already --jq filtered to "<number> <head>").
base="${2#*pulls\?base=}"
base="${base%%&*}"
if [[ "$base" == "feature1" ]]; then
# feature2 is a direct child of feature1 (PR #2)
echo '2 feature2'
Expand Down
15 changes: 8 additions & 7 deletions tests/test_conflict_resolution_resume.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ok() { echo "✅ $1"; PASS=$((PASS+1)); }
# Build a configurable gh mock in a temp dir. It records every invocation to
# $CALLS and is driven by env vars set per scenario:
# MOCK_LABELS newline-separated labels returned by `pr view --json labels`
# MOCK_COMMENTS_FILE file whose contents are returned by `pr view --json comments`
# MOCK_COMMENTS_FILE file served as the body of our own PR comments
# The PR's base branch is not mocked: the script must take it from PR_BASE
# (event payload), so a baseRefName query is an unhandled call and fails.
make_mock_gh() {
Expand All @@ -33,18 +33,18 @@ echo "gh $*" >> "$CALLS"
if [[ "$1 $2" == "pr view" ]]; then
case "$*" in
*--json\ labels*) printf '%s\n' "${MOCK_LABELS:-}";;
*--json\ comments*)
# The comments file stands for our own comments only, so the query
# must restrict itself to those.
[[ "$*" == *viewerDidAuthor* ]] || { echo "comments query must filter by viewerDidAuthor" >&2; exit 1; }
cat "${MOCK_COMMENTS_FILE:-/dev/null}";;
*) echo "unhandled pr view: $*" >&2; exit 1;;
esac
elif [[ "$1 $2" == "api graphql" ]]; then
# The comments file stands for our own comments only, so the query must
# restrict itself to those.
[[ "$*" == *viewerDidAuthor* ]] || { echo "comments query must filter by viewerDidAuthor" >&2; exit 1; }
cat "${MOCK_COMMENTS_FILE:-/dev/null}"
elif [[ "$1 $2" == "pr comment" ]]; then
cat >/dev/null # consume the -F - body
elif [[ "$1 $2" == "pr edit" ]]; then
:
elif [[ "$1 $2" == "pr list" ]]; then
elif [[ "$1" == "api" ]]; then
: # no sibling conflicts
elif [[ "$1 $2" == "label create" ]]; then
:
Expand Down Expand Up @@ -93,6 +93,7 @@ setup_repo() {

run_resume() {
env ACTION_MODE=conflict-resolved PR_BRANCH=child PR_NUMBER=5 PR_BASE="$PR_BASE" \
GITHUB_REPOSITORY=tester/repo \
GH="$MOCK_DIR/mock_gh.sh" GIT="$MOCK_DIR/mock_git.sh" \
MOCK_LABELS="$MOCK_LABELS" \
MOCK_COMMENTS_FILE="$MOCK_COMMENTS_FILE" CALLS="$CALLS" \
Expand Down
23 changes: 19 additions & 4 deletions update-pr-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# PR_BRANCH - The head branch of the PR being resumed
# PR_NUMBER - Its PR number, from the event payload
# PR_BASE - Its base branch, from the event payload
# GITHUB_REPOSITORY - "owner/repo", provided by Actions
#
# Design note:
# This script aims to output a transcript of "plain" git/gh commands that a
Expand Down Expand Up @@ -44,8 +45,19 @@ format_state_marker() {
read_state_marker() {
local PR_NUMBER="$1"
local BODIES
if ! BODIES=$(gh pr view "$PR_NUMBER" --json comments \
--jq '.comments[] | select(.viewerDidAuthor) | .body'); then
if ! BODIES=$(gh api graphql --paginate \
-F owner="${GITHUB_REPOSITORY%/*}" -F repo="${GITHUB_REPOSITORY#*/}" \
-F number="$PR_NUMBER" -f query='
query($owner: String!, $repo: String!, $number: Int!, $endCursor: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
comments(first: 100, after: $endCursor) {
pageInfo { hasNextPage endCursor }
nodes { viewerDidAuthor body }
}
}
}
}' --jq '.data.repository.pullRequest.comments.nodes[] | select(.viewerDidAuthor) | .body'); then
echo "Error: could not read comments of PR #$PR_NUMBER" >&2
exit 1
fi
Expand Down Expand Up @@ -139,7 +151,8 @@ is_rebase_merge() {

# Echoes "<number> <head branch>" for each open PR based on the merged branch.
list_child_prs() {
log_cmd gh pr list --base "$MERGED_BRANCH" --json number,headRefName --jq '.[] | "\(.number) \(.headRefName)"'
log_cmd gh api "repos/{owner}/{repo}/pulls?base=$MERGED_BRANCH&state=open&per_page=100" \
--paginate --jq '.[] | "\(.number) \(.head.ref)"'
}

# Args: head branch, base branch, PR number. git commands use the branch; gh
Expand Down Expand Up @@ -255,7 +268,8 @@ has_sibling_conflicts() {

# Find all open PRs with the conflict label that are based on BASE_BRANCH
local CONFLICTED_SIBLINGS
CONFLICTED_SIBLINGS=$(gh pr list --base "$BASE_BRANCH" --label "$CONFLICT_LABEL" --json headRefName --jq '.[].headRefName' 2>/dev/null || echo "")
CONFLICTED_SIBLINGS=$(gh api "repos/{owner}/{repo}/pulls?base=$BASE_BRANCH&state=open&per_page=100" \
--paginate --jq ".[] | select(any(.labels[]; .name == \"$CONFLICT_LABEL\")) | .head.ref" 2>/dev/null || echo "")

for SIBLING in $CONFLICTED_SIBLINGS; do
if [[ "$SIBLING" != "$EXCLUDE_BRANCH" ]]; then
Expand All @@ -281,6 +295,7 @@ continue_after_resolution() {
check_env_var "PR_BRANCH"
check_env_var "PR_NUMBER"
check_env_var "PR_BASE"
check_env_var "GITHUB_REPOSITORY"

echo "Checking if PR #$PR_NUMBER ($PR_BRANCH) needs continuation after conflict resolution..."

Expand Down
Loading