jj-octo 0.7.1 → 0.8.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 +191 -35
- package/package.json +1 -1
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.0",
|
|
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: {
|
|
@@ -18449,6 +18474,10 @@ var anyOf = (revsets) => ["none()", ...revsets].join(" | ");
|
|
|
18449
18474
|
var bookmarkRevset = (bookmark) => `bookmarks(exact:"${bookmark}")`;
|
|
18450
18475
|
var lines2 = (output) => output.split(`
|
|
18451
18476
|
`).filter((line) => line !== "");
|
|
18477
|
+
var parseDescription = (json) => {
|
|
18478
|
+
const parsed = JSON.parse(json);
|
|
18479
|
+
return typeof parsed === "string" ? parsed : "";
|
|
18480
|
+
};
|
|
18452
18481
|
var readLog = (revset, template) => gen2(function* () {
|
|
18453
18482
|
const jj = yield* Jj;
|
|
18454
18483
|
return yield* jj.read(["log", "--no-graph", "--reversed", "-r", revset, "-T", template]);
|
|
@@ -18496,8 +18525,7 @@ var loggedChanges = (revset) => logLines(revset, CHANGE).pipe(map7((logged) => l
|
|
|
18496
18525
|
bookmarks = "",
|
|
18497
18526
|
json = '""'
|
|
18498
18527
|
] = line.split("\t");
|
|
18499
|
-
const
|
|
18500
|
-
const description = typeof parsed === "string" ? parsed : "";
|
|
18528
|
+
const description = parseDescription(json);
|
|
18501
18529
|
return {
|
|
18502
18530
|
bookmarks: bookmarks === "" ? [] : bookmarks.split(" "),
|
|
18503
18531
|
changeId,
|
|
@@ -18916,6 +18944,64 @@ var log2 = (revision) => gen2(function* () {
|
|
|
18916
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(`
|
|
18917
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."));
|
|
18918
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
|
+
|
|
18919
19005
|
// src/cli/gh/stack/plan.ts
|
|
18920
19006
|
var basesFor = (branches, trunk) => [
|
|
18921
19007
|
trunk,
|
|
@@ -18960,44 +19046,93 @@ var normalizedDescription = (description, { ignoreClaudeCodeFooter }) => {
|
|
|
18960
19046
|
`);
|
|
18961
19047
|
return descriptionFrom(titleAndBody(ignoreClaudeCodeFooter ? text.replace(claudeCodeFooter, "") : text));
|
|
18962
19048
|
};
|
|
19049
|
+
var agreedDescription = ({
|
|
19050
|
+
isOwnEarlier,
|
|
19051
|
+
onGitHub,
|
|
19052
|
+
pushed,
|
|
19053
|
+
recorded
|
|
19054
|
+
}) => getOrElse(recorded, () => isOwnEarlier ? onGitHub : pushed);
|
|
18963
19055
|
var descriptionSyncFor = ({
|
|
19056
|
+
agreed,
|
|
18964
19057
|
local,
|
|
18965
|
-
onGitHub
|
|
18966
|
-
pushed
|
|
19058
|
+
onGitHub
|
|
18967
19059
|
}) => {
|
|
18968
19060
|
if (local === onGitHub) {
|
|
18969
19061
|
return { _tag: "InSync" };
|
|
18970
19062
|
}
|
|
18971
|
-
if (local ===
|
|
19063
|
+
if (local === agreed) {
|
|
18972
19064
|
return { _tag: "TakeGitHub" };
|
|
18973
19065
|
}
|
|
18974
|
-
return onGitHub ===
|
|
19066
|
+
return onGitHub === agreed ? { _tag: "SendLocal" } : { _tag: "Merge" };
|
|
18975
19067
|
};
|
|
18976
19068
|
|
|
18977
19069
|
// src/cli/gh/stack/descriptions.ts
|
|
18978
19070
|
class DescriptionConflict extends TaggedError2("DescriptionConflict") {
|
|
18979
19071
|
}
|
|
19072
|
+
var kept = (text) => ({ send: false, text });
|
|
19073
|
+
var sent = (text) => ({ send: true, text });
|
|
18980
19074
|
var ignoreClaudeCodeFooterKey = "jj-octo.ignore-claude-code-footer";
|
|
18981
19075
|
var ignoresClaudeCodeFooter = jjFlag(ignoreClaudeCodeFooterKey);
|
|
18982
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) => {
|
|
18983
19077
|
const [commit = "", json = '""'] = line.split("\t");
|
|
18984
|
-
|
|
18985
|
-
return [commit, typeof parsed === "string" ? parsed : ""];
|
|
19078
|
+
return [commit, parseDescription(json)];
|
|
18986
19079
|
}))));
|
|
18987
|
-
var
|
|
19080
|
+
var ownHistory = (bookmark, changeId) => gen2(function* () {
|
|
19081
|
+
const jj = yield* Jj;
|
|
19082
|
+
const output = yield* jj.read([
|
|
19083
|
+
"evolog",
|
|
19084
|
+
"--no-graph",
|
|
19085
|
+
"-r",
|
|
19086
|
+
bookmarkRevset(bookmark),
|
|
19087
|
+
"-T",
|
|
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"`
|
|
19089
|
+
]);
|
|
19090
|
+
return lines2(output).flatMap((line) => {
|
|
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
|
+
] : [];
|
|
19101
|
+
});
|
|
19102
|
+
});
|
|
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* () {
|
|
18988
19114
|
const git = yield* Git;
|
|
18989
19115
|
const jj = yield* Jj;
|
|
19116
|
+
const records = yield* PrDescriptions;
|
|
18990
19117
|
const describe = (text) => jj.run(["describe", "-r", bookmarkRevset(bookmark), "-m", text]);
|
|
18991
|
-
const
|
|
18992
|
-
const
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
const pushed =
|
|
18996
|
-
|
|
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* () {
|
|
18997
19132
|
if (overwriteDescriptions) {
|
|
18998
|
-
return
|
|
19133
|
+
return sent(local);
|
|
18999
19134
|
}
|
|
19000
|
-
const merged = yield* git.mergeText({ base:
|
|
19135
|
+
const merged = yield* git.mergeText({ base: agreed, local, remote: onGitHub });
|
|
19001
19136
|
if (merged.conflicted) {
|
|
19002
19137
|
return yield* new DescriptionConflict({
|
|
19003
19138
|
message: [
|
|
@@ -19011,7 +19146,7 @@ var syncDescription = ({ bookmark, description }, pullRequest, { ignoreClaudeCod
|
|
|
19011
19146
|
});
|
|
19012
19147
|
}
|
|
19013
19148
|
yield* describe(merged.text);
|
|
19014
|
-
return
|
|
19149
|
+
return sent(merged.text);
|
|
19015
19150
|
})), exhaustive2);
|
|
19016
19151
|
});
|
|
19017
19152
|
|
|
@@ -19199,6 +19334,8 @@ var reconcile = ({
|
|
|
19199
19334
|
}
|
|
19200
19335
|
const onStacks = new Set(scope === "every stack" ? before.map(({ name }) => name) : scope.bookmarks);
|
|
19201
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));
|
|
19202
19339
|
if (notCombined.length > 0) {
|
|
19203
19340
|
yield* new NotCombined({
|
|
19204
19341
|
message: [
|
|
@@ -19304,6 +19441,7 @@ var register = (remaining, pullRequests) => gen2(function* () {
|
|
|
19304
19441
|
var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function* () {
|
|
19305
19442
|
const github = yield* GitHub;
|
|
19306
19443
|
const jj = yield* Jj;
|
|
19444
|
+
const records = yield* PrDescriptions;
|
|
19307
19445
|
const bookmarks = stack.map(({ bookmark }) => bookmark);
|
|
19308
19446
|
const bases = basesFor(bookmarks, trunk);
|
|
19309
19447
|
const found = yield* all2(stack.map((change, index) => github.openPullRequest(change.bookmark).pipe(map7((pullRequest) => ({ base: bases[index] ?? trunk, change, pullRequest })))), { concurrency: "unbounded" });
|
|
@@ -19311,19 +19449,25 @@ var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function*
|
|
|
19311
19449
|
const pushedDescriptions = yield* descriptionsOf(bookmarks.flatMap((bookmark) => targets.get(bookmark)?.origin ?? []));
|
|
19312
19450
|
const ignoreClaudeCodeFooter = yield* ignoresClaudeCodeFooter;
|
|
19313
19451
|
const changes = [];
|
|
19314
|
-
for (const
|
|
19315
|
-
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
|
+
};
|
|
19316
19460
|
changes.push({
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19461
|
+
base,
|
|
19462
|
+
change,
|
|
19463
|
+
open: isNone2(pullRequest) ? none2() : some2({
|
|
19464
|
+
pullRequest: pullRequest.value,
|
|
19465
|
+
synced: yield* syncDescription(change, pullRequest.value, options)
|
|
19322
19466
|
})
|
|
19323
19467
|
});
|
|
19324
19468
|
}
|
|
19325
|
-
const moving = changes.flatMap(({ base,
|
|
19326
|
-
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));
|
|
19327
19471
|
const existing = yield* stackContaining(wanted.flatMap((number) => toArray(number)));
|
|
19328
19472
|
const dissolve = mustDissolve(existing, wanted, moving.length > 0);
|
|
19329
19473
|
if (dissolve && isSome2(existing)) {
|
|
@@ -19342,9 +19486,16 @@ var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function*
|
|
|
19342
19486
|
"push",
|
|
19343
19487
|
...bookmarks.flatMap((bookmark) => ["--bookmark", `exact:${bookmark}`])
|
|
19344
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
|
+
});
|
|
19345
19496
|
const numbers = [];
|
|
19346
|
-
for (const { base, change,
|
|
19347
|
-
if (isNone2(
|
|
19497
|
+
for (const { base, change, open } of changes) {
|
|
19498
|
+
if (isNone2(open)) {
|
|
19348
19499
|
const created = yield* github.createPullRequest({
|
|
19349
19500
|
...titleAndBody(change.description),
|
|
19350
19501
|
base,
|
|
@@ -19352,16 +19503,21 @@ var publish = (stack, { draft, overwriteDescriptions, trunk }) => gen2(function*
|
|
|
19352
19503
|
head: change.bookmark
|
|
19353
19504
|
});
|
|
19354
19505
|
yield* error(`opened #${created} for ${change.bookmark}`);
|
|
19506
|
+
yield* record(change.bookmark, change.description, created);
|
|
19355
19507
|
numbers.push(created);
|
|
19356
19508
|
} else {
|
|
19357
|
-
const {
|
|
19509
|
+
const {
|
|
19510
|
+
pullRequest: { number },
|
|
19511
|
+
synced: { send, text }
|
|
19512
|
+
} = open.value;
|
|
19358
19513
|
if (moved.has(number) && base !== trunk) {
|
|
19359
19514
|
yield* github.setBase(number, base);
|
|
19360
19515
|
}
|
|
19361
|
-
if (
|
|
19362
|
-
yield* github.setDescription(number, titleAndBody(
|
|
19516
|
+
if (send) {
|
|
19517
|
+
yield* github.setDescription(number, titleAndBody(text));
|
|
19363
19518
|
yield* error(`updated the description of #${number}`);
|
|
19364
19519
|
}
|
|
19520
|
+
yield* record(change.bookmark, text, number);
|
|
19365
19521
|
numbers.push(number);
|
|
19366
19522
|
}
|
|
19367
19523
|
}
|
|
@@ -19472,7 +19628,7 @@ var submitCommand = make31("submit", {
|
|
|
19472
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")),
|
|
19473
19629
|
publish: Boolean6("publish").pipe(withDefault5(false), withDescription2("Open new pull requests ready for review, instead of as drafts")),
|
|
19474
19630
|
revision: revisionFlag("The top change to submit")
|
|
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."));
|
|
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."));
|
|
19476
19632
|
|
|
19477
19633
|
// src/cli/gh/command.ts
|
|
19478
19634
|
var jjGhCommand = make31("jj gh").pipe(withSubcommands([
|
|
@@ -19485,6 +19641,6 @@ var jjGhCommand = make31("jj gh").pipe(withSubcommands([
|
|
|
19485
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`."));
|
|
19486
19642
|
|
|
19487
19643
|
// src/main.ts
|
|
19488
|
-
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({
|
|
19489
19645
|
builtIns: [Help, Version, Wizard, LogLevel]
|
|
19490
19646
|
})), provide3([layer12, layer14])));
|