jj-octo 0.6.0 → 0.7.0
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 +401 -251
- 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.0",
|
|
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: {
|
|
@@ -18439,6 +18439,7 @@ var SCRATCH = '(working_copies() & empty() & description(exact:""))';
|
|
|
18439
18439
|
var stackRevset = (revision) => `(reachable(${revision}, mutable()) & (${STACK_ROOTS})::) ~ ${SCRATCH}`;
|
|
18440
18440
|
var downstackRevset = (revision) => `::(${revision}) & (${stackRevset(revision)})`;
|
|
18441
18441
|
var upstackRevset = (revision) => `(${revision})+:: & (${stackRevset(revision)})`;
|
|
18442
|
+
var anyOf = (revsets) => ["none()", ...revsets].join(" | ");
|
|
18442
18443
|
var bookmarkRevset = (bookmark) => `bookmarks(exact:"${bookmark}")`;
|
|
18443
18444
|
var lines2 = (output) => output.split(`
|
|
18444
18445
|
`).filter((line) => line !== "");
|
|
@@ -18544,6 +18545,121 @@ var chain = (revset, revision) => gen2(function* () {
|
|
|
18544
18545
|
var stackChanges = (revision) => chain(stackRevset(revision), revision);
|
|
18545
18546
|
var downstackChanges = (revision) => chain(downstackRevset(revision), revision);
|
|
18546
18547
|
|
|
18548
|
+
// src/clients/github/openapi.ts
|
|
18549
|
+
var responseOf = () => (schema) => schema;
|
|
18550
|
+
var bodyOf = (body) => bodyJsonUnsafe(body);
|
|
18551
|
+
|
|
18552
|
+
// src/clients/github/types.ts
|
|
18553
|
+
var PullRequestNumberSchema = Number6.pipe(brand("PullRequestNumber"));
|
|
18554
|
+
var StackNumberSchema = Number6.pipe(brand("StackNumber"));
|
|
18555
|
+
|
|
18556
|
+
// src/clients/github/client.ts
|
|
18557
|
+
class GitHubFailed extends TaggedError2("GitHubFailed") {
|
|
18558
|
+
}
|
|
18559
|
+
|
|
18560
|
+
class GitHub extends Service()("jj-octo/GitHub") {
|
|
18561
|
+
}
|
|
18562
|
+
var CreatedPullRequest = responseOf()(Struct({ number: PullRequestNumberSchema }));
|
|
18563
|
+
var CreatedStack = responseOf()(Struct({ number: StackNumberSchema }));
|
|
18564
|
+
var PullRequests = responseOf()(ArraySchema(Struct({
|
|
18565
|
+
base: Struct({ ref: String4 }),
|
|
18566
|
+
body: NullOr(String4),
|
|
18567
|
+
draft: optionalKey2(Boolean3),
|
|
18568
|
+
number: PullRequestNumberSchema,
|
|
18569
|
+
title: String4
|
|
18570
|
+
})));
|
|
18571
|
+
var ClosedPullRequests = responseOf()(ArraySchema(Struct({
|
|
18572
|
+
head: Struct({ sha: String4 }),
|
|
18573
|
+
merged_at: NullOr(String4)
|
|
18574
|
+
})));
|
|
18575
|
+
var StackedPullRequest = Struct({
|
|
18576
|
+
number: PullRequestNumberSchema,
|
|
18577
|
+
state: Literals(["closed", "open"])
|
|
18578
|
+
});
|
|
18579
|
+
var Stacks = responseOf()(ArraySchema(Struct({ number: StackNumberSchema, pull_requests: ArraySchema(StackedPullRequest) })));
|
|
18580
|
+
var RepositoryDetails = responseOf()(Struct({ default_branch: String4 }));
|
|
18581
|
+
var MergeRequest = Struct({
|
|
18582
|
+
details: Struct({
|
|
18583
|
+
message: String4,
|
|
18584
|
+
uuid: optionalKey2(String4)
|
|
18585
|
+
}),
|
|
18586
|
+
status: Literals(["enqueued", "failed", "merged", "pending"])
|
|
18587
|
+
});
|
|
18588
|
+
var StartedMerge = responseOf()(MergeRequest);
|
|
18589
|
+
var MergeResult = responseOf()(MergeRequest);
|
|
18590
|
+
var GitHubLive = (repository, connection) => {
|
|
18591
|
+
const repo = (path) => `/repos/${repository.owner}/${repository.name}${path}`;
|
|
18592
|
+
return effect(GitHub, gen2(function* () {
|
|
18593
|
+
const requestInit = match(connection.proxy, {
|
|
18594
|
+
onNone: () => ({}),
|
|
18595
|
+
onSome: ({ ca, url }) => match(ca, {
|
|
18596
|
+
onNone: () => ({ proxy: url }),
|
|
18597
|
+
onSome: (certificates) => ({ proxy: url, tls: { ca: certificates } })
|
|
18598
|
+
})
|
|
18599
|
+
});
|
|
18600
|
+
const client = (yield* HttpClient).pipe(mapRequest((request) => request.pipe(prependUrl("https://api.github.com"), acceptJson, bearerToken(connection.token), setHeaders({
|
|
18601
|
+
"user-agent": "jj-octo",
|
|
18602
|
+
"x-github-api-version": "2022-11-28"
|
|
18603
|
+
}))));
|
|
18604
|
+
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({
|
|
18605
|
+
message: `${request.method} ${request.url} returned ${response.status}: ${text}`
|
|
18606
|
+
}))))), catchTag2("HttpClientError", (error) => fail6(new GitHubFailed({ message: error.message }))));
|
|
18607
|
+
const json = (schema) => (request) => send(request).pipe(flatMap4(schemaBodyJson(schema)), catchTags2({
|
|
18608
|
+
HttpClientError: (error) => fail6(new GitHubFailed({ message: error.message })),
|
|
18609
|
+
SchemaError: (error) => fail6(new GitHubFailed({
|
|
18610
|
+
message: `unexpected response to ${request.url}: ${error.message}`
|
|
18611
|
+
}))
|
|
18612
|
+
}));
|
|
18613
|
+
const stackOf = (pullRequest) => json(Stacks)(get4(repo(`/stacks?pull_request=${pullRequest}`))).pipe(map7((stacks) => map(fromNullishOr(stacks[0]), (stack) => ({
|
|
18614
|
+
number: stack.number,
|
|
18615
|
+
open: stack.pull_requests.filter(({ state }) => state === "open").map(({ number }) => number)
|
|
18616
|
+
}))));
|
|
18617
|
+
const mergeRequest = (path) => json(MergeResult)(get4(repo(path)));
|
|
18618
|
+
const fromBranch = (branch, state) => repo(`/pulls?state=${state}&head=${repository.owner}:${encodeURIComponent(branch)}`);
|
|
18619
|
+
return {
|
|
18620
|
+
addToStack: (stack, pullRequests) => send(post(repo(`/stacks/${stack}/add`)).pipe(bodyOf({ pull_requests: [...pullRequests] }))).pipe(asVoid2),
|
|
18621
|
+
createPullRequest: ({ base, body, draft, head, title }) => json(CreatedPullRequest)(post(repo("/pulls")).pipe(bodyOf({ base, body, draft, head, title }))).pipe(map7(({ number }) => number)),
|
|
18622
|
+
createStack: (pullRequests) => json(CreatedStack)(post(repo("/stacks")).pipe(bodyOf({ pull_requests: [...pullRequests] }))).pipe(map7(({ number }) => number)),
|
|
18623
|
+
defaultBranch: json(RepositoryDetails)(get4(repo(""))).pipe(map7(({ default_branch }) => default_branch)),
|
|
18624
|
+
mergePullRequest: (pullRequest) => send(put(repo(`/pulls/${pullRequest}/merge`)).pipe(bodyOf({ merge_method: "squash" }))).pipe(asVoid2),
|
|
18625
|
+
mergeStack: (pullRequest) => gen2(function* () {
|
|
18626
|
+
const started = yield* json(StartedMerge)(put(repo(`/pulls/${pullRequest}/merge-async`)).pipe(bodyOf({
|
|
18627
|
+
merge_action: "default",
|
|
18628
|
+
merge_method: "squash"
|
|
18629
|
+
})));
|
|
18630
|
+
const { uuid } = started.details;
|
|
18631
|
+
if (uuid === undefined) {
|
|
18632
|
+
return yield* new GitHubFailed({
|
|
18633
|
+
message: `merging #${pullRequest} returned no request to follow`
|
|
18634
|
+
});
|
|
18635
|
+
}
|
|
18636
|
+
const settled = yield* mergeRequest(`/pulls/${pullRequest}/merge-async/${uuid}`).pipe(repeat2({
|
|
18637
|
+
schedule: spaced("5 seconds"),
|
|
18638
|
+
until: ({ status }) => status !== "pending"
|
|
18639
|
+
}));
|
|
18640
|
+
if (settled.status === "merged" || settled.status === "enqueued") {
|
|
18641
|
+
return settled.status;
|
|
18642
|
+
}
|
|
18643
|
+
return yield* new GitHubFailed({
|
|
18644
|
+
message: `merging #${pullRequest} ended ${settled.status}: ${settled.details.message}`
|
|
18645
|
+
});
|
|
18646
|
+
}),
|
|
18647
|
+
mergedHeads: (branch) => json(ClosedPullRequests)(get4(fromBranch(branch, "closed"))).pipe(map7((closed) => closed.filter(({ merged_at }) => merged_at !== null).map(({ head }) => head.sha))),
|
|
18648
|
+
openPullRequest: (branch) => json(PullRequests)(get4(fromBranch(branch, "open"))).pipe(map7((found) => map(fromNullishOr(found[0]), ({ base, body, draft, number, title }) => ({
|
|
18649
|
+
base: base.ref,
|
|
18650
|
+
body: body ?? "",
|
|
18651
|
+
draft: draft ?? false,
|
|
18652
|
+
number,
|
|
18653
|
+
title
|
|
18654
|
+
})))),
|
|
18655
|
+
setBase: (pullRequest, base) => send(patch(repo(`/pulls/${pullRequest}`)).pipe(bodyOf({ base }))).pipe(asVoid2),
|
|
18656
|
+
setDescription: (pullRequest, { body, title }) => send(patch(repo(`/pulls/${pullRequest}`)).pipe(bodyOf({ body, title }))).pipe(asVoid2),
|
|
18657
|
+
stackOf,
|
|
18658
|
+
unstack: (stack) => send(post(repo(`/stacks/${stack}/unstack`))).pipe(asVoid2)
|
|
18659
|
+
};
|
|
18660
|
+
}));
|
|
18661
|
+
};
|
|
18662
|
+
|
|
18547
18663
|
// src/cli/gh/stack/trunk.ts
|
|
18548
18664
|
class TrunkMismatch extends TaggedError2("TrunkMismatch") {
|
|
18549
18665
|
}
|
|
@@ -18555,6 +18671,11 @@ var checkTrunk = (branch) => gen2(function* () {
|
|
|
18555
18671
|
});
|
|
18556
18672
|
}
|
|
18557
18673
|
});
|
|
18674
|
+
var checkedTrunk = gen2(function* () {
|
|
18675
|
+
const branch = yield* (yield* GitHub).defaultBranch;
|
|
18676
|
+
yield* checkTrunk(branch);
|
|
18677
|
+
return branch;
|
|
18678
|
+
});
|
|
18558
18679
|
|
|
18559
18680
|
// src/cli/gh/setup.ts
|
|
18560
18681
|
class NotSetUp extends TaggedError2("NotSetUp") {
|
|
@@ -18688,121 +18809,6 @@ var init = gen2(function* () {
|
|
|
18688
18809
|
});
|
|
18689
18810
|
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
18811
|
|
|
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
18812
|
// src/clients/github/connection.ts
|
|
18807
18813
|
var authKey = "jj-octo.github-auth";
|
|
18808
18814
|
var ownToken = gen2(function* () {
|
|
@@ -19003,115 +19009,24 @@ var syncDescription = ({ bookmark, description }, pullRequest, { ignoreClaudeCod
|
|
|
19003
19009
|
})), exhaustive2);
|
|
19004
19010
|
});
|
|
19005
19011
|
|
|
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* () {
|
|
19012
|
+
// src/cli/gh/stack/combine.ts
|
|
19013
|
+
var pushedOf = (bookmarks) => bookmarks.filter(({ remote }) => remote !== undefined);
|
|
19014
|
+
var bookmarksBeforeFetch = (revset) => gen2(function* () {
|
|
19110
19015
|
const targets = [...yield* bookmarkTargets];
|
|
19111
|
-
const
|
|
19016
|
+
const bookmarked = `(${revset}) & bookmarks()`;
|
|
19017
|
+
const bottomFirst = lines2(yield* readLog(bookmarked, String.raw`commit_id ++ "\t" ++ change_id ++ "\t" ++ current_working_copy ++ "\n"`));
|
|
19018
|
+
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
19019
|
return bottomFirst.toReversed().flatMap((line) => {
|
|
19113
|
-
const [commit = "", change = ""] = line.split("\t");
|
|
19114
|
-
|
|
19020
|
+
const [commit = "", change = "", workingCopy] = line.split("\t");
|
|
19021
|
+
const above = children.flatMap(([parents = "", child = ""]) => parents.split(",").includes(commit) ? [child] : []);
|
|
19022
|
+
return targets.filter(([, { local }]) => local === commit).map(([name, { origin }]) => ({
|
|
19023
|
+
above,
|
|
19024
|
+
change,
|
|
19025
|
+
editing: workingCopy === "true",
|
|
19026
|
+
local: commit,
|
|
19027
|
+
name,
|
|
19028
|
+
remote: origin
|
|
19029
|
+
}));
|
|
19115
19030
|
});
|
|
19116
19031
|
});
|
|
19117
19032
|
var localCommit = (change, remote, fetched) => gen2(function* () {
|
|
@@ -19122,22 +19037,31 @@ var localCommit = (change, remote, fetched) => gen2(function* () {
|
|
|
19122
19037
|
}
|
|
19123
19038
|
return local;
|
|
19124
19039
|
});
|
|
19125
|
-
var combine4 = ({ change, local: localBefore, name, remote }, fetched) => gen2(function* () {
|
|
19040
|
+
var combine4 = ({ above, change, editing, local: localBefore, name, remote }, fetched) => gen2(function* () {
|
|
19126
19041
|
const jj = yield* Jj;
|
|
19127
19042
|
if (remote === undefined || fetched === undefined || fetched === remote) {
|
|
19128
19043
|
return true;
|
|
19129
19044
|
}
|
|
19130
19045
|
const [local, ...others] = yield* localCommit(change, remote, fetched);
|
|
19131
19046
|
if (local === undefined) {
|
|
19047
|
+
if (editing) {
|
|
19048
|
+
yield* jj.run(["edit", fetched]);
|
|
19049
|
+
}
|
|
19050
|
+
const moved = `(${anyOf(above.map((child) => `change_id(${child})`))}) ~ children(${fetched})`;
|
|
19051
|
+
if ((yield* commitsAt(moved)).length > 0) {
|
|
19052
|
+
yield* jj.run(["rebase", "--source", moved, "--destination", fetched]);
|
|
19053
|
+
yield* error(`took GitHub's rewritten ${name}, and moved what was on it there`);
|
|
19054
|
+
}
|
|
19132
19055
|
return true;
|
|
19133
19056
|
}
|
|
19134
19057
|
if (others.length > 0) {
|
|
19135
19058
|
return false;
|
|
19136
19059
|
}
|
|
19137
19060
|
const keepLocal = jj.run(["bookmark", "set", name, "-r", local, "--allow-backwards"]);
|
|
19061
|
+
const held = `::(bookmarks() ~ ${bookmarkRevset(name)}) ~ ::${remote}`;
|
|
19138
19062
|
const dropCopies = jj.run([
|
|
19139
19063
|
"abandon",
|
|
19140
|
-
`((::${fetched} | ::${remote}) & mutable()) ~ ::${bookmarkRevset(name)}`
|
|
19064
|
+
`((::${fetched} | ::${remote}) & mutable()) ~ ::${bookmarkRevset(name)} ~ (${held})`
|
|
19141
19065
|
]);
|
|
19142
19066
|
if ((yield* commitsAt(`${remote} & ::${fetched}`)).length > 0) {
|
|
19143
19067
|
yield* keepLocal;
|
|
@@ -19155,6 +19079,9 @@ var combine4 = ({ change, local: localBefore, name, remote }, fetched) => gen2(f
|
|
|
19155
19079
|
}
|
|
19156
19080
|
if (localBefore === remote) {
|
|
19157
19081
|
yield* jj.run(["rebase", "--source", `children(${local})`, "--destination", fetched]);
|
|
19082
|
+
if (editing) {
|
|
19083
|
+
yield* jj.run(["edit", fetched]);
|
|
19084
|
+
}
|
|
19158
19085
|
yield* jj.run(["abandon", local]);
|
|
19159
19086
|
yield* error(`took GitHub's rewritten ${name}, which wasn't changed locally`);
|
|
19160
19087
|
return true;
|
|
@@ -19169,6 +19096,9 @@ var combine4 = ({ change, local: localBefore, name, remote }, fetched) => gen2(f
|
|
|
19169
19096
|
return false;
|
|
19170
19097
|
});
|
|
19171
19098
|
|
|
19099
|
+
// src/cli/gh/stack/reconcile.ts
|
|
19100
|
+
var BEHIND_ROOTS = `(${STACK_ROOTS}) ~ children(trunk())`;
|
|
19101
|
+
|
|
19172
19102
|
class NotCombined extends TaggedError2("NotCombined") {
|
|
19173
19103
|
}
|
|
19174
19104
|
var rebaseStack = (root, required) => gen2(function* () {
|
|
@@ -19188,8 +19118,8 @@ var rebaseStack = (root, required) => gen2(function* () {
|
|
|
19188
19118
|
String.raw`id ++ "\n"`
|
|
19189
19119
|
]));
|
|
19190
19120
|
yield* jj.run(["rebase", "--source", root, "--destination", "trunk()", "--skip-emptied"]);
|
|
19191
|
-
const rebased =
|
|
19192
|
-
const untouched =
|
|
19121
|
+
const rebased = anyOf(changes.map((change) => `change_id(${change})`));
|
|
19122
|
+
const untouched = anyOf(conflictedCommits);
|
|
19193
19123
|
const conflictedNow = yield* changeIdsAt(`(conflicts() & (${rebased})) ~ (${untouched})`);
|
|
19194
19124
|
const mayRestore = !changes.some((change) => required.includes(change));
|
|
19195
19125
|
if (mayRestore && conflictedNow.some((change) => !conflicted.includes(change))) {
|
|
@@ -19197,23 +19127,49 @@ var rebaseStack = (root, required) => gen2(function* () {
|
|
|
19197
19127
|
yield* error(`left "${name}" and the changes on it where they were: they conflict with the trunk`);
|
|
19198
19128
|
}
|
|
19199
19129
|
});
|
|
19200
|
-
var
|
|
19130
|
+
var notMerged = (merged) => (name) => merged.every(({ bookmark }) => bookmark !== name);
|
|
19131
|
+
var mergedHeadsOf = (bookmarks) => gen2(function* () {
|
|
19201
19132
|
const github = yield* GitHub;
|
|
19202
|
-
const
|
|
19203
|
-
|
|
19133
|
+
const pushed = pushedOf(bookmarks);
|
|
19134
|
+
const heads = yield* forEach2(pushed, ({ name }) => github.mergedHeads(name), {
|
|
19135
|
+
concurrency: "unbounded"
|
|
19136
|
+
});
|
|
19137
|
+
return Object.fromEntries(pushed.map(({ name }, index) => [name, heads[index] ?? []]));
|
|
19204
19138
|
});
|
|
19205
|
-
var
|
|
19139
|
+
var mergedIn = (bookmarks, heads) => gen2(function* () {
|
|
19206
19140
|
const jj = yield* Jj;
|
|
19207
|
-
const
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19141
|
+
const merged = yield* forEach2(bookmarks, ({ change, local, name, remote }) => gen2(function* () {
|
|
19142
|
+
const merges = heads[name] ?? [];
|
|
19143
|
+
const rebasedOnly = remote !== undefined && merges.includes(remote) && (yield* jj.read(["interdiff", "--from", remote, "--to", local, "--summary"])).trim() === "";
|
|
19144
|
+
return merges.includes(local) || rebasedOnly ? [{ bookmark: name, changeId: change }] : [];
|
|
19145
|
+
}));
|
|
19146
|
+
return merged.flat();
|
|
19147
|
+
});
|
|
19148
|
+
var mergedOnGitHub = (bookmarks) => flatMap4(mergedHeadsOf(bookmarks), (heads) => mergedIn(bookmarks, heads));
|
|
19149
|
+
var forget = (names) => gen2(function* () {
|
|
19150
|
+
if (names.length > 0) {
|
|
19151
|
+
const jj = yield* Jj;
|
|
19152
|
+
yield* jj.run([
|
|
19153
|
+
"bookmark",
|
|
19154
|
+
"forget",
|
|
19155
|
+
"--include-remotes",
|
|
19156
|
+
...names.map((name) => `exact:${name}`)
|
|
19157
|
+
]);
|
|
19215
19158
|
}
|
|
19216
|
-
|
|
19159
|
+
});
|
|
19160
|
+
var reconcile = ({
|
|
19161
|
+
before,
|
|
19162
|
+
merged,
|
|
19163
|
+
scope
|
|
19164
|
+
}) => gen2(function* () {
|
|
19165
|
+
const jj = yield* Jj;
|
|
19166
|
+
const branches = scope === "every stack" ? [] : [scope.trunk, ...pushedOf(before).map(({ name }) => name)].flatMap((branch) => [
|
|
19167
|
+
"--branch",
|
|
19168
|
+
`exact:${branch}`
|
|
19169
|
+
]);
|
|
19170
|
+
yield* jj.run(["git", "fetch", ...branches]);
|
|
19171
|
+
yield* forget(merged.map(({ bookmark }) => bookmark));
|
|
19172
|
+
const mergedRevset = `(${anyOf(merged.map(({ changeId }) => `change_id(${changeId})`))}) & mutable()`;
|
|
19217
19173
|
const required = yield* changeIdsAt(`(${mergedRevset}):: ~ (${mergedRevset})`);
|
|
19218
19174
|
if (merged.length > 0) {
|
|
19219
19175
|
yield* jj.run(["abandon", mergedRevset]);
|
|
@@ -19221,18 +19177,20 @@ var sync4 = (known = []) => gen2(function* () {
|
|
|
19221
19177
|
}
|
|
19222
19178
|
const fetched = yield* bookmarkTargets;
|
|
19223
19179
|
const notCombined = [];
|
|
19224
|
-
for (const bookmark of before.filter(({ name }) => merged
|
|
19180
|
+
for (const bookmark of before.filter(({ name }) => notMerged(merged)(name))) {
|
|
19225
19181
|
if (!(yield* combine4(bookmark, fetched.get(bookmark.name)?.origin))) {
|
|
19226
19182
|
notCombined.push(bookmark.name);
|
|
19227
19183
|
}
|
|
19228
19184
|
}
|
|
19229
|
-
|
|
19185
|
+
const roots = scope === "every stack" ? BEHIND_ROOTS : `(${BEHIND_ROOTS}) & ::(${anyOf([
|
|
19186
|
+
...scope.bookmarks.map((name) => bookmarkRevset(name)),
|
|
19187
|
+
...scope.unnamed.map((change) => `change_id(${change})`)
|
|
19188
|
+
])})`;
|
|
19189
|
+
for (const root of yield* commitsAt(roots)) {
|
|
19230
19190
|
yield* rebaseStack(root, required);
|
|
19231
19191
|
}
|
|
19232
|
-
const
|
|
19233
|
-
|
|
19234
|
-
yield* jj.run(["bookmark", "forget", "--include-remotes", `exact:${name}`]);
|
|
19235
|
-
}
|
|
19192
|
+
const onStacks = new Set(scope === "every stack" ? before.map(({ name }) => name) : scope.bookmarks);
|
|
19193
|
+
yield* forget((yield* bookmarksAt("::trunk()")).filter((name) => onStacks.has(name)));
|
|
19236
19194
|
if (notCombined.length > 0) {
|
|
19237
19195
|
yield* new NotCombined({
|
|
19238
19196
|
message: [
|
|
@@ -19244,8 +19202,201 @@ var sync4 = (known = []) => gen2(function* () {
|
|
|
19244
19202
|
});
|
|
19245
19203
|
}
|
|
19246
19204
|
});
|
|
19205
|
+
|
|
19206
|
+
// src/cli/gh/stack/sync-stack.ts
|
|
19207
|
+
class BranchesDeleted extends TaggedError2("BranchesDeleted") {
|
|
19208
|
+
}
|
|
19209
|
+
var whyDeleted = (heads, { remote }) => {
|
|
19210
|
+
if (remote !== undefined && heads.includes(remote)) {
|
|
19211
|
+
return "its pushed commit merged, and the change was edited since";
|
|
19212
|
+
}
|
|
19213
|
+
return heads.length > 0 ? "a pull request from it merged, but not the commit pushed for this change" : "no pull request from it merged";
|
|
19214
|
+
};
|
|
19215
|
+
var changedOnGitHub = (bookmarks) => gen2(function* () {
|
|
19216
|
+
const pushed = pushedOf(bookmarks);
|
|
19217
|
+
if (pushed.length === 0) {
|
|
19218
|
+
return { changed: [], deleted: [] };
|
|
19219
|
+
}
|
|
19220
|
+
const git = yield* Git;
|
|
19221
|
+
const gitDirectory = (yield* (yield* Jj).read(["git", "root"])).trim();
|
|
19222
|
+
const output = yield* git.read([
|
|
19223
|
+
"--git-dir",
|
|
19224
|
+
gitDirectory,
|
|
19225
|
+
"ls-remote",
|
|
19226
|
+
"origin",
|
|
19227
|
+
...pushed.map(({ name }) => `refs/heads/${name}`)
|
|
19228
|
+
]);
|
|
19229
|
+
const onGitHub = new Map(lines2(output).map((line) => {
|
|
19230
|
+
const [commit = "", ref = ""] = line.split("\t");
|
|
19231
|
+
return [ref.replace(/^refs\/heads\//u, ""), commit];
|
|
19232
|
+
}));
|
|
19233
|
+
return {
|
|
19234
|
+
changed: pushed.filter(({ name, remote }) => onGitHub.has(name) && onGitHub.get(name) !== remote).map(({ name }) => name),
|
|
19235
|
+
deleted: pushed.filter(({ name }) => !onGitHub.has(name))
|
|
19236
|
+
};
|
|
19237
|
+
});
|
|
19238
|
+
var syncStack = (revision, trunk) => gen2(function* () {
|
|
19239
|
+
const downstack = downstackRevset(revision);
|
|
19240
|
+
const before = yield* bookmarksBeforeFetch(downstack);
|
|
19241
|
+
const [heads, onGitHub] = yield* all2([mergedHeadsOf(before), changedOnGitHub(before)], {
|
|
19242
|
+
concurrency: "unbounded"
|
|
19243
|
+
});
|
|
19244
|
+
const merged = yield* mergedIn(before, heads);
|
|
19245
|
+
const changed = onGitHub.changed.filter(notMerged(merged));
|
|
19246
|
+
const deleted = onGitHub.deleted.filter(({ name }) => notMerged(merged)(name));
|
|
19247
|
+
if (deleted.length > 0) {
|
|
19248
|
+
return yield* new BranchesDeleted({
|
|
19249
|
+
message: [
|
|
19250
|
+
"these branches were deleted on GitHub, so nothing was changed:",
|
|
19251
|
+
...deleted.map((bookmark) => `${bookmark.name}: ${whyDeleted(heads[bookmark.name] ?? [], bookmark)}`),
|
|
19252
|
+
"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>`"
|
|
19253
|
+
].join(`
|
|
19254
|
+
`)
|
|
19255
|
+
});
|
|
19256
|
+
}
|
|
19257
|
+
const found = [
|
|
19258
|
+
...merged.map(({ bookmark }) => `${bookmark} merged`),
|
|
19259
|
+
...changed.map((name) => `${name} changed`)
|
|
19260
|
+
];
|
|
19261
|
+
if (found.length === 0) {
|
|
19262
|
+
return false;
|
|
19263
|
+
}
|
|
19264
|
+
yield* error(`on GitHub, ${found.join(", ")}; syncing the stack first`);
|
|
19265
|
+
const whole = stackRevset(revision);
|
|
19266
|
+
yield* reconcile({
|
|
19267
|
+
before,
|
|
19268
|
+
merged,
|
|
19269
|
+
scope: {
|
|
19270
|
+
bookmarks: yield* bookmarksAt(whole),
|
|
19271
|
+
trunk,
|
|
19272
|
+
unnamed: yield* changeIdsAt(`((${whole}) | (@ & children(${whole}))) ~ bookmarks()`)
|
|
19273
|
+
}
|
|
19274
|
+
});
|
|
19275
|
+
return true;
|
|
19276
|
+
});
|
|
19277
|
+
|
|
19278
|
+
// src/cli/gh/stack/publish.ts
|
|
19279
|
+
class SeveralStacks extends TaggedError2("SeveralStacks") {
|
|
19280
|
+
}
|
|
19281
|
+
var stackContaining = (pullRequests) => gen2(function* () {
|
|
19282
|
+
const github = yield* GitHub;
|
|
19283
|
+
const found = yield* all2(pullRequests.map((pullRequest) => github.stackOf(pullRequest)), { concurrency: "unbounded" });
|
|
19284
|
+
const stacks = dedupeWith(found.flatMap((stack) => toArray(stack)), (a, b) => a.number === b.number);
|
|
19285
|
+
if (stacks.length > 1) {
|
|
19286
|
+
return yield* new SeveralStacks({
|
|
19287
|
+
message: `the pull requests belong to stacks ${stacks.map(({ number }) => `#${number}`).join(" and ")}; unstack all but one`
|
|
19288
|
+
});
|
|
19289
|
+
}
|
|
19290
|
+
return fromNullishOr(stacks[0]);
|
|
19291
|
+
});
|
|
19292
|
+
var register = (remaining, pullRequests) => gen2(function* () {
|
|
19293
|
+
const github = yield* GitHub;
|
|
19294
|
+
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);
|
|
19295
|
+
});
|
|
19296
|
+
var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function* () {
|
|
19297
|
+
const github = yield* GitHub;
|
|
19298
|
+
const jj = yield* Jj;
|
|
19299
|
+
const bookmarks = stack.map(({ bookmark }) => bookmark);
|
|
19300
|
+
const bases = basesFor(bookmarks, trunk);
|
|
19301
|
+
const found = yield* all2(stack.map((change, index) => github.openPullRequest(change.bookmark).pipe(map7((pullRequest) => ({ base: bases[index] ?? trunk, change, pullRequest })))), { concurrency: "unbounded" });
|
|
19302
|
+
const targets = yield* bookmarkTargets;
|
|
19303
|
+
const pushedDescriptions = yield* descriptionsOf(bookmarks.flatMap((bookmark) => targets.get(bookmark)?.origin ?? []));
|
|
19304
|
+
const ignoreClaudeCodeFooter = yield* ignoresClaudeCodeFooter;
|
|
19305
|
+
const changes = [];
|
|
19306
|
+
for (const entry of found) {
|
|
19307
|
+
const pushed = targets.get(entry.change.bookmark)?.origin;
|
|
19308
|
+
changes.push({
|
|
19309
|
+
...entry,
|
|
19310
|
+
description: isNone2(entry.pullRequest) ? none2() : yield* syncDescription(entry.change, entry.pullRequest.value, {
|
|
19311
|
+
ignoreClaudeCodeFooter,
|
|
19312
|
+
overwriteDescriptions,
|
|
19313
|
+
pushedDescription: (pushed === undefined ? undefined : pushedDescriptions.get(pushed)) ?? ""
|
|
19314
|
+
})
|
|
19315
|
+
});
|
|
19316
|
+
}
|
|
19317
|
+
const moving = changes.flatMap(({ base, pullRequest }) => isSome2(pullRequest) && pullRequest.value.base !== base ? [pullRequest.value] : []);
|
|
19318
|
+
const wanted = changes.map(({ pullRequest }) => isSome2(pullRequest) ? some2(pullRequest.value.number) : none2());
|
|
19319
|
+
const existing = yield* stackContaining(wanted.flatMap((number) => toArray(number)));
|
|
19320
|
+
const dissolve = mustDissolve(existing, wanted, moving.length > 0);
|
|
19321
|
+
if (dissolve && isSome2(existing)) {
|
|
19322
|
+
yield* github.unstack(existing.value.number);
|
|
19323
|
+
yield* error(`dissolved stack #${existing.value.number}`);
|
|
19324
|
+
}
|
|
19325
|
+
for (const { number } of moving.filter(({ base }) => base !== trunk)) {
|
|
19326
|
+
yield* github.setBase(number, trunk);
|
|
19327
|
+
}
|
|
19328
|
+
const moved = new Set(moving.map(({ number }) => number));
|
|
19329
|
+
for (const bookmark of bookmarks.filter((name) => targets.get(name)?.tracked !== true)) {
|
|
19330
|
+
yield* jj.run(["bookmark", "track", `exact:${bookmark}`, "--remote", "origin"]);
|
|
19331
|
+
}
|
|
19332
|
+
yield* jj.run([
|
|
19333
|
+
"git",
|
|
19334
|
+
"push",
|
|
19335
|
+
...bookmarks.flatMap((bookmark) => ["--bookmark", `exact:${bookmark}`])
|
|
19336
|
+
]);
|
|
19337
|
+
const numbers = [];
|
|
19338
|
+
for (const { base, change, description, pullRequest } of changes) {
|
|
19339
|
+
if (isNone2(pullRequest)) {
|
|
19340
|
+
const created = yield* github.createPullRequest({
|
|
19341
|
+
...titleAndBody(change.description),
|
|
19342
|
+
base,
|
|
19343
|
+
draft,
|
|
19344
|
+
head: change.bookmark
|
|
19345
|
+
});
|
|
19346
|
+
yield* error(`opened #${created} for ${change.bookmark}`);
|
|
19347
|
+
numbers.push(created);
|
|
19348
|
+
} else {
|
|
19349
|
+
const { number } = pullRequest.value;
|
|
19350
|
+
if (moved.has(number) && base !== trunk) {
|
|
19351
|
+
yield* github.setBase(number, base);
|
|
19352
|
+
}
|
|
19353
|
+
if (isSome2(description)) {
|
|
19354
|
+
yield* github.setDescription(number, titleAndBody(description.value));
|
|
19355
|
+
yield* error(`updated the description of #${number}`);
|
|
19356
|
+
}
|
|
19357
|
+
numbers.push(number);
|
|
19358
|
+
}
|
|
19359
|
+
}
|
|
19360
|
+
yield* register(dissolve ? none2() : existing, numbers);
|
|
19361
|
+
});
|
|
19362
|
+
var submit = (revision, options) => gen2(function* () {
|
|
19363
|
+
const trunk = yield* checkedTrunk;
|
|
19364
|
+
const downstack = yield* downstackChanges(revision);
|
|
19365
|
+
if (!(yield* syncStack(revision, trunk))) {
|
|
19366
|
+
yield* publish(downstack, { ...options, trunk });
|
|
19367
|
+
return;
|
|
19368
|
+
}
|
|
19369
|
+
const left = yield* bookmarksAt(anyOf(downstack.map(({ bookmark }) => bookmarkRevset(bookmark))));
|
|
19370
|
+
const top = downstack.findLast(({ bookmark }) => left.includes(bookmark));
|
|
19371
|
+
if (top === undefined) {
|
|
19372
|
+
yield* error(`nothing left to submit: no bookmark of the stack is left after the sync`);
|
|
19373
|
+
return;
|
|
19374
|
+
}
|
|
19375
|
+
yield* publish(yield* downstackChanges(bookmarkRevset(top.bookmark)), { ...options, trunk });
|
|
19376
|
+
});
|
|
19377
|
+
var republish = (revision, trunk) => gen2(function* () {
|
|
19378
|
+
if ((yield* changesIn(stackRevset(revision))).length === 0) {
|
|
19379
|
+
return;
|
|
19380
|
+
}
|
|
19381
|
+
const stack = yield* stackChanges(revision);
|
|
19382
|
+
const targets = yield* bookmarkTargets;
|
|
19383
|
+
const top = stack.findLastIndex(({ bookmark }) => targets.get(bookmark)?.origin !== undefined);
|
|
19384
|
+
if (top !== -1) {
|
|
19385
|
+
yield* publish(stack.slice(0, top + 1), { draft: true, overwriteDescriptions: false, trunk });
|
|
19386
|
+
}
|
|
19387
|
+
});
|
|
19388
|
+
|
|
19389
|
+
// src/cli/gh/sync.ts
|
|
19390
|
+
var sync4 = (known = []) => gen2(function* () {
|
|
19391
|
+
const before = yield* bookmarksBeforeFetch("mutable()");
|
|
19392
|
+
const merged = [
|
|
19393
|
+
...known,
|
|
19394
|
+
...yield* mergedOnGitHub(before.filter(({ name }) => notMerged(known)(name)))
|
|
19395
|
+
];
|
|
19396
|
+
yield* reconcile({ before, merged, scope: "every stack" });
|
|
19397
|
+
});
|
|
19247
19398
|
var syncCommand = make31("sync", {}, () => asUserError(gen2(function* () {
|
|
19248
|
-
yield*
|
|
19399
|
+
yield* checkedTrunk;
|
|
19249
19400
|
yield* sync4();
|
|
19250
19401
|
}).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
19402
|
|
|
@@ -19281,8 +19432,7 @@ var mergeOnGitHub = ({ base, number }, trunk) => gen2(function* () {
|
|
|
19281
19432
|
return "merged";
|
|
19282
19433
|
});
|
|
19283
19434
|
var merge5 = (revision) => gen2(function* () {
|
|
19284
|
-
const trunk = yield*
|
|
19285
|
-
yield* checkTrunk(trunk);
|
|
19435
|
+
const trunk = yield* checkedTrunk;
|
|
19286
19436
|
const changes = yield* downstackChanges(revision);
|
|
19287
19437
|
const top = changes.at(-1)?.bookmark ?? "";
|
|
19288
19438
|
const targets = yield* bookmarkTargets;
|
|
@@ -19301,7 +19451,7 @@ var merge5 = (revision) => gen2(function* () {
|
|
|
19301
19451
|
yield* sync4(changes);
|
|
19302
19452
|
for (const bookmark of above) {
|
|
19303
19453
|
if ((yield* commitsAt(bookmarkRevset(bookmark))).length > 0) {
|
|
19304
|
-
yield* republish(bookmarkRevset(bookmark));
|
|
19454
|
+
yield* republish(bookmarkRevset(bookmark), trunk);
|
|
19305
19455
|
break;
|
|
19306
19456
|
}
|
|
19307
19457
|
}
|
|
@@ -19314,7 +19464,7 @@ var submitCommand = make31("submit", {
|
|
|
19314
19464
|
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
19465
|
publish: Boolean6("publish").pipe(withDefault5(false), withDescription2("Open new pull requests ready for review, instead of as drafts")),
|
|
19316
19466
|
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."));
|
|
19467
|
+
}, ({ 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
19468
|
|
|
19319
19469
|
// src/cli/gh/command.ts
|
|
19320
19470
|
var jjGhCommand = make31("jj gh").pipe(withSubcommands([
|