# Pull Request Merging API Reference

## Endpoints

### Check if PR is Merged

```
GET /repos/{owner}/{repo}/pulls/{index}/merge
```

Returns:
- **204 No Content** - PR is merged
- **404 Not Found** - PR is not merged

```bash
# Check merge status (use -o /dev/null to suppress body, -w for status code)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge")

if [ "$HTTP_CODE" = "204" ]; then
  echo "PR is merged."
elif [ "$HTTP_CODE" = "404" ]; then
  echo "PR is NOT merged."
fi
```

### Merge a Pull Request

```
POST /repos/{owner}/{repo}/pulls/{index}/merge
```

**MergePullRequestOption:**

| Field                      | Type    | Required | Description                                                    |
|----------------------------|---------|----------|----------------------------------------------------------------|
| Do                         | string  | yes      | Merge method (see below)                                       |
| MergeTitleField            | string  | no       | Custom merge commit title                                      |
| MergeMessageField          | string  | no       | Custom merge commit message body                               |
| MergeCommitID              | string  | no       | Commit SHA for `manually-merged` method                        |
| head_commit_id             | string  | no       | Expected HEAD SHA for optimistic locking                       |
| delete_branch_after_merge  | boolean | no       | Delete the head branch after merging                           |
| force_merge                | boolean | no       | Force merge even if checks haven't passed                      |
| merge_when_checks_succeed  | boolean | no       | Schedule automerge when all checks pass                        |

**Merge Methods (`Do` field):**

| Method              | Description                                                          |
|---------------------|----------------------------------------------------------------------|
| `merge`             | Create a merge commit (preserves all history)                        |
| `rebase`            | Rebase commits onto base branch (linear history, no merge commit)    |
| `rebase-merge`      | Rebase then create a merge commit                                    |
| `squash`            | Squash all commits into one, then merge                              |
| `fast-forward-only` | Fast-forward if possible, fail otherwise                             |
| `manually-merged`   | Mark PR as merged using an existing commit (no actual merge happens) |

---

## Merge Style Examples

### Standard Merge Commit

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "merge",
    "MergeTitleField": "Merge pull request #42: Add authentication",
    "MergeMessageField": "Implements JWT-based authentication.\n\nReviewed-by: {reviewer}",
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Squash Merge

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "squash",
    "MergeTitleField": "Add authentication (#42)",
    "MergeMessageField": "* Add JWT token generation\n* Add login endpoint\n* Add middleware\n\nCloses #42",
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Rebase (Linear History)

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "rebase",
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Rebase-Merge (Rebase + Merge Commit)

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "rebase-merge",
    "MergeTitleField": "Merge pull request #42: Add authentication",
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Fast-Forward Only

Fails if the base branch has diverged and a fast-forward is not possible.

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "fast-forward-only",
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Manually Merged

Marks a PR as merged using an existing commit SHA. No actual merge is performed. Useful when a merge was done outside of Forgejo (e.g., via command line).

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "manually-merged",
    "MergeCommitID": "abc123def456789012345678901234567890abcd",
    "delete_branch_after_merge": false
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Optimistic Locking with head_commit_id

Use `head_commit_id` to ensure no new commits were pushed between checking the PR and merging. The merge will fail if HEAD has changed.

```bash
# First, get the current HEAD
HEAD_SHA=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}" | jq -r '.head.sha')

# Merge with optimistic locking
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d "{
    \"Do\": \"squash\",
    \"head_commit_id\": \"${HEAD_SHA}\",
    \"delete_branch_after_merge\": true
  }" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Force Merge (Bypass Checks)

```bash
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "merge",
    "force_merge": true,
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

---

## Automerge

### Schedule Automerge

Set `merge_when_checks_succeed: true` to schedule the PR to be automatically merged once all required status checks pass.

```bash
# Schedule automerge with squash style
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "Do": "squash",
    "MergeTitleField": "Add authentication (#42)",
    "MergeMessageField": "Implements JWT-based auth.\n\nCloses #42",
    "merge_when_checks_succeed": true,
    "delete_branch_after_merge": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Cancel Scheduled Automerge

```
DELETE /repos/{owner}/{repo}/pulls/{index}/merge
```

```bash
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
```

### Check if Automerge is Scheduled

There is no direct field on the PR object indicating automerge status. To check, attempt to cancel automerge and inspect the response:

```bash
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/merge")

if [ "$HTTP_CODE" = "204" ]; then
  echo "Automerge was scheduled and has been cancelled."
elif [ "$HTTP_CODE" = "404" ] || [ "$HTTP_CODE" = "409" ]; then
  echo "No automerge was scheduled."
fi
```

---

## Update PR Branch (Server-Side Rebase/Merge)

```
POST /repos/{owner}/{repo}/pulls/{index}/update
```

Updates the PR's head branch by merging or rebasing the base branch into it. This is done entirely server-side — no local clone is needed. This only works when there are no merge conflicts.

**Query Parameters:**

| Parameter | Type   | Description                            |
|-----------|--------|----------------------------------------|
| style     | string | `rebase` or `merge` (default: `merge`) |

Note: The `style` parameter is a **query parameter** per the Swagger spec. This endpoint has no request body. Some Forgejo versions also accept `{"style": "rebase"}` as a JSON body, but the canonical way is as a query parameter.

```bash
# Update by merging base into head (default)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=merge"

# Update by rebasing head onto base
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase"
```

**Response codes:**
- **200** - Branch updated successfully
- **409 Conflict** - Cannot update due to merge conflicts (must resolve manually)
- **422 Unprocessable** - PR head branch is up to date, no update needed

---

## Mergeability Check

The `mergeable` field on the PullRequest object indicates whether the PR can be cleanly merged:

| Value   | Meaning                                                    |
|---------|------------------------------------------------------------|
| `true`  | No conflicts, merge is possible                           |
| `false` | Has conflicts, merge will fail                             |
| `null`  | Not yet computed (Forgejo computes this lazily on demand) |

```bash
MERGEABLE=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}" | jq '.mergeable')

case "$MERGEABLE" in
  true)  echo "Ready to merge" ;;
  false) echo "Has conflicts - resolve before merging" ;;
  null)  echo "Mergeability not yet computed - try again shortly" ;;
esac
```

---

## Staleness Check

A PR is stale when the base branch has advanced since the PR was created or last updated. Compare `pr.base.sha` with the current HEAD of the base branch:

```bash
PR=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}")

PR_BASE_SHA=$(echo "$PR" | jq -r '.base.sha')
BASE_BRANCH=$(echo "$PR" | jq -r '.base.ref')

CURRENT_BASE_SHA=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branches/${BASE_BRANCH}" | jq -r '.commit.id')

if [ "$PR_BASE_SHA" != "$CURRENT_BASE_SHA" ]; then
  echo "PR is stale. Updating branch..."
  curl -s -X POST \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase"
fi
```

---

## Complete Merge Workflow

```bash
#!/bin/bash
# Full merge workflow: check status, update if stale, merge
OWNER="{owner}"
REPO="{repo}"
PR_INDEX="{index}"
MERGE_STYLE="squash"

# 1. Get PR details
PR=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_INDEX}")

STATE=$(echo "$PR" | jq -r '.state')
MERGED=$(echo "$PR" | jq '.merged')
MERGEABLE=$(echo "$PR" | jq '.mergeable')
TITLE=$(echo "$PR" | jq -r '.title')

echo "PR #${PR_INDEX}: ${TITLE}"
echo "State: ${STATE}, Merged: ${MERGED}, Mergeable: ${MERGEABLE}"

# 2. Bail if already merged or closed
if [ "$MERGED" = "true" ]; then
  echo "Already merged. Nothing to do."
  exit 0
fi
if [ "$STATE" != "open" ]; then
  echo "PR is not open (state: ${STATE}). Cannot merge."
  exit 1
fi

# 3. Check staleness and update if needed
PR_BASE_SHA=$(echo "$PR" | jq -r '.base.sha')
BASE_BRANCH=$(echo "$PR" | jq -r '.base.ref')
CURRENT_BASE_SHA=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/branches/${BASE_BRANCH}" | jq -r '.commit.id')

if [ "$PR_BASE_SHA" != "$CURRENT_BASE_SHA" ]; then
  echo "PR is stale. Updating branch..."
  UPDATE_RESULT=$(curl -s -w "\n%{http_code}" -X POST \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_INDEX}/update?style=rebase")
  
  UPDATE_CODE=$(echo "$UPDATE_RESULT" | tail -1)
  if [ "$UPDATE_CODE" = "409" ]; then
    echo "Conflict during update. Resolve manually."
    exit 1
  fi
  echo "Branch updated successfully."
  
  # Re-fetch PR to get updated mergeable status
  sleep 2
  PR=$(curl -s \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_INDEX}")
  MERGEABLE=$(echo "$PR" | jq '.mergeable')
fi

# 4. Check mergeability
if [ "$MERGEABLE" = "false" ]; then
  echo "PR has conflicts. Cannot merge."
  exit 1
fi

# 5. Get HEAD SHA for optimistic locking
HEAD_SHA=$(echo "$PR" | jq -r '.head.sha')

# 6. Merge
echo "Merging with style: ${MERGE_STYLE}..."
MERGE_RESULT=$(curl -s -w "\n%{http_code}" -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d "{
    \"Do\": \"${MERGE_STYLE}\",
    \"head_commit_id\": \"${HEAD_SHA}\",
    \"delete_branch_after_merge\": true
  }" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_INDEX}/merge")

MERGE_CODE=$(echo "$MERGE_RESULT" | tail -1)
if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ]; then
  echo "PR #${PR_INDEX} merged successfully!"
else
  echo "Merge failed with HTTP ${MERGE_CODE}:"
  echo "$MERGE_RESULT" | head -n -1
  exit 1
fi
```
