diff --git a/.changeset/git-merge-endpoint.md b/.changeset/git-merge-endpoint.md index 35de26c..1c1d8a0 100644 --- a/.changeset/git-merge-endpoint.md +++ b/.changeset/git-merge-endpoint.md @@ -8,7 +8,7 @@ `POST /account/api/git/merge` puts a pull request's work on a repository's canonical branch, in either merge or squash mode. -Landing is the one act in this model that needs authority over the repository, +Merging is the one act in this model that needs authority over the repository, so it runs on that account's own server under the session cookie the account page already answers to. An atproto `repo:` scope names a collection and its actions and cannot name one record, so asking a browser page for diff --git a/.changeset/git-change-landing-page.md b/.changeset/git-pull-request-page.md similarity index 100% rename from .changeset/git-change-landing-page.md rename to .changeset/git-pull-request-page.md diff --git a/docs/plans/2026-08-24-pull-requests.md b/docs/plans/2026-08-24-pull-requests.md index 9a298f4..06c153d 100644 --- a/docs/plans/2026-08-24-pull-requests.md +++ b/docs/plans/2026-08-24-pull-requests.md @@ -68,7 +68,7 @@ Two cases the pair does not cover, and what covers them: from the next, which no record carries today. - **A merged pull request whose branch is gone.** The graph answers it where a merge commit carried the work in. Where a squash did, the merger writes the - answer into the commit. See "Landing". + answer into the commit. See "Merging". ## Versions come from the push log @@ -206,7 +206,7 @@ constraints and it did not take GitHub's model. - Change ids come from jj, as a commit header, and map a jj stack to one pull request per commit. - Merge is fast forward, as `git am` applies a series. There is no squash, so - the question this document answers under "Landing" does not arise for them. + the question this document answers under "Merging" does not arise for them. Their knot holds the git data and their appview indexes it. Neither exists here, which is why the fan-in and the daemon's derived records do that work. @@ -228,7 +228,7 @@ before these lexicons publish. rather than the two trees, and needs each version's own base. 4. ~~A record from a named collaborator, so a pull request carries a title, a body and a draft state of its own.~~ Done. -5. ~~Landing: the merge commit, then the squash with the merger's trailer.~~ +5. ~~Merging: the merge commit, then the squash with the merger's trailer.~~ Done. `POST /account/api/git/merge`, both modes, read back by git itself. ## Where merging runs, and why diff --git a/packages/core/src/ports.js b/packages/core/src/ports.js index 5825e9e..2fdad30 100644 --- a/packages/core/src/ports.js +++ b/packages/core/src/ports.js @@ -335,7 +335,7 @@ * platform builds it from the git package and hands it in, the way it hands * in the write guard. * @typedef {Object} GitMergerPort - * @property {(landing: {did: string, name: string, from: string, ref: string, mode: 'merge'|'squash', handle?: string, subject?: string, body?: string}) => Promise<{sha: string, ref: string, mode: string, objects: number}>} merge + * @property {(merge: {did: string, name: string, from: string, ref: string, mode: 'merge'|'squash', handle?: string, subject?: string, body?: string}) => Promise<{sha: string, ref: string, mode: string, objects: number}>} merge */ /** diff --git a/packages/git-ui/src/components/molecules/merge-control.jsx b/packages/git-ui/src/components/molecules/merge-control.jsx index bc72bbc..ae38356 100644 --- a/packages/git-ui/src/components/molecules/merge-control.jsx +++ b/packages/git-ui/src/components/molecules/merge-control.jsx @@ -23,14 +23,14 @@ export function MergeControl({ repo, pull }) { const queryClient = useQueryClient(); const [busy, setBusy] = useState(''); const [error, setError] = useState(''); - const [landed, setLanded] = useState(''); + const [merged, setMerged] = useState(''); if (pull.merged || session?.did !== account.did) return null; - if (landed) { + if (merged) { return (

- Merged as {landed.slice(0, 8)}. + Merged as {merged.slice(0, 8)}.

); } @@ -52,12 +52,12 @@ export function MergeControl({ repo, pull }) { ); } - const land = async (mode) => { + const merge = async (mode) => { setBusy(mode); setError(''); try { const answer = await mergePull(repo, pull, mode); - setLanded(answer.sha); + setMerged(answer.sha); // Every screen reads one work query, and main has just moved under all // of them. queryClient.invalidateQueries({ queryKey: ['work', repo] }); @@ -70,17 +70,17 @@ export function MergeControl({ repo, pull }) { return (
- A merge keeps the branch in the graph. A squash writes one commit and diff --git a/packages/git-ui/vite.config.js b/packages/git-ui/vite.config.js index 3b9927e..3e822d3 100644 --- a/packages/git-ui/vite.config.js +++ b/packages/git-ui/vite.config.js @@ -21,7 +21,7 @@ export default defineConfig({ proxy: { '/.well-known': PDS_URL, '/.blobs': PDS_URL, - // Landing runs on the account's own server under its cookie session. + // Merging runs on the account's own server under its cookie session. // Served from the account's site the page is already there; the dev // server is on a port of its own, so it proxies. '/account': PDS_URL, diff --git a/packages/git/src/merger.js b/packages/git/src/merger.js index 308bd62..52f7391 100644 --- a/packages/git/src/merger.js +++ b/packages/git/src/merger.js @@ -107,7 +107,7 @@ async function openObjects(record, read) { * @property {(did: string) => Promise<{did: string, service: string}>} locate - * where an account's repository lives, from its DID document * @property {() => Promise} handle - this account's handle, for - * the identity the landing commit records + * the identity the merge commit records * @property {() => number} [now] - epoch milliseconds, for a test that wants * the commit id to hold still */ diff --git a/packages/git/src/pack-write.js b/packages/git/src/pack-write.js index aed91a7..5262649 100644 --- a/packages/git/src/pack-write.js +++ b/packages/git/src/pack-write.js @@ -3,12 +3,12 @@ * no git to shell out to. * * The node helper takes the pack git hands it over the wire and never builds - * one. A browser landing a pull request has no such pack: it holds the commit + * one. A server merging a pull request has no such pack: it holds the commit * it just made and the objects the branch it is merging brought with it, and * a push is a bundle plus a record write. * * Every object is stored whole. A delta would be smaller, and finding a good - * base is the whole cost of packing; a landing writes one commit and the trees + * base is the whole cost of packing; a merge writes one commit and the trees * under it, so the saving is not worth the code that finds it. A repack on the * server side is what compacts a chain. * diff --git a/packages/git/test/merge-commit.test.js b/packages/git/test/merge-commit.test.js index e0c00bd..37681c4 100644 --- a/packages/git/test/merge-commit.test.js +++ b/packages/git/test/merge-commit.test.js @@ -1,4 +1,4 @@ -// The commit that lands a pull request, and what it is allowed to land. +// The commit that merges a pull request, and what it is allowed to merge. import { describe, expect, it } from 'vitest'; import { diff --git a/packages/git/test/merge.test.js b/packages/git/test/merge.test.js index 62e282e..b84f5a6 100644 --- a/packages/git/test/merge.test.js +++ b/packages/git/test/merge.test.js @@ -1,6 +1,6 @@ -// Assembling the push that lands a pull request, over two object stores that +// Assembling the push that merges a pull request, over two object stores that // hold what two servers would. The bundle it answers with is read back by the -// parsers that read git's own, so a landing that indexes here indexes there. +// parsers that read git's own, so a merge that indexes here indexes there. import { describe, expect, it } from 'vitest'; import { parseBundle } from '../src/bundle.js'; @@ -118,7 +118,7 @@ const from = { const by = { name: 'Alice', email: 'alice@example.com' }; /** @param {{mode: 'merge'|'squash'}} how */ -const land = ({ mode }) => +const merge = ({ mode }) => buildMerge({ main, source, @@ -134,20 +134,20 @@ const land = ({ mode }) => describe('buildMerge', () => { it('bundles what the canonical copy lacks, and nothing it has', async () => { - const landing = await land({ mode: 'merge' }); - const objects = await indexPacks([parseBundle(landing.bundle).pack]); + const built = await merge({ mode: 'merge' }); + const objects = await indexPacks([parseBundle(built.bundle).pack]); // The commit, its tree, the file it added, and the merge itself. The // README and the root tree are already there and are not sent again. expect([...objects.keys()].sort()).toEqual( - [work.sha, workTree.sha, added.sha, landing.sha].sort(), + [work.sha, workTree.sha, added.sha, built.sha].sort(), ); expect(objects.has(readme.sha)).toBe(false); expect(objects.has(rootTree.sha)).toBe(false); }); it('carries the tree of every commit a merge makes reachable', async () => { - const landing = await buildMerge({ + const built = await buildMerge({ main, source: stackedSource, mainTip: root.sha, @@ -159,7 +159,7 @@ describe('buildMerge', () => { at: 0, subject: 'add farewell()', }); - const objects = await indexPacks([parseBundle(landing.bundle).pack]); + const objects = await indexPacks([parseBundle(built.bundle).pack]); // The middle commit rides along, so the tree it points at has to as well: // git reads every commit the merge reaches and opens each one's tree. @@ -169,39 +169,37 @@ describe('buildMerge', () => { }); it('gives a merge the branch tip as a parent and its tree as the result', async () => { - const landing = await land({ mode: 'merge' }); - const objects = await indexPacks([parseBundle(landing.bundle).pack]); + const built = await merge({ mode: 'merge' }); + const objects = await indexPacks([parseBundle(built.bundle).pack]); const merged = parseCommit( - /** @type {{data: Uint8Array}} */ (objects.get(landing.sha)).data, + /** @type {{data: Uint8Array}} */ (objects.get(built.sha)).data, ); expect(merged.parents).toEqual([root.sha, work.sha]); expect(merged.tree).toBe(workTree.sha); }); it('leaves a squash the commits it squashed', async () => { - const landing = await land({ mode: 'squash' }); - const objects = await indexPacks([parseBundle(landing.bundle).pack]); + const built = await merge({ mode: 'squash' }); + const objects = await indexPacks([parseBundle(built.bundle).pack]); expect(objects.has(work.sha)).toBe(false); const squashed = parseCommit( - /** @type {{data: Uint8Array}} */ (objects.get(landing.sha)).data, + /** @type {{data: Uint8Array}} */ (objects.get(built.sha)).data, ); expect(squashed.parents).toEqual([root.sha]); // The graph says nothing, so the message carries the answer. expect( new TextDecoder().decode( - /** @type {{data: Uint8Array}} */ (objects.get(landing.sha)).data, + /** @type {{data: Uint8Array}} */ (objects.get(built.sha)).data, ), ).toContain('Merged-ref: refs/heads/agent/farewell'); }); it('names the ref it moves, and what a reader must already hold', async () => { - const landing = await land({ mode: 'merge' }); - const parsed = parseBundle(landing.bundle); - expect(parsed.refs).toEqual([ - { sha: landing.sha, name: 'refs/heads/main' }, - ]); + const built = await merge({ mode: 'merge' }); + const parsed = parseBundle(built.bundle); + expect(parsed.refs).toEqual([{ sha: built.sha, name: 'refs/heads/main' }]); expect(parsed.prerequisites).toEqual([root.sha]); - expect(landing.index.length).toBeGreaterThan(0); + expect(built.index.length).toBeGreaterThan(0); }); it('refuses a branch main has moved under', async () => { diff --git a/packages/git/test/merger.test.js b/packages/git/test/merger.test.js index dd192b0..4f784b4 100644 --- a/packages/git/test/merger.test.js +++ b/packages/git/test/merger.test.js @@ -1,4 +1,4 @@ -// What a landing commit says it is. A merge writes git's own subject, because +// What a merge commit says it is. It writes git's own subject, because // a merge is a record of taking a change rather than the change itself. import { describe, expect, it } from 'vitest'; @@ -71,7 +71,7 @@ describe('createGitMerger', () => { }), ).rejects.toThrow(/is not a branch here/); // A repository with no main is refused before the other account is - // resolved, so a landing that cannot work costs no request. + // resolved, so a merge that cannot work costs no request. expect(seen).toEqual([]); }); diff --git a/packages/node/src/index.js b/packages/node/src/index.js index 1659eda..30508dc 100644 --- a/packages/node/src/index.js +++ b/packages/node/src/index.js @@ -650,7 +650,7 @@ export async function createServer({ }); } catch { // The git package is an optional peer; without it the endpoint reports - // landing unavailable and the page offers no button. + // merging unavailable and the page offers no button. } } diff --git a/scripts/dev-git-collab-demo.mjs b/scripts/dev-git-collab-demo.mjs index 843f097..6f53444 100644 --- a/scripts/dev-git-collab-demo.mjs +++ b/scripts/dev-git-collab-demo.mjs @@ -505,7 +505,7 @@ const farewellV1 = git('bob', 'rev-parse', 'agent/farewell'); const bobV1 = await repoRef('bob'); console.log('bob pushed agent/badge and agent/farewell'); -// ---- 3. alice lands the badge -------------------------------------------- +// ---- 3. alice merges the badge ------------------------------------------- git('alice', 'fetch', '-q', url('bob'), 'agent/badge'); git('alice', 'merge', '-q', '--ff-only', 'FETCH_HEAD'); @@ -873,7 +873,7 @@ await say( await say( 'alice', farewellV2, - 'Good to land once somebody who is not the author approves it.', + 'Good to merge once somebody who is not the author approves it.', ); // ---- the other changes --------------------------------------------------- @@ -938,7 +938,7 @@ const carolIssue = await issue('carol', { body: 'The readme shows a call but never says the return value is a string.', }); await issue('alice', { - body: 'Covered by the usage section once agent/docs lands.', + body: 'Covered by the usage section once agent/docs merges.', replyTo: { uri: carolIssue.uri, cid: carolIssue.cid }, disposition: 'resolved', }); diff --git a/test/account-api.test.js b/test/account-api.test.js index 866ab5c..b42f041 100644 --- a/test/account-api.test.js +++ b/test/account-api.test.js @@ -2331,7 +2331,7 @@ describe('git create endpoint', () => { describe('git merge endpoint', () => { /** - * A lander over the records this test holds, so its refusals are the real + * A merger over the records this test holds, so its refusals are the real * ones rather than a stub's. * @param {Record} records - by rkey */ @@ -2374,7 +2374,7 @@ describe('git merge endpoint', () => { }); it('refuses a repository whose canonical branch is not there', async () => { - // A repository with no commits has no main to land onto. + // A repository with no commits has no main to merge onto. const pds = createPds({ gitMerger: mergerOver({ empty: {