jj-octo 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jj-octo.js +415 -257
- package/package.json +1 -1
package/dist/jj-octo.js
CHANGED
|
@@ -18239,7 +18239,7 @@ var layer14 = /* @__PURE__ */ layerMergedContext(/* @__PURE__ */ succeed6(fetch)
|
|
|
18239
18239
|
// package.json
|
|
18240
18240
|
var package_default = {
|
|
18241
18241
|
name: "jj-octo",
|
|
18242
|
-
version: "0.
|
|
18242
|
+
version: "0.7.1",
|
|
18243
18243
|
description: "A jj subcommand for GitHub that supports stacked pull requests and works in Claude Code cloud sessions",
|
|
18244
18244
|
license: "MIT",
|
|
18245
18245
|
repository: {
|
|
@@ -18377,20 +18377,26 @@ class Jj extends Service()("jj-octo/Jj") {
|
|
|
18377
18377
|
var failed = (args, detail) => new JjFailed({
|
|
18378
18378
|
message: detail.trim() === "" ? `jj ${args.join(" ")} failed` : detail.trim().replace(/^Error: /u, "")
|
|
18379
18379
|
});
|
|
18380
|
-
var jjLayer = ({ cwd, env,
|
|
18380
|
+
var jjLayer = ({ cwd, env, printed } = {}) => effect(Jj, gen2(function* () {
|
|
18381
18381
|
const spawner = yield* ChildProcessSpawner;
|
|
18382
18382
|
const location2 = { cwd, env, extendEnv: true };
|
|
18383
|
-
const
|
|
18384
|
-
const read = (args) => scoped2(gen2(function* () {
|
|
18383
|
+
const captured = (args) => scoped2(gen2(function* () {
|
|
18385
18384
|
const handle = yield* spawner.spawn(make15("jj", args, location2));
|
|
18386
|
-
|
|
18385
|
+
return yield* all2([
|
|
18387
18386
|
mkString(decodeText(handle.stdout)),
|
|
18388
18387
|
mkString(decodeText(handle.stderr)),
|
|
18389
18388
|
handle.exitCode
|
|
18390
18389
|
], { concurrency: "unbounded" });
|
|
18391
|
-
return code === 0 ? stdout : yield* failed(args, stderr);
|
|
18392
18390
|
})).pipe(catchTag2("PlatformError", (error) => fail6(failed(args, error.message))));
|
|
18393
|
-
const
|
|
18391
|
+
const read = (args) => flatMap4(captured(args), ([stdout, stderr, code]) => code === 0 ? succeed6(stdout) : fail6(failed(args, stderr)));
|
|
18392
|
+
const run = (args) => printed === undefined ? spawner.exitCode(make15("jj", args, {
|
|
18393
|
+
...location2,
|
|
18394
|
+
stderr: "inherit",
|
|
18395
|
+
stdout: "inherit"
|
|
18396
|
+
})).pipe(catchTag2("PlatformError", (error) => fail6(failed(args, error.message))), flatMap4((code) => code === 0 ? void_3 : fail6(failed(args, "")))) : flatMap4(captured(args), ([stdout, stderr, code]) => {
|
|
18397
|
+
printed(stdout + stderr);
|
|
18398
|
+
return code === 0 ? void_3 : fail6(failed(args, stderr));
|
|
18399
|
+
});
|
|
18394
18400
|
return { read, run };
|
|
18395
18401
|
}));
|
|
18396
18402
|
var JjLive = jjLayer();
|
|
@@ -18439,6 +18445,7 @@ var SCRATCH = '(working_copies() & empty() & description(exact:""))';
|
|
|
18439
18445
|
var stackRevset = (revision) => `(reachable(${revision}, mutable()) & (${STACK_ROOTS})::) ~ ${SCRATCH}`;
|
|
18440
18446
|
var downstackRevset = (revision) => `::(${revision}) & (${stackRevset(revision)})`;
|
|
18441
18447
|
var upstackRevset = (revision) => `(${revision})+:: & (${stackRevset(revision)})`;
|
|
18448
|
+
var anyOf = (revsets) => ["none()", ...revsets].join(" | ");
|
|
18442
18449
|
var bookmarkRevset = (bookmark) => `bookmarks(exact:"${bookmark}")`;
|
|
18443
18450
|
var lines2 = (output) => output.split(`
|
|
18444
18451
|
`).filter((line) => line !== "");
|
|
@@ -18544,6 +18551,121 @@ var chain = (revset, revision) => gen2(function* () {
|
|
|
18544
18551
|
var stackChanges = (revision) => chain(stackRevset(revision), revision);
|
|
18545
18552
|
var downstackChanges = (revision) => chain(downstackRevset(revision), revision);
|
|
18546
18553
|
|
|
18554
|
+
// src/clients/github/openapi.ts
|
|
18555
|
+
var responseOf = () => (schema) => schema;
|
|
18556
|
+
var bodyOf = (body) => bodyJsonUnsafe(body);
|
|
18557
|
+
|
|
18558
|
+
// src/clients/github/types.ts
|
|
18559
|
+
var PullRequestNumberSchema = Number6.pipe(brand("PullRequestNumber"));
|
|
18560
|
+
var StackNumberSchema = Number6.pipe(brand("StackNumber"));
|
|
18561
|
+
|
|
18562
|
+
// src/clients/github/client.ts
|
|
18563
|
+
class GitHubFailed extends TaggedError2("GitHubFailed") {
|
|
18564
|
+
}
|
|
18565
|
+
|
|
18566
|
+
class GitHub extends Service()("jj-octo/GitHub") {
|
|
18567
|
+
}
|
|
18568
|
+
var CreatedPullRequest = responseOf()(Struct({ number: PullRequestNumberSchema }));
|
|
18569
|
+
var CreatedStack = responseOf()(Struct({ number: StackNumberSchema }));
|
|
18570
|
+
var PullRequests = responseOf()(ArraySchema(Struct({
|
|
18571
|
+
base: Struct({ ref: String4 }),
|
|
18572
|
+
body: NullOr(String4),
|
|
18573
|
+
draft: optionalKey2(Boolean3),
|
|
18574
|
+
number: PullRequestNumberSchema,
|
|
18575
|
+
title: String4
|
|
18576
|
+
})));
|
|
18577
|
+
var ClosedPullRequests = responseOf()(ArraySchema(Struct({
|
|
18578
|
+
head: Struct({ sha: String4 }),
|
|
18579
|
+
merged_at: NullOr(String4)
|
|
18580
|
+
})));
|
|
18581
|
+
var StackedPullRequest = Struct({
|
|
18582
|
+
number: PullRequestNumberSchema,
|
|
18583
|
+
state: Literals(["closed", "open"])
|
|
18584
|
+
});
|
|
18585
|
+
var Stacks = responseOf()(ArraySchema(Struct({ number: StackNumberSchema, pull_requests: ArraySchema(StackedPullRequest) })));
|
|
18586
|
+
var RepositoryDetails = responseOf()(Struct({ default_branch: String4 }));
|
|
18587
|
+
var MergeRequest = Struct({
|
|
18588
|
+
details: Struct({
|
|
18589
|
+
message: String4,
|
|
18590
|
+
uuid: optionalKey2(String4)
|
|
18591
|
+
}),
|
|
18592
|
+
status: Literals(["enqueued", "failed", "merged", "pending"])
|
|
18593
|
+
});
|
|
18594
|
+
var StartedMerge = responseOf()(MergeRequest);
|
|
18595
|
+
var MergeResult = responseOf()(MergeRequest);
|
|
18596
|
+
var GitHubLive = (repository, connection) => {
|
|
18597
|
+
const repo = (path) => `/repos/${repository.owner}/${repository.name}${path}`;
|
|
18598
|
+
return effect(GitHub, gen2(function* () {
|
|
18599
|
+
const requestInit = match(connection.proxy, {
|
|
18600
|
+
onNone: () => ({}),
|
|
18601
|
+
onSome: ({ ca, url }) => match(ca, {
|
|
18602
|
+
onNone: () => ({ proxy: url }),
|
|
18603
|
+
onSome: (certificates) => ({ proxy: url, tls: { ca: certificates } })
|
|
18604
|
+
})
|
|
18605
|
+
});
|
|
18606
|
+
const client = (yield* HttpClient).pipe(mapRequest((request) => request.pipe(prependUrl("https://api.github.com"), acceptJson, bearerToken(connection.token), setHeaders({
|
|
18607
|
+
"user-agent": "jj-octo",
|
|
18608
|
+
"x-github-api-version": "2022-11-28"
|
|
18609
|
+
}))));
|
|
18610
|
+
const send = (request) => client.execute(request).pipe(provideService2(RequestInit, requestInit), flatMap4((response) => response.status < 400 ? succeed6(response) : response.text.pipe(orElseSucceed2(() => ""), flatMap4((text) => fail6(new GitHubFailed({
|
|
18611
|
+
message: `${request.method} ${request.url} returned ${response.status}: ${text}`
|
|
18612
|
+
}))))), catchTag2("HttpClientError", (error) => fail6(new GitHubFailed({ message: error.message }))));
|
|
18613
|
+
const json = (schema) => (request) => send(request).pipe(flatMap4(schemaBodyJson(schema)), catchTags2({
|
|
18614
|
+
HttpClientError: (error) => fail6(new GitHubFailed({ message: error.message })),
|
|
18615
|
+
SchemaError: (error) => fail6(new GitHubFailed({
|
|
18616
|
+
message: `unexpected response to ${request.url}: ${error.message}`
|
|
18617
|
+
}))
|
|
18618
|
+
}));
|
|
18619
|
+
const stackOf = (pullRequest) => json(Stacks)(get4(repo(`/stacks?pull_request=${pullRequest}`))).pipe(map7((stacks) => map(fromNullishOr(stacks[0]), (stack) => ({
|
|
18620
|
+
number: stack.number,
|
|
18621
|
+
open: stack.pull_requests.filter(({ state }) => state === "open").map(({ number }) => number)
|
|
18622
|
+
}))));
|
|
18623
|
+
const mergeRequest = (path) => json(MergeResult)(get4(repo(path)));
|
|
18624
|
+
const fromBranch = (branch, state) => repo(`/pulls?state=${state}&head=${repository.owner}:${encodeURIComponent(branch)}`);
|
|
18625
|
+
return {
|
|
18626
|
+
addToStack: (stack, pullRequests) => send(post(repo(`/stacks/${stack}/add`)).pipe(bodyOf({ pull_requests: [...pullRequests] }))).pipe(asVoid2),
|
|
18627
|
+
createPullRequest: ({ base, body, draft, head, title }) => json(CreatedPullRequest)(post(repo("/pulls")).pipe(bodyOf({ base, body, draft, head, title }))).pipe(map7(({ number }) => number)),
|
|
18628
|
+
createStack: (pullRequests) => json(CreatedStack)(post(repo("/stacks")).pipe(bodyOf({ pull_requests: [...pullRequests] }))).pipe(map7(({ number }) => number)),
|
|
18629
|
+
defaultBranch: json(RepositoryDetails)(get4(repo(""))).pipe(map7(({ default_branch }) => default_branch)),
|
|
18630
|
+
mergePullRequest: (pullRequest) => send(put(repo(`/pulls/${pullRequest}/merge`)).pipe(bodyOf({ merge_method: "squash" }))).pipe(asVoid2),
|
|
18631
|
+
mergeStack: (pullRequest) => gen2(function* () {
|
|
18632
|
+
const started = yield* json(StartedMerge)(put(repo(`/pulls/${pullRequest}/merge-async`)).pipe(bodyOf({
|
|
18633
|
+
merge_action: "default",
|
|
18634
|
+
merge_method: "squash"
|
|
18635
|
+
})));
|
|
18636
|
+
const { uuid } = started.details;
|
|
18637
|
+
if (uuid === undefined) {
|
|
18638
|
+
return yield* new GitHubFailed({
|
|
18639
|
+
message: `merging #${pullRequest} returned no request to follow`
|
|
18640
|
+
});
|
|
18641
|
+
}
|
|
18642
|
+
const settled = yield* mergeRequest(`/pulls/${pullRequest}/merge-async/${uuid}`).pipe(repeat2({
|
|
18643
|
+
schedule: spaced("5 seconds"),
|
|
18644
|
+
until: ({ status }) => status !== "pending"
|
|
18645
|
+
}));
|
|
18646
|
+
if (settled.status === "merged" || settled.status === "enqueued") {
|
|
18647
|
+
return settled.status;
|
|
18648
|
+
}
|
|
18649
|
+
return yield* new GitHubFailed({
|
|
18650
|
+
message: `merging #${pullRequest} ended ${settled.status}: ${settled.details.message}`
|
|
18651
|
+
});
|
|
18652
|
+
}),
|
|
18653
|
+
mergedHeads: (branch) => json(ClosedPullRequests)(get4(fromBranch(branch, "closed"))).pipe(map7((closed) => closed.filter(({ merged_at }) => merged_at !== null).map(({ head }) => head.sha))),
|
|
18654
|
+
openPullRequest: (branch) => json(PullRequests)(get4(fromBranch(branch, "open"))).pipe(map7((found) => map(fromNullishOr(found[0]), ({ base, body, draft, number, title }) => ({
|
|
18655
|
+
base: base.ref,
|
|
18656
|
+
body: body ?? "",
|
|
18657
|
+
draft: draft ?? false,
|
|
18658
|
+
number,
|
|
18659
|
+
title
|
|
18660
|
+
})))),
|
|
18661
|
+
setBase: (pullRequest, base) => send(patch(repo(`/pulls/${pullRequest}`)).pipe(bodyOf({ base }))).pipe(asVoid2),
|
|
18662
|
+
setDescription: (pullRequest, { body, title }) => send(patch(repo(`/pulls/${pullRequest}`)).pipe(bodyOf({ body, title }))).pipe(asVoid2),
|
|
18663
|
+
stackOf,
|
|
18664
|
+
unstack: (stack) => send(post(repo(`/stacks/${stack}/unstack`))).pipe(asVoid2)
|
|
18665
|
+
};
|
|
18666
|
+
}));
|
|
18667
|
+
};
|
|
18668
|
+
|
|
18547
18669
|
// src/cli/gh/stack/trunk.ts
|
|
18548
18670
|
class TrunkMismatch extends TaggedError2("TrunkMismatch") {
|
|
18549
18671
|
}
|
|
@@ -18555,6 +18677,11 @@ var checkTrunk = (branch) => gen2(function* () {
|
|
|
18555
18677
|
});
|
|
18556
18678
|
}
|
|
18557
18679
|
});
|
|
18680
|
+
var checkedTrunk = gen2(function* () {
|
|
18681
|
+
const branch = yield* (yield* GitHub).defaultBranch;
|
|
18682
|
+
yield* checkTrunk(branch);
|
|
18683
|
+
return branch;
|
|
18684
|
+
});
|
|
18558
18685
|
|
|
18559
18686
|
// src/cli/gh/setup.ts
|
|
18560
18687
|
class NotSetUp extends TaggedError2("NotSetUp") {
|
|
@@ -18688,121 +18815,6 @@ var init = gen2(function* () {
|
|
|
18688
18815
|
});
|
|
18689
18816
|
var initCommand = make31("init", {}, () => asUserError(init)).pipe(withHelp("Set up a git checkout for the other commands", "Makes it a jj repository colocated with git, unless it already is one, and copies git's name and email into the repository's jj config where jj has none. Where neither has them, set them with `jj config set --user`.", "Reads the default branch from `origin`, fetches and tracks it, and sets `trunk()` to it in the repository's config. jj's own `trunk()` guesses from branch names, so without this a default branch it doesn't guess would be the base of pull requests but not of local rebases. `submit`, `merge` and `sync` check that the two agree.", "Sets the `jj gh` alias to run `jj-octo` from `PATH`, in the repository's config, unless the repository has one, such as one that runs another build.", "Needs no GitHub token. Running it again changes nothing that is already set up. Run it again after the default branch changes on GitHub. Fails with what it couldn't set up, after setting up the rest. Where it can't make a colocated jj repository, such as in a git worktree, it sets up nothing else."));
|
|
18690
18817
|
|
|
18691
|
-
// src/clients/github/openapi.ts
|
|
18692
|
-
var responseOf = () => (schema) => schema;
|
|
18693
|
-
var bodyOf = (body) => bodyJsonUnsafe(body);
|
|
18694
|
-
|
|
18695
|
-
// src/clients/github/types.ts
|
|
18696
|
-
var PullRequestNumberSchema = Number6.pipe(brand("PullRequestNumber"));
|
|
18697
|
-
var StackNumberSchema = Number6.pipe(brand("StackNumber"));
|
|
18698
|
-
|
|
18699
|
-
// src/clients/github/client.ts
|
|
18700
|
-
class GitHubFailed extends TaggedError2("GitHubFailed") {
|
|
18701
|
-
}
|
|
18702
|
-
|
|
18703
|
-
class GitHub extends Service()("jj-octo/GitHub") {
|
|
18704
|
-
}
|
|
18705
|
-
var CreatedPullRequest = responseOf()(Struct({ number: PullRequestNumberSchema }));
|
|
18706
|
-
var CreatedStack = responseOf()(Struct({ number: StackNumberSchema }));
|
|
18707
|
-
var PullRequests = responseOf()(ArraySchema(Struct({
|
|
18708
|
-
base: Struct({ ref: String4 }),
|
|
18709
|
-
body: NullOr(String4),
|
|
18710
|
-
draft: optionalKey2(Boolean3),
|
|
18711
|
-
number: PullRequestNumberSchema,
|
|
18712
|
-
title: String4
|
|
18713
|
-
})));
|
|
18714
|
-
var ClosedPullRequests = responseOf()(ArraySchema(Struct({
|
|
18715
|
-
head: Struct({ sha: String4 }),
|
|
18716
|
-
merged_at: NullOr(String4)
|
|
18717
|
-
})));
|
|
18718
|
-
var StackedPullRequest = Struct({
|
|
18719
|
-
number: PullRequestNumberSchema,
|
|
18720
|
-
state: Literals(["closed", "open"])
|
|
18721
|
-
});
|
|
18722
|
-
var Stacks = responseOf()(ArraySchema(Struct({ number: StackNumberSchema, pull_requests: ArraySchema(StackedPullRequest) })));
|
|
18723
|
-
var RepositoryDetails = responseOf()(Struct({ default_branch: String4 }));
|
|
18724
|
-
var MergeRequest = Struct({
|
|
18725
|
-
details: Struct({
|
|
18726
|
-
message: String4,
|
|
18727
|
-
uuid: optionalKey2(String4)
|
|
18728
|
-
}),
|
|
18729
|
-
status: Literals(["enqueued", "failed", "merged", "pending"])
|
|
18730
|
-
});
|
|
18731
|
-
var StartedMerge = responseOf()(MergeRequest);
|
|
18732
|
-
var MergeResult = responseOf()(MergeRequest);
|
|
18733
|
-
var GitHubLive = (repository, connection) => {
|
|
18734
|
-
const repo = (path) => `/repos/${repository.owner}/${repository.name}${path}`;
|
|
18735
|
-
return effect(GitHub, gen2(function* () {
|
|
18736
|
-
const requestInit = match(connection.proxy, {
|
|
18737
|
-
onNone: () => ({}),
|
|
18738
|
-
onSome: ({ ca, url }) => match(ca, {
|
|
18739
|
-
onNone: () => ({ proxy: url }),
|
|
18740
|
-
onSome: (certificates) => ({ proxy: url, tls: { ca: certificates } })
|
|
18741
|
-
})
|
|
18742
|
-
});
|
|
18743
|
-
const client = (yield* HttpClient).pipe(mapRequest((request) => request.pipe(prependUrl("https://api.github.com"), acceptJson, bearerToken(connection.token), setHeaders({
|
|
18744
|
-
"user-agent": "jj-octo",
|
|
18745
|
-
"x-github-api-version": "2022-11-28"
|
|
18746
|
-
}))));
|
|
18747
|
-
const send = (request) => client.execute(request).pipe(provideService2(RequestInit, requestInit), flatMap4((response) => response.status < 400 ? succeed6(response) : response.text.pipe(orElseSucceed2(() => ""), flatMap4((text) => fail6(new GitHubFailed({
|
|
18748
|
-
message: `${request.method} ${request.url} returned ${response.status}: ${text}`
|
|
18749
|
-
}))))), catchTag2("HttpClientError", (error) => fail6(new GitHubFailed({ message: error.message }))));
|
|
18750
|
-
const json = (schema) => (request) => send(request).pipe(flatMap4(schemaBodyJson(schema)), catchTags2({
|
|
18751
|
-
HttpClientError: (error) => fail6(new GitHubFailed({ message: error.message })),
|
|
18752
|
-
SchemaError: (error) => fail6(new GitHubFailed({
|
|
18753
|
-
message: `unexpected response to ${request.url}: ${error.message}`
|
|
18754
|
-
}))
|
|
18755
|
-
}));
|
|
18756
|
-
const stackOf = (pullRequest) => json(Stacks)(get4(repo(`/stacks?pull_request=${pullRequest}`))).pipe(map7((stacks) => map(fromNullishOr(stacks[0]), (stack) => ({
|
|
18757
|
-
number: stack.number,
|
|
18758
|
-
open: stack.pull_requests.filter(({ state }) => state === "open").map(({ number }) => number)
|
|
18759
|
-
}))));
|
|
18760
|
-
const mergeRequest = (path) => json(MergeResult)(get4(repo(path)));
|
|
18761
|
-
const fromBranch = (branch, state) => repo(`/pulls?state=${state}&head=${repository.owner}:${encodeURIComponent(branch)}`);
|
|
18762
|
-
return {
|
|
18763
|
-
addToStack: (stack, pullRequests) => send(post(repo(`/stacks/${stack}/add`)).pipe(bodyOf({ pull_requests: [...pullRequests] }))).pipe(asVoid2),
|
|
18764
|
-
createPullRequest: ({ base, body, draft, head, title }) => json(CreatedPullRequest)(post(repo("/pulls")).pipe(bodyOf({ base, body, draft, head, title }))).pipe(map7(({ number }) => number)),
|
|
18765
|
-
createStack: (pullRequests) => json(CreatedStack)(post(repo("/stacks")).pipe(bodyOf({ pull_requests: [...pullRequests] }))).pipe(map7(({ number }) => number)),
|
|
18766
|
-
defaultBranch: json(RepositoryDetails)(get4(repo(""))).pipe(map7(({ default_branch }) => default_branch)),
|
|
18767
|
-
mergePullRequest: (pullRequest) => send(put(repo(`/pulls/${pullRequest}/merge`)).pipe(bodyOf({ merge_method: "squash" }))).pipe(asVoid2),
|
|
18768
|
-
mergeStack: (pullRequest) => gen2(function* () {
|
|
18769
|
-
const started = yield* json(StartedMerge)(put(repo(`/pulls/${pullRequest}/merge-async`)).pipe(bodyOf({
|
|
18770
|
-
merge_action: "default",
|
|
18771
|
-
merge_method: "squash"
|
|
18772
|
-
})));
|
|
18773
|
-
const { uuid } = started.details;
|
|
18774
|
-
if (uuid === undefined) {
|
|
18775
|
-
return yield* new GitHubFailed({
|
|
18776
|
-
message: `merging #${pullRequest} returned no request to follow`
|
|
18777
|
-
});
|
|
18778
|
-
}
|
|
18779
|
-
const settled = yield* mergeRequest(`/pulls/${pullRequest}/merge-async/${uuid}`).pipe(repeat2({
|
|
18780
|
-
schedule: spaced("5 seconds"),
|
|
18781
|
-
until: ({ status }) => status !== "pending"
|
|
18782
|
-
}));
|
|
18783
|
-
if (settled.status === "merged" || settled.status === "enqueued") {
|
|
18784
|
-
return settled.status;
|
|
18785
|
-
}
|
|
18786
|
-
return yield* new GitHubFailed({
|
|
18787
|
-
message: `merging #${pullRequest} ended ${settled.status}: ${settled.details.message}`
|
|
18788
|
-
});
|
|
18789
|
-
}),
|
|
18790
|
-
mergedHeads: (branch) => json(ClosedPullRequests)(get4(fromBranch(branch, "closed"))).pipe(map7((closed) => closed.filter(({ merged_at }) => merged_at !== null).map(({ head }) => head.sha))),
|
|
18791
|
-
openPullRequest: (branch) => json(PullRequests)(get4(fromBranch(branch, "open"))).pipe(map7((found) => map(fromNullishOr(found[0]), ({ base, body, draft, number, title }) => ({
|
|
18792
|
-
base: base.ref,
|
|
18793
|
-
body: body ?? "",
|
|
18794
|
-
draft: draft ?? false,
|
|
18795
|
-
number,
|
|
18796
|
-
title
|
|
18797
|
-
})))),
|
|
18798
|
-
setBase: (pullRequest, base) => send(patch(repo(`/pulls/${pullRequest}`)).pipe(bodyOf({ base }))).pipe(asVoid2),
|
|
18799
|
-
setDescription: (pullRequest, { body, title }) => send(patch(repo(`/pulls/${pullRequest}`)).pipe(bodyOf({ body, title }))).pipe(asVoid2),
|
|
18800
|
-
stackOf,
|
|
18801
|
-
unstack: (stack) => send(post(repo(`/stacks/${stack}/unstack`))).pipe(asVoid2)
|
|
18802
|
-
};
|
|
18803
|
-
}));
|
|
18804
|
-
};
|
|
18805
|
-
|
|
18806
18818
|
// src/clients/github/connection.ts
|
|
18807
18819
|
var authKey = "jj-octo.github-auth";
|
|
18808
18820
|
var ownToken = gen2(function* () {
|
|
@@ -19003,115 +19015,24 @@ var syncDescription = ({ bookmark, description }, pullRequest, { ignoreClaudeCod
|
|
|
19003
19015
|
})), exhaustive2);
|
|
19004
19016
|
});
|
|
19005
19017
|
|
|
19006
|
-
// src/cli/gh/stack/
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
var stackContaining = (pullRequests) => gen2(function* () {
|
|
19010
|
-
const github = yield* GitHub;
|
|
19011
|
-
const found = yield* all2(pullRequests.map((pullRequest) => github.stackOf(pullRequest)), { concurrency: "unbounded" });
|
|
19012
|
-
const stacks = dedupeWith(found.flatMap((stack) => toArray(stack)), (a, b) => a.number === b.number);
|
|
19013
|
-
if (stacks.length > 1) {
|
|
19014
|
-
return yield* new SeveralStacks({
|
|
19015
|
-
message: `the pull requests belong to stacks ${stacks.map(({ number }) => `#${number}`).join(" and ")}; unstack all but one`
|
|
19016
|
-
});
|
|
19017
|
-
}
|
|
19018
|
-
return fromNullishOr(stacks[0]);
|
|
19019
|
-
});
|
|
19020
|
-
var register = (remaining, pullRequests) => gen2(function* () {
|
|
19021
|
-
const github = yield* GitHub;
|
|
19022
|
-
yield* value4(registrationFor(remaining, pullRequests)).pipe(tag3("Unchanged", () => void_3), tag3("Append", ({ pullRequests: added, stack }) => github.addToStack(stack, added).pipe(andThen2(error(`added ${added.join(", ")} to stack #${stack}`)))), tag3("Create", ({ pullRequests: all }) => github.createStack(all).pipe(flatMap4((stack) => error(`registered stack #${stack}`)))), exhaustive2);
|
|
19023
|
-
});
|
|
19024
|
-
var publish = (stack, { draft, overwriteDescriptions }) => gen2(function* () {
|
|
19025
|
-
const github = yield* GitHub;
|
|
19026
|
-
const jj = yield* Jj;
|
|
19027
|
-
const bookmarks = stack.map(({ bookmark }) => bookmark);
|
|
19028
|
-
const trunk = yield* github.defaultBranch;
|
|
19029
|
-
const bases = basesFor(bookmarks, trunk);
|
|
19030
|
-
const found = yield* all2(stack.map((change, index) => github.openPullRequest(change.bookmark).pipe(map7((pullRequest) => ({ base: bases[index] ?? trunk, change, pullRequest })))), { concurrency: "unbounded" });
|
|
19031
|
-
const targets = yield* bookmarkTargets;
|
|
19032
|
-
const pushedDescriptions = yield* descriptionsOf(bookmarks.flatMap((bookmark) => targets.get(bookmark)?.origin ?? []));
|
|
19033
|
-
const ignoreClaudeCodeFooter = yield* ignoresClaudeCodeFooter;
|
|
19034
|
-
const changes = [];
|
|
19035
|
-
for (const entry of found) {
|
|
19036
|
-
const pushed = targets.get(entry.change.bookmark)?.origin;
|
|
19037
|
-
changes.push({
|
|
19038
|
-
...entry,
|
|
19039
|
-
description: isNone2(entry.pullRequest) ? none2() : yield* syncDescription(entry.change, entry.pullRequest.value, {
|
|
19040
|
-
ignoreClaudeCodeFooter,
|
|
19041
|
-
overwriteDescriptions,
|
|
19042
|
-
pushedDescription: (pushed === undefined ? undefined : pushedDescriptions.get(pushed)) ?? ""
|
|
19043
|
-
})
|
|
19044
|
-
});
|
|
19045
|
-
}
|
|
19046
|
-
const moving = changes.flatMap(({ base, pullRequest }) => isSome2(pullRequest) && pullRequest.value.base !== base ? [pullRequest.value] : []);
|
|
19047
|
-
const wanted = changes.map(({ pullRequest }) => isSome2(pullRequest) ? some2(pullRequest.value.number) : none2());
|
|
19048
|
-
const existing = yield* stackContaining(wanted.flatMap((number) => toArray(number)));
|
|
19049
|
-
const dissolve = mustDissolve(existing, wanted, moving.length > 0);
|
|
19050
|
-
if (dissolve && isSome2(existing)) {
|
|
19051
|
-
yield* github.unstack(existing.value.number);
|
|
19052
|
-
yield* error(`dissolved stack #${existing.value.number}`);
|
|
19053
|
-
}
|
|
19054
|
-
for (const { number } of moving.filter(({ base }) => base !== trunk)) {
|
|
19055
|
-
yield* github.setBase(number, trunk);
|
|
19056
|
-
}
|
|
19057
|
-
const moved = new Set(moving.map(({ number }) => number));
|
|
19058
|
-
for (const bookmark of bookmarks.filter((name) => targets.get(name)?.tracked !== true)) {
|
|
19059
|
-
yield* jj.run(["bookmark", "track", `exact:${bookmark}`, "--remote", "origin"]);
|
|
19060
|
-
}
|
|
19061
|
-
yield* jj.run([
|
|
19062
|
-
"git",
|
|
19063
|
-
"push",
|
|
19064
|
-
...bookmarks.flatMap((bookmark) => ["--bookmark", `exact:${bookmark}`])
|
|
19065
|
-
]);
|
|
19066
|
-
const numbers = [];
|
|
19067
|
-
for (const { base, change, description, pullRequest } of changes) {
|
|
19068
|
-
if (isNone2(pullRequest)) {
|
|
19069
|
-
const created = yield* github.createPullRequest({
|
|
19070
|
-
...titleAndBody(change.description),
|
|
19071
|
-
base,
|
|
19072
|
-
draft,
|
|
19073
|
-
head: change.bookmark
|
|
19074
|
-
});
|
|
19075
|
-
yield* error(`opened #${created} for ${change.bookmark}`);
|
|
19076
|
-
numbers.push(created);
|
|
19077
|
-
} else {
|
|
19078
|
-
const { number } = pullRequest.value;
|
|
19079
|
-
if (moved.has(number) && base !== trunk) {
|
|
19080
|
-
yield* github.setBase(number, base);
|
|
19081
|
-
}
|
|
19082
|
-
if (isSome2(description)) {
|
|
19083
|
-
yield* github.setDescription(number, titleAndBody(description.value));
|
|
19084
|
-
yield* error(`updated the description of #${number}`);
|
|
19085
|
-
}
|
|
19086
|
-
numbers.push(number);
|
|
19087
|
-
}
|
|
19088
|
-
}
|
|
19089
|
-
yield* register(dissolve ? none2() : existing, numbers);
|
|
19090
|
-
});
|
|
19091
|
-
var submit = (revision, options) => gen2(function* () {
|
|
19092
|
-
yield* checkTrunk(yield* (yield* GitHub).defaultBranch);
|
|
19093
|
-
yield* publish(yield* downstackChanges(revision), options);
|
|
19094
|
-
});
|
|
19095
|
-
var republish = (revision) => gen2(function* () {
|
|
19096
|
-
if ((yield* changesIn(stackRevset(revision))).length === 0) {
|
|
19097
|
-
return;
|
|
19098
|
-
}
|
|
19099
|
-
const stack = yield* stackChanges(revision);
|
|
19100
|
-
const targets = yield* bookmarkTargets;
|
|
19101
|
-
const top = stack.findLastIndex(({ bookmark }) => targets.get(bookmark)?.origin !== undefined);
|
|
19102
|
-
if (top !== -1) {
|
|
19103
|
-
yield* publish(stack.slice(0, top + 1), { draft: true, overwriteDescriptions: false });
|
|
19104
|
-
}
|
|
19105
|
-
});
|
|
19106
|
-
|
|
19107
|
-
// src/cli/gh/sync.ts
|
|
19108
|
-
var BEHIND_ROOTS = `(${STACK_ROOTS}) ~ children(trunk())`;
|
|
19109
|
-
var bookmarksBeforeFetch = gen2(function* () {
|
|
19018
|
+
// src/cli/gh/stack/combine.ts
|
|
19019
|
+
var pushedOf = (bookmarks) => bookmarks.filter(({ remote }) => remote !== undefined);
|
|
19020
|
+
var bookmarksBeforeFetch = (revset) => gen2(function* () {
|
|
19110
19021
|
const targets = [...yield* bookmarkTargets];
|
|
19111
|
-
const
|
|
19022
|
+
const bookmarked = `(${revset}) & bookmarks()`;
|
|
19023
|
+
const bottomFirst = lines2(yield* readLog(bookmarked, String.raw`commit_id ++ "\t" ++ change_id ++ "\t" ++ current_working_copy ++ "\n"`));
|
|
19024
|
+
const children = lines2(yield* readLog(`children(${bookmarked})`, String.raw`parents.map(|p| p.commit_id()).join(",") ++ "\t" ++ change_id ++ "\n"`)).map((line) => line.split("\t"));
|
|
19112
19025
|
return bottomFirst.toReversed().flatMap((line) => {
|
|
19113
|
-
const [commit = "", change = ""] = line.split("\t");
|
|
19114
|
-
|
|
19026
|
+
const [commit = "", change = "", workingCopy] = line.split("\t");
|
|
19027
|
+
const above = children.flatMap(([parents = "", child = ""]) => parents.split(",").includes(commit) ? [child] : []);
|
|
19028
|
+
return targets.filter(([, { local }]) => local === commit).map(([name, { origin }]) => ({
|
|
19029
|
+
above,
|
|
19030
|
+
change,
|
|
19031
|
+
editing: workingCopy === "true",
|
|
19032
|
+
local: commit,
|
|
19033
|
+
name,
|
|
19034
|
+
remote: origin
|
|
19035
|
+
}));
|
|
19115
19036
|
});
|
|
19116
19037
|
});
|
|
19117
19038
|
var localCommit = (change, remote, fetched) => gen2(function* () {
|
|
@@ -19122,22 +19043,31 @@ var localCommit = (change, remote, fetched) => gen2(function* () {
|
|
|
19122
19043
|
}
|
|
19123
19044
|
return local;
|
|
19124
19045
|
});
|
|
19125
|
-
var combine4 = ({ change, local: localBefore, name, remote }, fetched) => gen2(function* () {
|
|
19046
|
+
var combine4 = ({ above, change, editing, local: localBefore, name, remote }, fetched) => gen2(function* () {
|
|
19126
19047
|
const jj = yield* Jj;
|
|
19127
19048
|
if (remote === undefined || fetched === undefined || fetched === remote) {
|
|
19128
19049
|
return true;
|
|
19129
19050
|
}
|
|
19130
19051
|
const [local, ...others] = yield* localCommit(change, remote, fetched);
|
|
19131
19052
|
if (local === undefined) {
|
|
19053
|
+
if (editing) {
|
|
19054
|
+
yield* jj.run(["edit", fetched]);
|
|
19055
|
+
}
|
|
19056
|
+
const moved = `(${anyOf(above.map((child) => `change_id(${child})`))}) ~ children(${fetched})`;
|
|
19057
|
+
if ((yield* commitsAt(moved)).length > 0) {
|
|
19058
|
+
yield* jj.run(["rebase", "--source", moved, "--destination", fetched]);
|
|
19059
|
+
yield* error(`took GitHub's rewritten ${name}, and moved what was on it there`);
|
|
19060
|
+
}
|
|
19132
19061
|
return true;
|
|
19133
19062
|
}
|
|
19134
19063
|
if (others.length > 0) {
|
|
19135
19064
|
return false;
|
|
19136
19065
|
}
|
|
19137
19066
|
const keepLocal = jj.run(["bookmark", "set", name, "-r", local, "--allow-backwards"]);
|
|
19067
|
+
const held = `::(bookmarks() ~ ${bookmarkRevset(name)}) ~ ::${remote}`;
|
|
19138
19068
|
const dropCopies = jj.run([
|
|
19139
19069
|
"abandon",
|
|
19140
|
-
`((::${fetched} | ::${remote}) & mutable()) ~ ::${bookmarkRevset(name)}`
|
|
19070
|
+
`((::${fetched} | ::${remote}) & mutable()) ~ ::${bookmarkRevset(name)} ~ (${held})`
|
|
19141
19071
|
]);
|
|
19142
19072
|
if ((yield* commitsAt(`${remote} & ::${fetched}`)).length > 0) {
|
|
19143
19073
|
yield* keepLocal;
|
|
@@ -19155,6 +19085,9 @@ var combine4 = ({ change, local: localBefore, name, remote }, fetched) => gen2(f
|
|
|
19155
19085
|
}
|
|
19156
19086
|
if (localBefore === remote) {
|
|
19157
19087
|
yield* jj.run(["rebase", "--source", `children(${local})`, "--destination", fetched]);
|
|
19088
|
+
if (editing) {
|
|
19089
|
+
yield* jj.run(["edit", fetched]);
|
|
19090
|
+
}
|
|
19158
19091
|
yield* jj.run(["abandon", local]);
|
|
19159
19092
|
yield* error(`took GitHub's rewritten ${name}, which wasn't changed locally`);
|
|
19160
19093
|
return true;
|
|
@@ -19169,6 +19102,9 @@ var combine4 = ({ change, local: localBefore, name, remote }, fetched) => gen2(f
|
|
|
19169
19102
|
return false;
|
|
19170
19103
|
});
|
|
19171
19104
|
|
|
19105
|
+
// src/cli/gh/stack/reconcile.ts
|
|
19106
|
+
var BEHIND_ROOTS = `(${STACK_ROOTS}) ~ children(trunk())`;
|
|
19107
|
+
|
|
19172
19108
|
class NotCombined extends TaggedError2("NotCombined") {
|
|
19173
19109
|
}
|
|
19174
19110
|
var rebaseStack = (root, required) => gen2(function* () {
|
|
@@ -19188,8 +19124,8 @@ var rebaseStack = (root, required) => gen2(function* () {
|
|
|
19188
19124
|
String.raw`id ++ "\n"`
|
|
19189
19125
|
]));
|
|
19190
19126
|
yield* jj.run(["rebase", "--source", root, "--destination", "trunk()", "--skip-emptied"]);
|
|
19191
|
-
const rebased =
|
|
19192
|
-
const untouched =
|
|
19127
|
+
const rebased = anyOf(changes.map((change) => `change_id(${change})`));
|
|
19128
|
+
const untouched = anyOf(conflictedCommits);
|
|
19193
19129
|
const conflictedNow = yield* changeIdsAt(`(conflicts() & (${rebased})) ~ (${untouched})`);
|
|
19194
19130
|
const mayRestore = !changes.some((change) => required.includes(change));
|
|
19195
19131
|
if (mayRestore && conflictedNow.some((change) => !conflicted.includes(change))) {
|
|
@@ -19197,23 +19133,51 @@ var rebaseStack = (root, required) => gen2(function* () {
|
|
|
19197
19133
|
yield* error(`left "${name}" and the changes on it where they were: they conflict with the trunk`);
|
|
19198
19134
|
}
|
|
19199
19135
|
});
|
|
19200
|
-
var
|
|
19136
|
+
var notMerged = (merged) => (name) => merged.every(({ bookmark }) => bookmark !== name);
|
|
19137
|
+
var mergedHeadsOf = (bookmarks) => gen2(function* () {
|
|
19201
19138
|
const github = yield* GitHub;
|
|
19202
|
-
const
|
|
19203
|
-
|
|
19139
|
+
const pushed = pushedOf(bookmarks);
|
|
19140
|
+
const heads = yield* forEach2(pushed, ({ name }) => github.mergedHeads(name), {
|
|
19141
|
+
concurrency: "unbounded"
|
|
19142
|
+
});
|
|
19143
|
+
return Object.fromEntries(pushed.map(({ name }, index) => [name, heads[index] ?? []]));
|
|
19204
19144
|
});
|
|
19205
|
-
var
|
|
19145
|
+
var mergedIn = (bookmarks, heads) => gen2(function* () {
|
|
19206
19146
|
const jj = yield* Jj;
|
|
19207
|
-
const
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19147
|
+
const merged = yield* forEach2(bookmarks, ({ change, local, name, remote }) => gen2(function* () {
|
|
19148
|
+
const merges = heads[name] ?? [];
|
|
19149
|
+
const rebasedOnly = remote !== undefined && merges.includes(remote) && (yield* jj.read(["interdiff", "--from", remote, "--to", local, "--summary"])).trim() === "";
|
|
19150
|
+
return merges.includes(local) || rebasedOnly ? [{ bookmark: name, changeId: change }] : [];
|
|
19151
|
+
}));
|
|
19152
|
+
return merged.flat();
|
|
19153
|
+
});
|
|
19154
|
+
var mergedOnGitHub = (bookmarks) => flatMap4(mergedHeadsOf(bookmarks), (heads) => mergedIn(bookmarks, heads));
|
|
19155
|
+
var forget = (names) => gen2(function* () {
|
|
19156
|
+
const listed = yield* bookmarkTargets;
|
|
19157
|
+
const present = names.filter((name) => listed.has(name));
|
|
19158
|
+
if (present.length > 0) {
|
|
19159
|
+
const jj = yield* Jj;
|
|
19160
|
+
yield* jj.run([
|
|
19161
|
+
"bookmark",
|
|
19162
|
+
"forget",
|
|
19163
|
+
"--include-remotes",
|
|
19164
|
+
...present.map((name) => `exact:${name}`)
|
|
19165
|
+
]);
|
|
19215
19166
|
}
|
|
19216
|
-
|
|
19167
|
+
});
|
|
19168
|
+
var reconcile = ({
|
|
19169
|
+
before,
|
|
19170
|
+
merged,
|
|
19171
|
+
scope
|
|
19172
|
+
}) => gen2(function* () {
|
|
19173
|
+
const jj = yield* Jj;
|
|
19174
|
+
const branches = scope === "every stack" ? [] : [scope.trunk, ...pushedOf(before).map(({ name }) => name)].flatMap((branch) => [
|
|
19175
|
+
"--branch",
|
|
19176
|
+
`exact:${branch}`
|
|
19177
|
+
]);
|
|
19178
|
+
yield* jj.run(["git", "fetch", ...branches]);
|
|
19179
|
+
yield* forget(merged.map(({ bookmark }) => bookmark));
|
|
19180
|
+
const mergedRevset = `(${anyOf(merged.map(({ changeId }) => `change_id(${changeId})`))}) & mutable()`;
|
|
19217
19181
|
const required = yield* changeIdsAt(`(${mergedRevset}):: ~ (${mergedRevset})`);
|
|
19218
19182
|
if (merged.length > 0) {
|
|
19219
19183
|
yield* jj.run(["abandon", mergedRevset]);
|
|
@@ -19221,18 +19185,20 @@ var sync4 = (known = []) => gen2(function* () {
|
|
|
19221
19185
|
}
|
|
19222
19186
|
const fetched = yield* bookmarkTargets;
|
|
19223
19187
|
const notCombined = [];
|
|
19224
|
-
for (const bookmark of before.filter(({ name }) => merged
|
|
19188
|
+
for (const bookmark of before.filter(({ name }) => notMerged(merged)(name))) {
|
|
19225
19189
|
if (!(yield* combine4(bookmark, fetched.get(bookmark.name)?.origin))) {
|
|
19226
19190
|
notCombined.push(bookmark.name);
|
|
19227
19191
|
}
|
|
19228
19192
|
}
|
|
19229
|
-
|
|
19193
|
+
const roots = scope === "every stack" ? BEHIND_ROOTS : `(${BEHIND_ROOTS}) & ::(${anyOf([
|
|
19194
|
+
...scope.bookmarks.map((name) => bookmarkRevset(name)),
|
|
19195
|
+
...scope.unnamed.map((change) => `change_id(${change})`)
|
|
19196
|
+
])})`;
|
|
19197
|
+
for (const root of yield* commitsAt(roots)) {
|
|
19230
19198
|
yield* rebaseStack(root, required);
|
|
19231
19199
|
}
|
|
19232
|
-
const
|
|
19233
|
-
|
|
19234
|
-
yield* jj.run(["bookmark", "forget", "--include-remotes", `exact:${name}`]);
|
|
19235
|
-
}
|
|
19200
|
+
const onStacks = new Set(scope === "every stack" ? before.map(({ name }) => name) : scope.bookmarks);
|
|
19201
|
+
yield* forget((yield* bookmarksAt("::trunk()")).filter((name) => onStacks.has(name)));
|
|
19236
19202
|
if (notCombined.length > 0) {
|
|
19237
19203
|
yield* new NotCombined({
|
|
19238
19204
|
message: [
|
|
@@ -19244,8 +19210,201 @@ var sync4 = (known = []) => gen2(function* () {
|
|
|
19244
19210
|
});
|
|
19245
19211
|
}
|
|
19246
19212
|
});
|
|
19213
|
+
|
|
19214
|
+
// src/cli/gh/stack/sync-stack.ts
|
|
19215
|
+
class BranchesDeleted extends TaggedError2("BranchesDeleted") {
|
|
19216
|
+
}
|
|
19217
|
+
var whyDeleted = (heads, { remote }) => {
|
|
19218
|
+
if (remote !== undefined && heads.includes(remote)) {
|
|
19219
|
+
return "its pushed commit merged, and the change was edited since";
|
|
19220
|
+
}
|
|
19221
|
+
return heads.length > 0 ? "a pull request from it merged, but not the commit pushed for this change" : "no pull request from it merged";
|
|
19222
|
+
};
|
|
19223
|
+
var changedOnGitHub = (bookmarks) => gen2(function* () {
|
|
19224
|
+
const pushed = pushedOf(bookmarks);
|
|
19225
|
+
if (pushed.length === 0) {
|
|
19226
|
+
return { changed: [], deleted: [] };
|
|
19227
|
+
}
|
|
19228
|
+
const git = yield* Git;
|
|
19229
|
+
const gitDirectory = (yield* (yield* Jj).read(["git", "root"])).trim();
|
|
19230
|
+
const output = yield* git.read([
|
|
19231
|
+
"--git-dir",
|
|
19232
|
+
gitDirectory,
|
|
19233
|
+
"ls-remote",
|
|
19234
|
+
"origin",
|
|
19235
|
+
...pushed.map(({ name }) => `refs/heads/${name}`)
|
|
19236
|
+
]);
|
|
19237
|
+
const onGitHub = new Map(lines2(output).map((line) => {
|
|
19238
|
+
const [commit = "", ref = ""] = line.split("\t");
|
|
19239
|
+
return [ref.replace(/^refs\/heads\//u, ""), commit];
|
|
19240
|
+
}));
|
|
19241
|
+
return {
|
|
19242
|
+
changed: pushed.filter(({ name, remote }) => onGitHub.has(name) && onGitHub.get(name) !== remote).map(({ name }) => name),
|
|
19243
|
+
deleted: pushed.filter(({ name }) => !onGitHub.has(name))
|
|
19244
|
+
};
|
|
19245
|
+
});
|
|
19246
|
+
var syncStack = (revision, trunk) => gen2(function* () {
|
|
19247
|
+
const downstack = downstackRevset(revision);
|
|
19248
|
+
const before = yield* bookmarksBeforeFetch(downstack);
|
|
19249
|
+
const [heads, onGitHub] = yield* all2([mergedHeadsOf(before), changedOnGitHub(before)], {
|
|
19250
|
+
concurrency: "unbounded"
|
|
19251
|
+
});
|
|
19252
|
+
const merged = yield* mergedIn(before, heads);
|
|
19253
|
+
const changed = onGitHub.changed.filter(notMerged(merged));
|
|
19254
|
+
const deleted = onGitHub.deleted.filter(({ name }) => notMerged(merged)(name));
|
|
19255
|
+
if (deleted.length > 0) {
|
|
19256
|
+
return yield* new BranchesDeleted({
|
|
19257
|
+
message: [
|
|
19258
|
+
"these branches were deleted on GitHub, so nothing was changed:",
|
|
19259
|
+
...deleted.map((bookmark) => `${bookmark.name}: ${whyDeleted(heads[bookmark.name] ?? [], bookmark)}`),
|
|
19260
|
+
"to push a change again, run `jj bookmark forget --include-remotes <name>`, then `jj bookmark create <name> -r <change>`; to drop one, run `jj abandon <change>`"
|
|
19261
|
+
].join(`
|
|
19262
|
+
`)
|
|
19263
|
+
});
|
|
19264
|
+
}
|
|
19265
|
+
const found = [
|
|
19266
|
+
...merged.map(({ bookmark }) => `${bookmark} merged`),
|
|
19267
|
+
...changed.map((name) => `${name} changed`)
|
|
19268
|
+
];
|
|
19269
|
+
if (found.length === 0) {
|
|
19270
|
+
return false;
|
|
19271
|
+
}
|
|
19272
|
+
yield* error(`on GitHub, ${found.join(", ")}; syncing the stack first`);
|
|
19273
|
+
const whole = stackRevset(revision);
|
|
19274
|
+
yield* reconcile({
|
|
19275
|
+
before,
|
|
19276
|
+
merged,
|
|
19277
|
+
scope: {
|
|
19278
|
+
bookmarks: yield* bookmarksAt(whole),
|
|
19279
|
+
trunk,
|
|
19280
|
+
unnamed: yield* changeIdsAt(`((${whole}) | (@ & children(${whole}))) ~ bookmarks()`)
|
|
19281
|
+
}
|
|
19282
|
+
});
|
|
19283
|
+
return true;
|
|
19284
|
+
});
|
|
19285
|
+
|
|
19286
|
+
// src/cli/gh/stack/publish.ts
|
|
19287
|
+
class SeveralStacks extends TaggedError2("SeveralStacks") {
|
|
19288
|
+
}
|
|
19289
|
+
var stackContaining = (pullRequests) => gen2(function* () {
|
|
19290
|
+
const github = yield* GitHub;
|
|
19291
|
+
const found = yield* all2(pullRequests.map((pullRequest) => github.stackOf(pullRequest)), { concurrency: "unbounded" });
|
|
19292
|
+
const stacks = dedupeWith(found.flatMap((stack) => toArray(stack)), (a, b) => a.number === b.number);
|
|
19293
|
+
if (stacks.length > 1) {
|
|
19294
|
+
return yield* new SeveralStacks({
|
|
19295
|
+
message: `the pull requests belong to stacks ${stacks.map(({ number }) => `#${number}`).join(" and ")}; unstack all but one`
|
|
19296
|
+
});
|
|
19297
|
+
}
|
|
19298
|
+
return fromNullishOr(stacks[0]);
|
|
19299
|
+
});
|
|
19300
|
+
var register = (remaining, pullRequests) => gen2(function* () {
|
|
19301
|
+
const github = yield* GitHub;
|
|
19302
|
+
yield* value4(registrationFor(remaining, pullRequests)).pipe(tag3("Unchanged", () => void_3), tag3("Append", ({ pullRequests: added, stack }) => github.addToStack(stack, added).pipe(andThen2(error(`added ${added.join(", ")} to stack #${stack}`)))), tag3("Create", ({ pullRequests: all }) => github.createStack(all).pipe(flatMap4((stack) => error(`registered stack #${stack}`)))), exhaustive2);
|
|
19303
|
+
});
|
|
19304
|
+
var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function* () {
|
|
19305
|
+
const github = yield* GitHub;
|
|
19306
|
+
const jj = yield* Jj;
|
|
19307
|
+
const bookmarks = stack.map(({ bookmark }) => bookmark);
|
|
19308
|
+
const bases = basesFor(bookmarks, trunk);
|
|
19309
|
+
const found = yield* all2(stack.map((change, index) => github.openPullRequest(change.bookmark).pipe(map7((pullRequest) => ({ base: bases[index] ?? trunk, change, pullRequest })))), { concurrency: "unbounded" });
|
|
19310
|
+
const targets = yield* bookmarkTargets;
|
|
19311
|
+
const pushedDescriptions = yield* descriptionsOf(bookmarks.flatMap((bookmark) => targets.get(bookmark)?.origin ?? []));
|
|
19312
|
+
const ignoreClaudeCodeFooter = yield* ignoresClaudeCodeFooter;
|
|
19313
|
+
const changes = [];
|
|
19314
|
+
for (const entry of found) {
|
|
19315
|
+
const pushed = targets.get(entry.change.bookmark)?.origin;
|
|
19316
|
+
changes.push({
|
|
19317
|
+
...entry,
|
|
19318
|
+
description: isNone2(entry.pullRequest) ? none2() : yield* syncDescription(entry.change, entry.pullRequest.value, {
|
|
19319
|
+
ignoreClaudeCodeFooter,
|
|
19320
|
+
overwriteDescriptions,
|
|
19321
|
+
pushedDescription: (pushed === undefined ? undefined : pushedDescriptions.get(pushed)) ?? ""
|
|
19322
|
+
})
|
|
19323
|
+
});
|
|
19324
|
+
}
|
|
19325
|
+
const moving = changes.flatMap(({ base, pullRequest }) => isSome2(pullRequest) && pullRequest.value.base !== base ? [pullRequest.value] : []);
|
|
19326
|
+
const wanted = changes.map(({ pullRequest }) => isSome2(pullRequest) ? some2(pullRequest.value.number) : none2());
|
|
19327
|
+
const existing = yield* stackContaining(wanted.flatMap((number) => toArray(number)));
|
|
19328
|
+
const dissolve = mustDissolve(existing, wanted, moving.length > 0);
|
|
19329
|
+
if (dissolve && isSome2(existing)) {
|
|
19330
|
+
yield* github.unstack(existing.value.number);
|
|
19331
|
+
yield* error(`dissolved stack #${existing.value.number}`);
|
|
19332
|
+
}
|
|
19333
|
+
for (const { number } of moving.filter(({ base }) => base !== trunk)) {
|
|
19334
|
+
yield* github.setBase(number, trunk);
|
|
19335
|
+
}
|
|
19336
|
+
const moved = new Set(moving.map(({ number }) => number));
|
|
19337
|
+
for (const bookmark of bookmarks.filter((name) => targets.get(name)?.tracked !== true)) {
|
|
19338
|
+
yield* jj.run(["bookmark", "track", `exact:${bookmark}`, "--remote", "origin"]);
|
|
19339
|
+
}
|
|
19340
|
+
yield* jj.run([
|
|
19341
|
+
"git",
|
|
19342
|
+
"push",
|
|
19343
|
+
...bookmarks.flatMap((bookmark) => ["--bookmark", `exact:${bookmark}`])
|
|
19344
|
+
]);
|
|
19345
|
+
const numbers = [];
|
|
19346
|
+
for (const { base, change, description, pullRequest } of changes) {
|
|
19347
|
+
if (isNone2(pullRequest)) {
|
|
19348
|
+
const created = yield* github.createPullRequest({
|
|
19349
|
+
...titleAndBody(change.description),
|
|
19350
|
+
base,
|
|
19351
|
+
draft,
|
|
19352
|
+
head: change.bookmark
|
|
19353
|
+
});
|
|
19354
|
+
yield* error(`opened #${created} for ${change.bookmark}`);
|
|
19355
|
+
numbers.push(created);
|
|
19356
|
+
} else {
|
|
19357
|
+
const { number } = pullRequest.value;
|
|
19358
|
+
if (moved.has(number) && base !== trunk) {
|
|
19359
|
+
yield* github.setBase(number, base);
|
|
19360
|
+
}
|
|
19361
|
+
if (isSome2(description)) {
|
|
19362
|
+
yield* github.setDescription(number, titleAndBody(description.value));
|
|
19363
|
+
yield* error(`updated the description of #${number}`);
|
|
19364
|
+
}
|
|
19365
|
+
numbers.push(number);
|
|
19366
|
+
}
|
|
19367
|
+
}
|
|
19368
|
+
yield* register(dissolve ? none2() : existing, numbers);
|
|
19369
|
+
});
|
|
19370
|
+
var submit = (revision, options) => gen2(function* () {
|
|
19371
|
+
const trunk = yield* checkedTrunk;
|
|
19372
|
+
const downstack = yield* downstackChanges(revision);
|
|
19373
|
+
if (!(yield* syncStack(revision, trunk))) {
|
|
19374
|
+
yield* publish(downstack, { ...options, trunk });
|
|
19375
|
+
return;
|
|
19376
|
+
}
|
|
19377
|
+
const left = yield* bookmarksAt(anyOf(downstack.map(({ bookmark }) => bookmarkRevset(bookmark))));
|
|
19378
|
+
const top = downstack.findLast(({ bookmark }) => left.includes(bookmark));
|
|
19379
|
+
if (top === undefined) {
|
|
19380
|
+
yield* error(`nothing left to submit: no bookmark of the stack is left after the sync`);
|
|
19381
|
+
return;
|
|
19382
|
+
}
|
|
19383
|
+
yield* publish(yield* downstackChanges(bookmarkRevset(top.bookmark)), { ...options, trunk });
|
|
19384
|
+
});
|
|
19385
|
+
var republish = (revision, trunk) => gen2(function* () {
|
|
19386
|
+
if ((yield* changesIn(stackRevset(revision))).length === 0) {
|
|
19387
|
+
return;
|
|
19388
|
+
}
|
|
19389
|
+
const stack = yield* stackChanges(revision);
|
|
19390
|
+
const targets = yield* bookmarkTargets;
|
|
19391
|
+
const top = stack.findLastIndex(({ bookmark }) => targets.get(bookmark)?.origin !== undefined);
|
|
19392
|
+
if (top !== -1) {
|
|
19393
|
+
yield* publish(stack.slice(0, top + 1), { draft: true, overwriteDescriptions: false, trunk });
|
|
19394
|
+
}
|
|
19395
|
+
});
|
|
19396
|
+
|
|
19397
|
+
// src/cli/gh/sync.ts
|
|
19398
|
+
var sync4 = (known = []) => gen2(function* () {
|
|
19399
|
+
const before = yield* bookmarksBeforeFetch("mutable()");
|
|
19400
|
+
const merged = [
|
|
19401
|
+
...known,
|
|
19402
|
+
...yield* mergedOnGitHub(before.filter(({ name }) => notMerged(known)(name)))
|
|
19403
|
+
];
|
|
19404
|
+
yield* reconcile({ before, merged, scope: "every stack" });
|
|
19405
|
+
});
|
|
19247
19406
|
var syncCommand = make31("sync", {}, () => asUserError(gen2(function* () {
|
|
19248
|
-
yield*
|
|
19407
|
+
yield* checkedTrunk;
|
|
19249
19408
|
yield* sync4();
|
|
19250
19409
|
}).pipe(provide3(GitHubFromOrigin)))).pipe(withHelp("Fetch, and rebase every change onto the trunk", "Fetches, then rebases every change that isn't merged yet onto the trunk, keeping each on the changes below it. A change whose pull request GitHub merged is abandoned and its bookmark forgotten, unless it was edited after the merge.", "A stack that would conflict with the trunk is left where it was, and named. Rebase it yourself with `jj rebase` when you want to resolve it. The rest of a stack whose changes merged is rebased even where it conflicts, and the conflict is recorded in the change.", "Commits added to a branch on GitHub are squashed into the branch's local change, whether or not that change was also edited locally. Where GitHub only rebased a branch, the local change is kept.", "Fails before fetching if `trunk()` isn't GitHub's default branch, which `jj-octo init` sets.", "Pushes nothing. Run `jj gh submit` to update the pull requests of a change and the ones below it."));
|
|
19251
19410
|
|
|
@@ -19281,8 +19440,7 @@ var mergeOnGitHub = ({ base, number }, trunk) => gen2(function* () {
|
|
|
19281
19440
|
return "merged";
|
|
19282
19441
|
});
|
|
19283
19442
|
var merge5 = (revision) => gen2(function* () {
|
|
19284
|
-
const trunk = yield*
|
|
19285
|
-
yield* checkTrunk(trunk);
|
|
19443
|
+
const trunk = yield* checkedTrunk;
|
|
19286
19444
|
const changes = yield* downstackChanges(revision);
|
|
19287
19445
|
const top = changes.at(-1)?.bookmark ?? "";
|
|
19288
19446
|
const targets = yield* bookmarkTargets;
|
|
@@ -19301,7 +19459,7 @@ var merge5 = (revision) => gen2(function* () {
|
|
|
19301
19459
|
yield* sync4(changes);
|
|
19302
19460
|
for (const bookmark of above) {
|
|
19303
19461
|
if ((yield* commitsAt(bookmarkRevset(bookmark))).length > 0) {
|
|
19304
|
-
yield* republish(bookmarkRevset(bookmark));
|
|
19462
|
+
yield* republish(bookmarkRevset(bookmark), trunk);
|
|
19305
19463
|
break;
|
|
19306
19464
|
}
|
|
19307
19465
|
}
|
|
@@ -19314,7 +19472,7 @@ var submitCommand = make31("submit", {
|
|
|
19314
19472
|
overwriteDescriptions: Boolean6("overwrite-descriptions").pipe(withDefault5(false), withDescription2("Where a description changed both locally and on GitHub, send the local one instead of merging the two")),
|
|
19315
19473
|
publish: Boolean6("publish").pipe(withDefault5(false), withDescription2("Open new pull requests ready for review, instead of as drafts")),
|
|
19316
19474
|
revision: revisionFlag("The top change to submit")
|
|
19317
|
-
}, ({ overwriteDescriptions, publish, revision }) => asUserError(submit(revision, { draft: !publish, overwriteDescriptions }).pipe(provide3(GitHubFromOrigin)))).pipe(withHelp("Push a change and the ones below it, and open their pull requests", "Pushes the bookmark of each of those changes. An empty working copy means the change below it, so from the top of a stack this submits all of it. Opens a pull request for each branch that has none, with the title and body from the change's description. Points each pull request at the branch below it, and the bottom one at the default branch.", "New pull requests are opened as drafts, or ready for review with `--publish`. The flag doesn't change a pull request that already exists.", "Keeps each pull request's title and body the same as its change's description. A description edited on GitHub is written into the change, one edited locally is sent to the pull request, and edits on both sides are merged line by line. Edits to the same lines stop the submit before anything is pushed.", "Registers the pull requests as one GitHub stack. A stack of one change is a single pull request, which GitHub has no stack for.", "If changes were reordered, inserted or dropped since the last submit, the GitHub stack is dissolved and registered again under a new number. The pull requests stay open, including the one of a dropped change."));
|
|
19475
|
+
}, ({ overwriteDescriptions, publish, revision }) => asUserError(submit(revision, { draft: !publish, overwriteDescriptions }).pipe(provide3(GitHubFromOrigin)))).pipe(withHelp("Push a change and the ones below it, and open their pull requests", "Pushes the bookmark of each of those changes. An empty working copy means the change below it, so from the top of a stack this submits all of it. Opens a pull request for each branch that has none, with the title and body from the change's description. Points each pull request at the branch below it, and the bottom one at the default branch.", "New pull requests are opened as drafts, or ready for review with `--publish`. The flag doesn't change a pull request that already exists.", "Keeps each pull request's title and body the same as its change's description. A description edited on GitHub is written into the change, one edited locally is sent to the pull request, and edits on both sides are merged line by line. Edits to the same lines stop the submit before anything is pushed.", "First, where any of those changes' pull requests merged or any of their branches changed on GitHub, syncs their stack as `jj gh sync` does, and leaves other stacks alone. That drops the merged changes, takes what GitHub added to the branches, and rebases the rest onto the trunk, so the push doesn't undo GitHub's work.", "Stops before changing anything where one of their branches was deleted on GitHub and their change doesn't count as merged: no pull request from it merged, another commit merged, or the change was edited since. It says how to push the change again, or drop it.", "Registers the pull requests as one GitHub stack. A stack of one change is a single pull request, which GitHub has no stack for.", "If changes were reordered, inserted or dropped since the last submit, the GitHub stack is dissolved and registered again under a new number. The pull requests stay open, including the one of a dropped change."));
|
|
19318
19476
|
|
|
19319
19477
|
// src/cli/gh/command.ts
|
|
19320
19478
|
var jjGhCommand = make31("jj gh").pipe(withSubcommands([
|