~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
122 lines · 3.7 KB Raw History
// The commit that merges a pull request, and what it is allowed to merge.

import { describe, expect, it } from 'vitest';
import {
  buildMergeCommit,
  mergeable,
  mergeTrailers,
} from '../src/merge-commit.js';
import { indexPacks, parseCommit } from '../src/objects.js';
import { writePack } from '../src/pack-write.js';

/** @param {string} name */
const sha = (name) => name.repeat(40).slice(0, 40);

const MAIN = sha('m');
const TIP = sha('t');
const TREE = sha('r');

const by = { name: 'Alice', email: 'alice@example.com' };
// A fixed instant, so the commit id a test asserts on does not move.
const AT = Date.UTC(2026, 7, 24, 12, 0, 0);

describe('mergeable', () => {
  it('accepts a branch whose merge base is main itself', () => {
    expect(mergeable({ base: MAIN, sha: TIP }, MAIN).can).toBe(true);
  });

  it('accepts a branch main has moved under; the merge itself decides', () => {
    expect(mergeable({ base: sha('o'), sha: TIP }, MAIN).can).toBe(true);
  });

  it('refuses a branch main already reaches', () => {
    const answer = mergeable({ base: TIP, sha: TIP }, MAIN);
    expect(answer.can).toBe(false);
    expect(answer.why).toMatch(/already reaches/);
  });

  it('refuses a branch sharing no history with main', () => {
    expect(mergeable({ base: null, sha: TIP }, MAIN).can).toBe(false);
  });

  it('refuses main itself', () => {
    expect(mergeable({ base: MAIN, sha: MAIN }, MAIN).can).toBe(false);
  });
});

describe('mergeTrailers', () => {
  it('names the copy and the ref the work came from', () => {
    expect(
      mergeTrailers({
        did: 'did:plc:bob',
        repo: 'project',
        ref: 'refs/heads/fix',
      }),
    ).toEqual([
      'Merged-from: at://did:plc:bob/dev.pdsjs.git.repo/project',
      'Merged-ref: refs/heads/fix',
    ]);
  });
});

describe('buildMergeCommit', () => {
  /**
   * @param {'merge'|'squash'} mode
   * @param {{body?: string, trailers?: string[]}} [extra]
   */
  const build = (mode, extra = {}) =>
    buildMergeCommit({
      mode,
      tree: TREE,
      mainTip: MAIN,
      branchTip: TIP,
      by,
      at: AT,
      subject: 'add farewell()',
      ...extra,
    });

  it('gives a merge two parents, main first', async () => {
    const commit = build('merge');
    const objects = await indexPacks([writePack([commit])]);
    const parsed = parseCommit(
      /** @type {{data: Uint8Array}} */ (objects.get(commit.sha)).data,
    );
    expect(parsed.parents).toEqual([MAIN, TIP]);
    expect(parsed.tree).toBe(TREE);
  });

  it('gives a squash main alone, so the graph says nothing', async () => {
    const commit = build('squash');
    const objects = await indexPacks([writePack([commit])]);
    const parsed = parseCommit(
      /** @type {{data: Uint8Array}} */ (objects.get(commit.sha)).data,
    );
    expect(parsed.parents).toEqual([MAIN]);
  });

  it('carries the trailers in their own block', () => {
    const commit = build('squash', {
      body: 'Two greetings, one register.',
      trailers: mergeTrailers({
        did: 'did:plc:bob',
        repo: 'project',
        ref: 'refs/heads/fix',
      }),
    });
    const message = new TextDecoder().decode(commit.data);
    expect(message).toContain(
      '\nadd farewell()\n\nTwo greetings, one register.\n\nMerged-from: at://did:plc:bob/dev.pdsjs.git.repo/project\nMerged-ref: refs/heads/fix\n',
    );
  });

  it('leaves no blank lines behind a subject with nothing after it', () => {
    const message = new TextDecoder().decode(build('merge').data);
    expect(message.endsWith('\nadd farewell()\n')).toBe(true);
  });

  it('is the same commit for the same inputs', () => {
    expect(build('merge').sha).toBe(build('merge').sha);
    expect(build('merge').sha).not.toBe(build('squash').sha);
  });
});