1---
2title: Pull requests
3description: Server-side merge base, tree diffs, and fast-forward or three-way merges, computed inside the repository.
4---
5
6Pull requests are computed where the objects are. Merge base, diffs,
7and the merge itself run inside the repository's Durable Object, so
8nothing clones a history to answer what changed.
9
10## Opening
11
12```typescript
13const pull = yield* client.pulls.create({
14 params: { owner: "acme", repo: "web" },
15 payload: {
16 title: "Add the pricing page",
17 body: "Closes the last gap in the marketing site.",
18 base: "main",
19 head: "pricing",
20 },
21});
22```
23
24Branch names may be short or fully qualified. Numbers are per
25repository and start at 1. A missing branch, or `base` equal to
26`head`, is a typed failure.
27
28## Listing and reading
29
30```typescript
31const page = yield* client.pulls.list({
32 params: { owner: "acme", repo: "web" },
33 query: { state: "open" }, // "open" | "closed" | "merged" | "all"
34});
35```
36
37Listing is keyset-paginated, newest first. Reading one pull request
38computes its state live: merge base, ahead and behind counts, and the
39changed files, as the branches are now. If the head branch is deleted
40while it is open, the comparison fields read as null.
41
42## Diffs
43
44The same machinery works without a pull request:
45
46```typescript
47// what one commit changed
48yield* client.objects.commitDiff({
49 params: { owner: "acme", repo: "web", oid },
50});
51
52// three-dot comparison between two refs
53yield* client.objects.compare({
54 params: { owner: "acme", repo: "web" },
55 query: { base: "main", head: "pricing" },
56});
57```
58
59Comparison finds the merge base in the commit graph and diffs the two
60trees. Identical subtrees are pruned on entry, so the cost tracks what
61changed rather than the size of the repository.
62
63## Merging
64
65```typescript
66yield* client.pulls.merge({
67 params: { owner: "acme", repo: "web", number: 1 },
68 payload: { message: "Add the pricing page (#1)" },
69});
70```
71
72The strategy is chosen for you. When the base has not moved, the ref
73fast-forwards and `message` is ignored. Otherwise, if the two sides
74changed disjoint files, a merge commit is written with both parents.
75
76Overlapping edits to one file are a typed `409`. The host does not
77merge hunks or write conflict markers. Resolve locally and push.
78
79Merging is terminal. A merged pull request cannot be reopened, and
80merging it twice is a typed conflict.
81
82## Races
83
84Pass `expectedHeadOid` so a branch that moved between the review and
85the click is refused rather than merged:
86
87```typescript
88yield* client.pulls.merge({
89 params: { owner: "acme", repo: "web", number: 1 },
90 payload: { expectedHeadOid: headOidTheUserSaw },
91});
92```
93
94## Not included
95
96Reviews, comments, approvals, and required checks. A pull request here
97is a branch comparison with a title and a merge, and the review
98workflow is yours to build with these endpoints.
99[Part 4 of the tutorial](/git/tutorial/part-4) starts there.