jj-octo 0.7.2 → 0.8.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 +177 -42
- package/package.json +3 -3
package/dist/jj-octo.js
CHANGED
|
@@ -1410,6 +1410,7 @@ var getOrUndefined = /* @__PURE__ */ getOrElse(constUndefined);
|
|
|
1410
1410
|
var map = /* @__PURE__ */ dual(2, (self, f) => isNone2(self) ? none2() : some2(f(self.value)));
|
|
1411
1411
|
var flatMap = /* @__PURE__ */ dual(2, (self, f) => isNone2(self) ? none2() : f(self.value));
|
|
1412
1412
|
var toArray = (self) => isNone2(self) ? [] : [self.value];
|
|
1413
|
+
var filter = /* @__PURE__ */ dual(2, (self, predicate) => isNone2(self) ? none2() : predicate(self.value) ? some2(self.value) : none2());
|
|
1413
1414
|
var exists = /* @__PURE__ */ dual(2, (self, refinement) => isNone2(self) ? false : refinement(self.value));
|
|
1414
1415
|
|
|
1415
1416
|
// node_modules/effect/dist/Context.js
|
|
@@ -3380,6 +3381,14 @@ var catchCauseFilter = /* @__PURE__ */ dual(3, (self, filter, f) => catchCause(s
|
|
|
3380
3381
|
}));
|
|
3381
3382
|
var catch_ = /* @__PURE__ */ dual(2, (self, f) => catchCauseFilter(self, findError, (e) => f(e)));
|
|
3382
3383
|
var tapCause = /* @__PURE__ */ dual(2, (self, f) => catchCause(self, (cause) => andThen(f(cause), failCause(cause))));
|
|
3384
|
+
var tapCauseFilter = /* @__PURE__ */ dual(3, (self, filter, f) => catchCause(self, (cause) => {
|
|
3385
|
+
const result = filter(cause);
|
|
3386
|
+
if (isFailure2(result)) {
|
|
3387
|
+
return failCause(cause);
|
|
3388
|
+
}
|
|
3389
|
+
return andThen(f(result.success, cause), failCause(cause));
|
|
3390
|
+
}));
|
|
3391
|
+
var tapError = /* @__PURE__ */ dual(2, (self, f) => tapCauseFilter(self, findError, (e) => f(e)));
|
|
3383
3392
|
var catchIf = /* @__PURE__ */ dual((args) => isEffect(args[0]), (self, predicate, f, orElse) => catchCause(self, (cause) => {
|
|
3384
3393
|
const error = findError(cause);
|
|
3385
3394
|
if (isFailure2(error))
|
|
@@ -4998,6 +5007,7 @@ var catchCause2 = catchCause;
|
|
|
4998
5007
|
var catchFilter2 = catchFilter;
|
|
4999
5008
|
var mapError3 = mapError2;
|
|
5000
5009
|
var orDie2 = orDie;
|
|
5010
|
+
var tapError2 = tapError;
|
|
5001
5011
|
var tapCause2 = tapCause;
|
|
5002
5012
|
var ignore2 = ignore;
|
|
5003
5013
|
var orElseSucceed2 = orElseSucceed;
|
|
@@ -10937,6 +10947,9 @@ function decodeUnknownEffect(schema, options) {
|
|
|
10937
10947
|
const parser = run2(schema.ast);
|
|
10938
10948
|
return options === undefined ? parser : (input, overrideOptions) => parser(input, mergeParseOptions(options, overrideOptions));
|
|
10939
10949
|
}
|
|
10950
|
+
function decodeUnknownOption(schema, options) {
|
|
10951
|
+
return asOption(decodeUnknownEffect(schema, options));
|
|
10952
|
+
}
|
|
10940
10953
|
var mergeParseOptions = (options, overrideOptions) => overrideOptions ? {
|
|
10941
10954
|
...options,
|
|
10942
10955
|
...overrideOptions
|
|
@@ -10975,6 +10988,17 @@ function runWithCompiler(compiler, ast) {
|
|
|
10975
10988
|
function asExit(parser) {
|
|
10976
10989
|
return (input, options) => runSyncExit2(parser(input, options));
|
|
10977
10990
|
}
|
|
10991
|
+
function asOption(parser) {
|
|
10992
|
+
const parserExit = asExit(parser);
|
|
10993
|
+
return (input, options) => {
|
|
10994
|
+
const exit = parserExit(input, options);
|
|
10995
|
+
if (isSuccess3(exit)) {
|
|
10996
|
+
return some2(exit.value);
|
|
10997
|
+
}
|
|
10998
|
+
getSchemaIssueOrThrow(exit.cause, "Option adapter can only return none for schema issues");
|
|
10999
|
+
return none2();
|
|
11000
|
+
};
|
|
11001
|
+
}
|
|
10978
11002
|
function runSync3(effect, message) {
|
|
10979
11003
|
const exit = runSyncExit2(effect);
|
|
10980
11004
|
if (isSuccess3(exit)) {
|
|
@@ -11304,6 +11328,7 @@ function decodeUnknownEffect2(schema, options) {
|
|
|
11304
11328
|
};
|
|
11305
11329
|
}
|
|
11306
11330
|
var decodeEffect2 = decodeUnknownEffect2;
|
|
11331
|
+
var decodeUnknownOption2 = decodeUnknownOption;
|
|
11307
11332
|
var make25 = make24;
|
|
11308
11333
|
function isSchema(u) {
|
|
11309
11334
|
return hasProperty(u, TypeId29) && u[TypeId29] === TypeId29;
|
|
@@ -18239,7 +18264,7 @@ var layer14 = /* @__PURE__ */ layerMergedContext(/* @__PURE__ */ succeed6(fetch)
|
|
|
18239
18264
|
// package.json
|
|
18240
18265
|
var package_default = {
|
|
18241
18266
|
name: "jj-octo",
|
|
18242
|
-
version: "0.
|
|
18267
|
+
version: "0.8.1",
|
|
18243
18268
|
description: "A jj subcommand for GitHub that supports stacked pull requests and works in Claude Code cloud sessions",
|
|
18244
18269
|
license: "MIT",
|
|
18245
18270
|
repository: {
|
|
@@ -18276,8 +18301,8 @@ var package_default = {
|
|
|
18276
18301
|
"@tsconfig/strictest": "2.0.8",
|
|
18277
18302
|
"@types/bun": "1.4.3",
|
|
18278
18303
|
effect: "4.0.5",
|
|
18279
|
-
oxfmt: "0.
|
|
18280
|
-
oxlint: "1.
|
|
18304
|
+
oxfmt: "0.74.0",
|
|
18305
|
+
oxlint: "1.89.0",
|
|
18281
18306
|
"oxlint-tsgolint": "7.0.2004",
|
|
18282
18307
|
typescript: "7.0.2"
|
|
18283
18308
|
},
|
|
@@ -18919,6 +18944,64 @@ var log2 = (revision) => gen2(function* () {
|
|
|
18919
18944
|
var logCommand = make31("log", { revision: revisionFlag("A change of the stack to show") }, ({ revision }) => asUserError(log2(revision).pipe(provide3(GitHubFromOrigin))).pipe(flatMap4((printed) => log(printed.join(`
|
|
18920
18945
|
`))))).pipe(withHelp("Show changes and their pull requests", "Prints one line per change, top first: its bookmark, its pull request with whether that is a draft or ready for review, and whether the change is pushed, changed since the push, or not pushed.", "Fails if the changes aren't a chain, if one has conflicts, or if one doesn't have exactly one bookmark. `submit` makes the same check before it changes anything.", "Changes nothing, locally or on GitHub."));
|
|
18921
18946
|
|
|
18947
|
+
// src/clients/pr-descriptions.ts
|
|
18948
|
+
class PrDescriptions extends Service()("jj-octo/PrDescriptions") {
|
|
18949
|
+
}
|
|
18950
|
+
var StoredRecord = Struct({
|
|
18951
|
+
bookmark: String4,
|
|
18952
|
+
commit: String4,
|
|
18953
|
+
description: String4
|
|
18954
|
+
});
|
|
18955
|
+
var decode2 = decodeUnknownOption2(fromJsonString2(StoredRecord));
|
|
18956
|
+
var RECORD = /^\d+$/u;
|
|
18957
|
+
var PARTIAL = /^\d+\.[\da-f-]+\.partial$/u;
|
|
18958
|
+
var STOPPED_AFTER_MS = 60 * 60 * 1000;
|
|
18959
|
+
var warn = (action) => (error2) => error(`couldn't ${action} the agreed pull request descriptions: ${error2.message}`);
|
|
18960
|
+
var prDescriptionsLayer = effect(PrDescriptions, gen2(function* () {
|
|
18961
|
+
const fs = yield* FileSystem;
|
|
18962
|
+
const jj = yield* Jj;
|
|
18963
|
+
const path = yield* Path;
|
|
18964
|
+
const directory = yield* cached2(gen2(function* () {
|
|
18965
|
+
const dotJj = path.join((yield* jj.read(["workspace", "root"])).trim(), ".jj");
|
|
18966
|
+
const repository = path.join(dotJj, "repo");
|
|
18967
|
+
const shared = (yield* fs.stat(repository)).type === "File" ? path.resolve(dotJj, (yield* fs.readFileString(repository)).trim()) : repository;
|
|
18968
|
+
return path.join(shared, "jj-octo", "pr-descriptions");
|
|
18969
|
+
}));
|
|
18970
|
+
const recordOf = (file) => fs.readFileString(file).pipe(map7(decode2), orElseSucceed2(() => none2()));
|
|
18971
|
+
const remove = (file, name) => fs.remove(file, { force: true }).pipe(catch_2(warn(`prune ${name} in`)));
|
|
18972
|
+
return {
|
|
18973
|
+
prune: (bookmarks) => gen2(function* () {
|
|
18974
|
+
const records = yield* directory;
|
|
18975
|
+
const names = (yield* fs.exists(records)) ? yield* fs.readDirectory(records) : [];
|
|
18976
|
+
const recorded = names.filter((name) => RECORD.test(name));
|
|
18977
|
+
const left = recorded.length === 0 ? [] : yield* bookmarks;
|
|
18978
|
+
for (const name of recorded) {
|
|
18979
|
+
const file = path.join(records, name);
|
|
18980
|
+
const record = yield* recordOf(file);
|
|
18981
|
+
if (isNone2(record) || !left.includes(record.value.bookmark)) {
|
|
18982
|
+
yield* remove(file, name);
|
|
18983
|
+
}
|
|
18984
|
+
}
|
|
18985
|
+
const now = Date.now();
|
|
18986
|
+
for (const name of names.filter((file) => PARTIAL.test(file))) {
|
|
18987
|
+
const file = path.join(records, name);
|
|
18988
|
+
const modified = yield* fs.stat(file).pipe(map7(({ mtime }) => getOrElse(mtime, () => new Date(0)).getTime()), orElseSucceed2(() => now));
|
|
18989
|
+
if (now - modified > STOPPED_AFTER_MS) {
|
|
18990
|
+
yield* remove(file, name);
|
|
18991
|
+
}
|
|
18992
|
+
}
|
|
18993
|
+
}).pipe(catch_2(warn("prune"))),
|
|
18994
|
+
read: (pullRequest, bookmark) => directory.pipe(flatMap4((records) => recordOf(path.join(records, String(pullRequest)))), map7(filter((record) => record.bookmark === bookmark)), orElseSucceed2(() => none2())),
|
|
18995
|
+
write: ({ bookmark, commit, description, pullRequest }) => gen2(function* () {
|
|
18996
|
+
const records = yield* directory;
|
|
18997
|
+
yield* fs.makeDirectory(records, { recursive: true });
|
|
18998
|
+
const record = path.join(records, String(pullRequest));
|
|
18999
|
+
const partial = `${record}.${crypto.randomUUID()}.partial`;
|
|
19000
|
+
yield* fs.writeFileString(partial, JSON.stringify({ bookmark, commit, description })).pipe(andThen2(fs.rename(partial, record)), tapError2(() => fs.remove(partial, { force: true }).pipe(ignore2)));
|
|
19001
|
+
}).pipe(catch_2(warn("record")))
|
|
19002
|
+
};
|
|
19003
|
+
}));
|
|
19004
|
+
|
|
18922
19005
|
// src/cli/gh/stack/plan.ts
|
|
18923
19006
|
var basesFor = (branches, trunk) => [
|
|
18924
19007
|
trunk,
|
|
@@ -18948,7 +19031,7 @@ var titleAndBody = (description) => {
|
|
|
18948
19031
|
const [title = "", ...rest] = description.trimEnd().split(`
|
|
18949
19032
|
`);
|
|
18950
19033
|
return { body: rest.join(`
|
|
18951
|
-
`).
|
|
19034
|
+
`).replace(/^\s*\n/u, ""), title };
|
|
18952
19035
|
};
|
|
18953
19036
|
var descriptionFrom = ({
|
|
18954
19037
|
body,
|
|
@@ -18963,30 +19046,38 @@ var normalizedDescription = (description, { ignoreClaudeCodeFooter }) => {
|
|
|
18963
19046
|
`);
|
|
18964
19047
|
return descriptionFrom(titleAndBody(ignoreClaudeCodeFooter ? text.replace(claudeCodeFooter, "") : text));
|
|
18965
19048
|
};
|
|
19049
|
+
var agreedDescription = ({
|
|
19050
|
+
isOwnEarlier,
|
|
19051
|
+
onGitHub,
|
|
19052
|
+
pushed,
|
|
19053
|
+
recorded
|
|
19054
|
+
}) => getOrElse(recorded, () => isOwnEarlier ? onGitHub : pushed);
|
|
18966
19055
|
var descriptionSyncFor = ({
|
|
19056
|
+
agreed,
|
|
18967
19057
|
local,
|
|
18968
|
-
onGitHub
|
|
18969
|
-
pushed
|
|
19058
|
+
onGitHub
|
|
18970
19059
|
}) => {
|
|
18971
19060
|
if (local === onGitHub) {
|
|
18972
19061
|
return { _tag: "InSync" };
|
|
18973
19062
|
}
|
|
18974
|
-
if (local ===
|
|
19063
|
+
if (local === agreed) {
|
|
18975
19064
|
return { _tag: "TakeGitHub" };
|
|
18976
19065
|
}
|
|
18977
|
-
return onGitHub ===
|
|
19066
|
+
return onGitHub === agreed ? { _tag: "SendLocal" } : { _tag: "Merge" };
|
|
18978
19067
|
};
|
|
18979
19068
|
|
|
18980
19069
|
// src/cli/gh/stack/descriptions.ts
|
|
18981
19070
|
class DescriptionConflict extends TaggedError2("DescriptionConflict") {
|
|
18982
19071
|
}
|
|
19072
|
+
var kept = (text) => ({ send: false, text });
|
|
19073
|
+
var sent = (text) => ({ send: true, text });
|
|
18983
19074
|
var ignoreClaudeCodeFooterKey = "jj-octo.ignore-claude-code-footer";
|
|
18984
19075
|
var ignoresClaudeCodeFooter = jjFlag(ignoreClaudeCodeFooterKey);
|
|
18985
19076
|
var descriptionsOf = (commits) => commits.length === 0 ? succeed6(new Map) : readLog(commits.join(" | "), String.raw`commit_id ++ "\t" ++ description.escape_json() ++ "\n"`).pipe(map7((output) => new Map(lines2(output).map((line) => {
|
|
18986
19077
|
const [commit = "", json = '""'] = line.split("\t");
|
|
18987
19078
|
return [commit, parseDescription(json)];
|
|
18988
19079
|
}))));
|
|
18989
|
-
var
|
|
19080
|
+
var ownHistory = (bookmark, changeId) => gen2(function* () {
|
|
18990
19081
|
const jj = yield* Jj;
|
|
18991
19082
|
const output = yield* jj.read([
|
|
18992
19083
|
"evolog",
|
|
@@ -18994,31 +19085,54 @@ var ownDescriptions = (bookmark, changeId) => gen2(function* () {
|
|
|
18994
19085
|
"-r",
|
|
18995
19086
|
bookmarkRevset(bookmark),
|
|
18996
19087
|
"-T",
|
|
18997
|
-
String.raw`commit.change_id() ++ "\t" ++ commit.description().escape_json() ++ "\n"`
|
|
19088
|
+
String.raw`commit.change_id() ++ "\t" ++ commit.commit_id() ++ "\t" ++ predecessors.map(|p| p.commit_id()).join(",") ++ "\t" ++ if(operation, operation.description()) ++ "\t" ++ commit.description().escape_json() ++ "\n"`
|
|
18998
19089
|
]);
|
|
18999
19090
|
return lines2(output).flatMap((line) => {
|
|
19000
|
-
const [change = "", json = '""'] = line.split("\t");
|
|
19001
|
-
|
|
19091
|
+
const [change = "", commit = "", predecessors = "", operation = "", json = '""'] = line.split("\t");
|
|
19092
|
+
const isImported = operation.startsWith("fetch from git") || operation.startsWith("import git");
|
|
19093
|
+
return change === changeId ? [
|
|
19094
|
+
{
|
|
19095
|
+
commit,
|
|
19096
|
+
description: parseDescription(json),
|
|
19097
|
+
isImported,
|
|
19098
|
+
predecessors: predecessors === "" ? [] : predecessors.split(",")
|
|
19099
|
+
}
|
|
19100
|
+
] : [];
|
|
19002
19101
|
});
|
|
19003
19102
|
});
|
|
19004
|
-
var
|
|
19103
|
+
var isDescribedHereFrom = (history, commit) => {
|
|
19104
|
+
const since = history.findIndex((entry) => entry.commit === commit);
|
|
19105
|
+
const descriptions = new Map(history.map((entry) => [entry.commit, entry.description]));
|
|
19106
|
+
return since !== -1 && history.slice(0, since).every(({ description, isImported, predecessors }) => !isImported || predecessors.some((each) => descriptions.get(each) === description));
|
|
19107
|
+
};
|
|
19108
|
+
var syncDescription = ({ bookmark, changeId, description }, pullRequest, {
|
|
19109
|
+
ignoreClaudeCodeFooter,
|
|
19110
|
+
overwriteDescriptions,
|
|
19111
|
+
pushedCommit,
|
|
19112
|
+
pushedDescription
|
|
19113
|
+
}) => gen2(function* () {
|
|
19005
19114
|
const git = yield* Git;
|
|
19006
19115
|
const jj = yield* Jj;
|
|
19116
|
+
const records = yield* PrDescriptions;
|
|
19007
19117
|
const describe = (text) => jj.run(["describe", "-r", bookmarkRevset(bookmark), "-m", text]);
|
|
19008
|
-
const
|
|
19009
|
-
const
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
const pushed =
|
|
19013
|
-
const
|
|
19014
|
-
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19118
|
+
const footer = { ignoreClaudeCodeFooter };
|
|
19119
|
+
const normalized = (text) => normalizedDescription(text, footer);
|
|
19120
|
+
const local = normalized(description);
|
|
19121
|
+
const onGitHub = normalized(descriptionFrom(pullRequest));
|
|
19122
|
+
const pushed = normalized(pushedDescription);
|
|
19123
|
+
const record = yield* records.read(pullRequest.number, bookmark);
|
|
19124
|
+
const history = local === onGitHub || isNone2(record) && onGitHub === pushed ? [] : yield* ownHistory(bookmark, changeId);
|
|
19125
|
+
const age = (commit) => history.findIndex((entry) => entry.commit === commit);
|
|
19126
|
+
const pushedAge = age(pushedCommit);
|
|
19127
|
+
const isPushedAgreedSince = (commit) => onGitHub === pushed && pushedAge !== -1 && pushedAge < age(commit);
|
|
19128
|
+
const recorded = record.pipe(filter(({ commit }) => isDescribedHereFrom(history, commit) && !isPushedAgreedSince(commit)), map(({ description: text }) => normalized(text)));
|
|
19129
|
+
const isOwnEarlier = onGitHub !== pushed && history.some((entry) => normalized(entry.description) === onGitHub);
|
|
19130
|
+
const agreed = agreedDescription({ isOwnEarlier, onGitHub, pushed, recorded });
|
|
19131
|
+
return yield* value4(descriptionSyncFor({ agreed, local, onGitHub })).pipe(tag3("InSync", () => succeed6(kept(local))), tag3("TakeGitHub", () => describe(onGitHub).pipe(andThen2(error(`took the description of #${pullRequest.number} from GitHub`)), as2(kept(onGitHub)))), tag3("SendLocal", () => succeed6(sent(local))), tag3("Merge", () => gen2(function* () {
|
|
19018
19132
|
if (overwriteDescriptions) {
|
|
19019
|
-
return
|
|
19133
|
+
return sent(local);
|
|
19020
19134
|
}
|
|
19021
|
-
const merged = yield* git.mergeText({ base:
|
|
19135
|
+
const merged = yield* git.mergeText({ base: agreed, local, remote: onGitHub });
|
|
19022
19136
|
if (merged.conflicted) {
|
|
19023
19137
|
return yield* new DescriptionConflict({
|
|
19024
19138
|
message: [
|
|
@@ -19032,7 +19146,7 @@ var syncDescription = ({ bookmark, changeId, description }, pullRequest, { ignor
|
|
|
19032
19146
|
});
|
|
19033
19147
|
}
|
|
19034
19148
|
yield* describe(merged.text);
|
|
19035
|
-
return
|
|
19149
|
+
return sent(merged.text);
|
|
19036
19150
|
})), exhaustive2);
|
|
19037
19151
|
});
|
|
19038
19152
|
|
|
@@ -19220,6 +19334,8 @@ var reconcile = ({
|
|
|
19220
19334
|
}
|
|
19221
19335
|
const onStacks = new Set(scope === "every stack" ? before.map(({ name }) => name) : scope.bookmarks);
|
|
19222
19336
|
yield* forget((yield* bookmarksAt("::trunk()")).filter((name) => onStacks.has(name)));
|
|
19337
|
+
const records = yield* PrDescriptions;
|
|
19338
|
+
yield* records.prune(provideService2(bookmarksAt("all()"), Jj, jj));
|
|
19223
19339
|
if (notCombined.length > 0) {
|
|
19224
19340
|
yield* new NotCombined({
|
|
19225
19341
|
message: [
|
|
@@ -19325,6 +19441,7 @@ var register = (remaining, pullRequests) => gen2(function* () {
|
|
|
19325
19441
|
var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function* () {
|
|
19326
19442
|
const github = yield* GitHub;
|
|
19327
19443
|
const jj = yield* Jj;
|
|
19444
|
+
const records = yield* PrDescriptions;
|
|
19328
19445
|
const bookmarks = stack.map(({ bookmark }) => bookmark);
|
|
19329
19446
|
const bases = basesFor(bookmarks, trunk);
|
|
19330
19447
|
const found = yield* all2(stack.map((change, index) => github.openPullRequest(change.bookmark).pipe(map7((pullRequest) => ({ base: bases[index] ?? trunk, change, pullRequest })))), { concurrency: "unbounded" });
|
|
@@ -19332,19 +19449,25 @@ var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function*
|
|
|
19332
19449
|
const pushedDescriptions = yield* descriptionsOf(bookmarks.flatMap((bookmark) => targets.get(bookmark)?.origin ?? []));
|
|
19333
19450
|
const ignoreClaudeCodeFooter = yield* ignoresClaudeCodeFooter;
|
|
19334
19451
|
const changes = [];
|
|
19335
|
-
for (const
|
|
19336
|
-
const pushed = targets.get(
|
|
19452
|
+
for (const { base, change, pullRequest } of found) {
|
|
19453
|
+
const pushed = targets.get(change.bookmark)?.origin;
|
|
19454
|
+
const options = {
|
|
19455
|
+
ignoreClaudeCodeFooter,
|
|
19456
|
+
overwriteDescriptions,
|
|
19457
|
+
pushedCommit: pushed,
|
|
19458
|
+
pushedDescription: (pushed === undefined ? undefined : pushedDescriptions.get(pushed)) ?? ""
|
|
19459
|
+
};
|
|
19337
19460
|
changes.push({
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19461
|
+
base,
|
|
19462
|
+
change,
|
|
19463
|
+
open: isNone2(pullRequest) ? none2() : some2({
|
|
19464
|
+
pullRequest: pullRequest.value,
|
|
19465
|
+
synced: yield* syncDescription(change, pullRequest.value, options)
|
|
19343
19466
|
})
|
|
19344
19467
|
});
|
|
19345
19468
|
}
|
|
19346
|
-
const moving = changes.flatMap(({ base,
|
|
19347
|
-
const wanted = changes.map(({
|
|
19469
|
+
const moving = changes.flatMap(({ base, open }) => isSome2(open) && open.value.pullRequest.base !== base ? [open.value.pullRequest] : []);
|
|
19470
|
+
const wanted = changes.map(({ open }) => map(open, ({ pullRequest }) => pullRequest.number));
|
|
19348
19471
|
const existing = yield* stackContaining(wanted.flatMap((number) => toArray(number)));
|
|
19349
19472
|
const dissolve = mustDissolve(existing, wanted, moving.length > 0);
|
|
19350
19473
|
if (dissolve && isSome2(existing)) {
|
|
@@ -19363,9 +19486,16 @@ var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function*
|
|
|
19363
19486
|
"push",
|
|
19364
19487
|
...bookmarks.flatMap((bookmark) => ["--bookmark", `exact:${bookmark}`])
|
|
19365
19488
|
]);
|
|
19489
|
+
const pushedTargets = yield* bookmarkTargets;
|
|
19490
|
+
const record = (bookmark, description, pullRequest) => records.write({
|
|
19491
|
+
bookmark,
|
|
19492
|
+
commit: pushedTargets.get(bookmark)?.local ?? "",
|
|
19493
|
+
description,
|
|
19494
|
+
pullRequest
|
|
19495
|
+
});
|
|
19366
19496
|
const numbers = [];
|
|
19367
|
-
for (const { base, change,
|
|
19368
|
-
if (isNone2(
|
|
19497
|
+
for (const { base, change, open } of changes) {
|
|
19498
|
+
if (isNone2(open)) {
|
|
19369
19499
|
const created = yield* github.createPullRequest({
|
|
19370
19500
|
...titleAndBody(change.description),
|
|
19371
19501
|
base,
|
|
@@ -19373,16 +19503,21 @@ var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function*
|
|
|
19373
19503
|
head: change.bookmark
|
|
19374
19504
|
});
|
|
19375
19505
|
yield* error(`opened #${created} for ${change.bookmark}`);
|
|
19506
|
+
yield* record(change.bookmark, change.description, created);
|
|
19376
19507
|
numbers.push(created);
|
|
19377
19508
|
} else {
|
|
19378
|
-
const {
|
|
19509
|
+
const {
|
|
19510
|
+
pullRequest: { number },
|
|
19511
|
+
synced: { send, text }
|
|
19512
|
+
} = open.value;
|
|
19379
19513
|
if (moved.has(number) && base !== trunk) {
|
|
19380
19514
|
yield* github.setBase(number, base);
|
|
19381
19515
|
}
|
|
19382
|
-
if (
|
|
19383
|
-
yield* github.setDescription(number, titleAndBody(
|
|
19516
|
+
if (send) {
|
|
19517
|
+
yield* github.setDescription(number, titleAndBody(text));
|
|
19384
19518
|
yield* error(`updated the description of #${number}`);
|
|
19385
19519
|
}
|
|
19520
|
+
yield* record(change.bookmark, text, number);
|
|
19386
19521
|
numbers.push(number);
|
|
19387
19522
|
}
|
|
19388
19523
|
}
|
|
@@ -19493,7 +19628,7 @@ var submitCommand = make31("submit", {
|
|
|
19493
19628
|
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")),
|
|
19494
19629
|
publish: Boolean6("publish").pipe(withDefault5(false), withDescription2("Open new pull requests ready for review, instead of as drafts")),
|
|
19495
19630
|
revision: revisionFlag("The top change to submit")
|
|
19496
|
-
}, ({ 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.
|
|
19631
|
+
}, ({ 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. That holds for a local edit pushed with `jj git push` too, since submit records the description that both sides last agreed on in `jj-octo/pr-descriptions` under the repository's `.jj/repo`, in the main workspace. 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."));
|
|
19497
19632
|
|
|
19498
19633
|
// src/cli/gh/command.ts
|
|
19499
19634
|
var jjGhCommand = make31("jj gh").pipe(withSubcommands([
|
|
@@ -19506,6 +19641,6 @@ var jjGhCommand = make31("jj gh").pipe(withSubcommands([
|
|
|
19506
19641
|
]), withHelp("Commands for publishing jj changes as GitHub pull requests", "A stack is a chain of changes, each published as one pull request on the branch its bookmark names. Each pull request targets the branch of the change below it, so it shows only that change's diff. A single change is a stack of one, and is published as an ordinary pull request.", "The stack of a change is every change connected to it through mutable changes. An empty working copy with no description isn't part of it. `log` shows the whole stack. `submit` and `merge` act on a change and the ones below it, which must be a chain: no merges, no conflicts, and exactly one bookmark on each change.", "The repository is the one the `origin` remote points to. The token is read from `GH_TOKEN` or `GITHUB_TOKEN`, and otherwise from `gh auth token`."));
|
|
19507
19642
|
|
|
19508
19643
|
// src/main.ts
|
|
19509
|
-
runMain2(run5(jjGhCommand, { version: package_default.version }).pipe(provide3([GitLive, JjLive]), provide3(layer13({
|
|
19644
|
+
runMain2(run5(jjGhCommand, { version: package_default.version }).pipe(provide3([GitLive, provideMerge(prDescriptionsLayer, JjLive)]), provide3(layer13({
|
|
19510
19645
|
builtIns: [Help, Version, Wizard, LogLevel]
|
|
19511
19646
|
})), provide3([layer12, layer14])));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jj-octo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "A jj subcommand for GitHub that supports stacked pull requests and works in Claude Code cloud sessions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@tsconfig/strictest": "2.0.8",
|
|
38
38
|
"@types/bun": "1.4.3",
|
|
39
39
|
"effect": "4.0.5",
|
|
40
|
-
"oxfmt": "0.
|
|
41
|
-
"oxlint": "1.
|
|
40
|
+
"oxfmt": "0.74.0",
|
|
41
|
+
"oxlint": "1.89.0",
|
|
42
42
|
"oxlint-tsgolint": "7.0.2004",
|
|
43
43
|
"typescript": "7.0.2"
|
|
44
44
|
},
|