Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ venfork stage feature-auth --pr --base develop

**What `--pr` adds:**
1. Looks up the most recent PR on the private mirror with `--head <branch>` (open first, then most recent of any state).
2. Renders the upstream PR body by stripping any `<!-- venfork:internal -->...<!-- /venfork:internal -->` blocks and appending a footer linking back to the internal review.
2. Renders the upstream PR body by stripping any `<!-- venfork:internal -->...<!-- /venfork:internal -->` blocks. The private mirror stays invisible to upstream — no back-link to the internal review and no hint that one exists. The internal PR URL is recorded only in your mirror config (step 5).
3. Shows you the translated body **before** confirming, so you can catch redaction mistakes before they go public.
4. Runs `gh pr create --repo <upstream> --base <default> --head <fork-owner>:<branch>` and surfaces the resulting PR URL.
5. Records the linkage in `venfork-config.shippedBranches[<branch>]` for later tracking.
Expand Down
41 changes: 16 additions & 25 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2560,24 +2560,16 @@ async function findInternalPr(
}

/**
* Strips `<!-- venfork:internal -->...<!-- /venfork:internal -->` blocks via
* a depth-tracking pass (see `stripInternalBlocks`) and appends a footer
* linking back to the internal review PR or issue. Only the team can follow
* that link; the upstream maintainer sees only that there *was* an internal
* review.
* Renders an internal PR/issue body for upstream by stripping
* `<!-- venfork:internal -->...<!-- /venfork:internal -->` blocks via a
* depth-tracking pass (see `stripInternalBlocks`).
*
* The private mirror must stay invisible to upstream: no back-link to the
* internal PR/issue and no hint that one exists. The upstream maintainer sees
* only the public-facing body.
*/
function translateInternalBody(
body: string,
internalUrl: string | undefined,
kind: 'pr' | 'issue' = 'pr'
): string {
const stripped = stripInternalBlocks(body).trim();
const footer = internalUrl
? kind === 'issue'
? `\n\n> Upstreamed from internal issue (${internalUrl}).`
: `\n\n> Upstreamed from internal review (${internalUrl}).`
: '';
return `${stripped}${footer}`.trim();
function translateInternalBody(body: string): string {
return stripInternalBlocks(body).trim();
}

/**
Expand All @@ -2586,6 +2578,9 @@ function translateInternalBody(
* `upstream/<defaultBranch>..<branch>` so the upstream maintainer sees what
* the change actually is, rather than a "please add a description" placeholder.
*
* The body must not reveal the private mirror — upstream only ever sees the
* commit summary, never that the work was staged from a mirror.
*
* Fetches `upstream/<defaultBranch>` first so the log works even when schedule
* is disabled and the ref may not exist locally yet.
*/
Expand All @@ -2601,14 +2596,14 @@ async function buildSyntheticBody(
reject: false,
})`git log --oneline --no-merges upstream/${defaultBranch}..${branch}`;
if (log.exitCode !== 0 || !log.stdout.trim()) {
return 'Staged from a private mirror. No internal review PR was open at stage time.';
return 'No description provided.';
}
const lines = log.stdout
.trim()
.split('\n')
.map((line) => `- ${line}`)
.join('\n');
return `Staged from a private mirror. Commits in this branch:\n\n${lines}\n\n_(No internal review PR was found at stage time.)_`;
return `Commits in this branch:\n\n${lines}`;
}

/**
Expand All @@ -2624,7 +2619,7 @@ async function buildUpstreamPrPayload(
if (internal) {
return {
title: override.title ?? internal.title,
body: override.body ?? translateInternalBody(internal.body, internal.url),
body: override.body ?? translateInternalBody(internal.body),
};
}
return {
Expand Down Expand Up @@ -3304,11 +3299,7 @@ export async function issueCommand(
const internal = await readIssue(mirrorRepoPath, internalNumber, repoDir);
s.stop(`Read: ${internal.title}`);

const translatedBody = translateInternalBody(
internal.body,
internal.url,
'issue'
);
const translatedBody = translateInternalBody(internal.body);
const upstreamTitle = options.title ?? internal.title;

p.note(
Expand Down
5 changes: 4 additions & 1 deletion tests/e2e/sync-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,10 @@ e2eDescribe('venfork e2e — scheduled sync flow', () => {
expect(upstreamPr.body).not.toContain('Internal note');
expect(upstreamPr.body).not.toContain('venfork:internal');
expect(upstreamPr.body).toContain('Public summary');
expect(upstreamPr.body).toContain('Upstreamed from internal review');
// The private mirror must stay invisible to upstream: no back-link to the
// internal review PR and no hint that one exists.
expect(upstreamPr.body).not.toContain('Upstreamed from internal');
expect(upstreamPr.body).not.toContain(names.mirrorBare);
expect(upstreamPr.isDraft).toBe(true);
}, 300_000);

Expand Down
Loading