@ricsam/r5d-worker 0.0.83 → 0.0.87
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/cjs/main.cjs +180 -106
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +183 -104
- package/dist/cjs/workspace-automatic-sync-policy.cjs +12 -2
- package/dist/cjs/workspace-command-sync-policy.cjs +21 -0
- package/dist/cjs/workspace-git-sync.cjs +501 -191
- package/dist/mjs/main.mjs +185 -107
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +183 -104
- package/dist/mjs/workspace-automatic-sync-policy.mjs +9 -1
- package/dist/mjs/workspace-command-sync-policy.mjs +20 -0
- package/dist/mjs/workspace-git-sync.mjs +500 -191
- package/dist/types/main.d.ts +19 -2
- package/dist/types/project-worktrees.d.ts +12 -12
- package/dist/types/workspace-automatic-sync-policy.d.ts +24 -4
- package/dist/types/workspace-command-sync-policy.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +17 -9
- package/package.json +1 -1
package/dist/cjs/package.json
CHANGED
|
@@ -133,6 +133,29 @@ function git(cwd, args, action) {
|
|
|
133
133
|
function tryGit(cwd, args) {
|
|
134
134
|
return gitResult(cwd, args).exitCode === 0;
|
|
135
135
|
}
|
|
136
|
+
async function gitResultAsync(cwd, args) {
|
|
137
|
+
const subprocess = Bun.spawn(gitCommandArgs(args), {
|
|
138
|
+
cwd,
|
|
139
|
+
stdin: "ignore",
|
|
140
|
+
stdout: "pipe",
|
|
141
|
+
stderr: "pipe",
|
|
142
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
143
|
+
});
|
|
144
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
145
|
+
new Response(subprocess.stdout).text(),
|
|
146
|
+
new Response(subprocess.stderr).text(),
|
|
147
|
+
subprocess.exited
|
|
148
|
+
]);
|
|
149
|
+
return { exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
150
|
+
}
|
|
151
|
+
async function gitAsync(cwd, args, action) {
|
|
152
|
+
const result = await gitResultAsync(cwd, args);
|
|
153
|
+
if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
154
|
+
return result.stdout;
|
|
155
|
+
}
|
|
156
|
+
async function tryGitAsync(cwd, args) {
|
|
157
|
+
return (await gitResultAsync(cwd, args)).exitCode === 0;
|
|
158
|
+
}
|
|
136
159
|
function branchPath(projectRoot, branchName) {
|
|
137
160
|
(0, import_managed_paths.validateManagedBranchName)(branchName);
|
|
138
161
|
const result = import_node_path.default.join(projectRoot, ...branchName.split("/"));
|
|
@@ -192,6 +215,10 @@ function commonGitDirectory(checkoutPath) {
|
|
|
192
215
|
const result = gitResult(checkoutPath, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
193
216
|
return result.exitCode === 0 ? import_node_path.default.resolve(result.stdout) : null;
|
|
194
217
|
}
|
|
218
|
+
async function commonGitDirectoryAsync(checkoutPath) {
|
|
219
|
+
const result = await gitResultAsync(checkoutPath, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
220
|
+
return result.exitCode === 0 ? import_node_path.default.resolve(result.stdout) : null;
|
|
221
|
+
}
|
|
195
222
|
function hasLinkedProjectWorktreeLayout(input) {
|
|
196
223
|
const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
|
|
197
224
|
if (gitDirectoryKind(primaryPath) !== "directory") return false;
|
|
@@ -203,17 +230,19 @@ function hasLinkedProjectWorktreeLayout(input) {
|
|
|
203
230
|
return gitDirectoryKind(checkoutPath) === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
|
|
204
231
|
});
|
|
205
232
|
}
|
|
206
|
-
function hasCompatibleLinkedProjectWorktreeLayout(input) {
|
|
233
|
+
async function hasCompatibleLinkedProjectWorktreeLayout(input) {
|
|
207
234
|
const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
|
|
208
235
|
if (gitDirectoryKind(primaryPath) !== "directory") return false;
|
|
209
|
-
const primaryCommonDir =
|
|
236
|
+
const primaryCommonDir = await commonGitDirectoryAsync(primaryPath);
|
|
210
237
|
if (!primaryCommonDir) return false;
|
|
211
|
-
|
|
212
|
-
if (branchName === input.primaryBranchName)
|
|
238
|
+
for (const { branchName } of input.branches) {
|
|
239
|
+
if (branchName === input.primaryBranchName) continue;
|
|
213
240
|
const checkoutPath = branchPath(input.projectRoot, branchName);
|
|
214
241
|
const kind = gitDirectoryKind(checkoutPath);
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
if (kind === null) continue;
|
|
243
|
+
if (kind !== "file" || await commonGitDirectoryAsync(checkoutPath) !== primaryCommonDir) return false;
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
217
246
|
}
|
|
218
247
|
function fsyncDirectory(directory) {
|
|
219
248
|
const descriptor = import_node_fs.default.openSync(directory, "r");
|
|
@@ -627,24 +656,34 @@ function configureProjectBranchTracking(checkoutPath, branchName, originBranchNa
|
|
|
627
656
|
`configure upstream branch for ${branchName}`
|
|
628
657
|
);
|
|
629
658
|
}
|
|
630
|
-
function
|
|
631
|
-
|
|
632
|
-
|
|
659
|
+
async function configureProjectBranchTrackingAsync(checkoutPath, branchName, originBranchName) {
|
|
660
|
+
(0, import_managed_paths.validateManagedBranchName)(branchName);
|
|
661
|
+
(0, import_managed_paths.validateManagedBranchName)(originBranchName);
|
|
662
|
+
await gitAsync(checkoutPath, ["config", "--local", `branch.${branchName}.remote`, "origin"], `configure origin for ${branchName}`);
|
|
663
|
+
await gitAsync(
|
|
664
|
+
checkoutPath,
|
|
665
|
+
["config", "--local", `branch.${branchName}.merge`, `refs/heads/${originBranchName}`],
|
|
666
|
+
`configure upstream branch for ${branchName}`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
async function configureRepository(input) {
|
|
670
|
+
if (await tryGitAsync(input.primaryPath, ["remote", "get-url", "origin"])) {
|
|
671
|
+
await gitAsync(input.primaryPath, ["remote", "set-url", "origin", input.originUrl], "configure project origin");
|
|
633
672
|
} else {
|
|
634
|
-
|
|
673
|
+
await gitAsync(input.primaryPath, ["remote", "add", "origin", input.originUrl], "configure project origin");
|
|
635
674
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
675
|
+
await tryGitAsync(input.primaryPath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
|
|
676
|
+
await gitAsync(input.primaryPath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset project credential helpers");
|
|
677
|
+
await gitAsync(input.primaryPath, ["config", "--local", "credential.useHttpPath", "false"], "configure project credential path matching");
|
|
639
678
|
if (input.originCredentialUsername) {
|
|
640
|
-
|
|
679
|
+
await gitAsync(
|
|
641
680
|
input.primaryPath,
|
|
642
681
|
["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.originUrl), input.originCredentialUsername],
|
|
643
682
|
"configure project credential username"
|
|
644
683
|
);
|
|
645
684
|
}
|
|
646
685
|
if (input.credentialHelper) {
|
|
647
|
-
|
|
686
|
+
await gitAsync(
|
|
648
687
|
input.primaryPath,
|
|
649
688
|
["config", "--local", "--add", "credential.helper", input.credentialHelper],
|
|
650
689
|
"configure project credential helper"
|
|
@@ -654,23 +693,23 @@ function configureRepository(input) {
|
|
|
654
693
|
const name = input.gitIdentity.name.trim();
|
|
655
694
|
const email = input.gitIdentity.email.trim();
|
|
656
695
|
if (!name || !email) throw new Error("Git identity must include name and email");
|
|
657
|
-
|
|
658
|
-
|
|
696
|
+
await gitAsync(input.primaryPath, ["config", "--local", "user.name", name], "configure project Git user name");
|
|
697
|
+
await gitAsync(input.primaryPath, ["config", "--local", "user.email", email], "configure project Git user email");
|
|
659
698
|
}
|
|
660
699
|
if (input.primaryBranchName && input.defaultBranch && input.branches) {
|
|
661
|
-
|
|
700
|
+
await gitAsync(input.primaryPath, ["config", "--local", "push.default", "upstream"], "configure project push mapping");
|
|
662
701
|
for (const { branchName } of input.branches) {
|
|
663
702
|
const originBranchName = projectOriginBranchName({
|
|
664
703
|
branchName,
|
|
665
704
|
primaryBranchName: input.primaryBranchName,
|
|
666
705
|
defaultBranch: input.defaultBranch
|
|
667
706
|
});
|
|
668
|
-
|
|
707
|
+
await configureProjectBranchTrackingAsync(input.primaryPath, branchName, originBranchName);
|
|
669
708
|
}
|
|
670
709
|
}
|
|
671
710
|
}
|
|
672
|
-
function fetchProjectHeads(input) {
|
|
673
|
-
const originFetch =
|
|
711
|
+
async function fetchProjectHeads(input) {
|
|
712
|
+
const originFetch = await gitResultAsync(input.primaryPath, [
|
|
674
713
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(input.originUrl, input.credentialHelper, input.originCredentialUsername),
|
|
675
714
|
"fetch",
|
|
676
715
|
"--no-recurse-submodules",
|
|
@@ -681,7 +720,7 @@ function fetchProjectHeads(input) {
|
|
|
681
720
|
if (originFetch.exitCode !== 0) {
|
|
682
721
|
throw new Error(`Fetch project origin: ${originFetch.stderr || originFetch.stdout || `git exited ${originFetch.exitCode}`}`);
|
|
683
722
|
}
|
|
684
|
-
const mirrorFetch =
|
|
723
|
+
const mirrorFetch = await gitResultAsync(input.primaryPath, [
|
|
685
724
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.mirrorCredentialUsername),
|
|
686
725
|
"fetch",
|
|
687
726
|
"--no-recurse-submodules",
|
|
@@ -696,13 +735,13 @@ ${mirrorFetch.stdout}`
|
|
|
696
735
|
throw new Error(`Fetch project head mirror: ${mirrorFetch.stderr || mirrorFetch.stdout || `git exited ${mirrorFetch.exitCode}`}`);
|
|
697
736
|
}
|
|
698
737
|
}
|
|
699
|
-
function ensureCommitAvailable(input) {
|
|
700
|
-
if (
|
|
738
|
+
async function ensureCommitAvailable(input) {
|
|
739
|
+
if (await tryGitAsync(input.primaryPath, ["cat-file", "-e", `${input.commitHash}^{commit}`])) return;
|
|
701
740
|
for (const [url, username] of [
|
|
702
741
|
[input.mirrorUrl, input.mirrorCredentialUsername],
|
|
703
742
|
[input.originUrl, input.originCredentialUsername]
|
|
704
743
|
]) {
|
|
705
|
-
if (
|
|
744
|
+
if (await tryGitAsync(input.primaryPath, [
|
|
706
745
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(url, input.credentialHelper, username),
|
|
707
746
|
"fetch",
|
|
708
747
|
"--no-recurse-submodules",
|
|
@@ -714,34 +753,34 @@ function ensureCommitAvailable(input) {
|
|
|
714
753
|
}
|
|
715
754
|
throw new Error(`Project base commit ${input.commitHash} is unavailable from origin and the canonical mirror`);
|
|
716
755
|
}
|
|
717
|
-
function seedForBranch(input) {
|
|
756
|
+
async function seedForBranch(input) {
|
|
718
757
|
const mirrorRef = `refs/r5d/mirror/${input.branch.branchName}`;
|
|
719
|
-
const mirrorHead =
|
|
758
|
+
const mirrorHead = await tryGitAsync(input.primaryPath, ["show-ref", "--verify", "--quiet", mirrorRef]) ? await gitAsync(input.primaryPath, ["rev-parse", mirrorRef], "resolve project mirror head") : null;
|
|
720
759
|
if (input.requireMirrorHead && !mirrorHead) {
|
|
721
760
|
throw new Error(`Exact hidden project mirror head is missing for ${input.branch.branchName}`);
|
|
722
761
|
}
|
|
723
762
|
const seed = mirrorHead ?? input.branch.baseCommitHash;
|
|
724
|
-
ensureCommitAvailable({ ...input, commitHash: seed });
|
|
763
|
+
await ensureCommitAvailable({ ...input, commitHash: seed });
|
|
725
764
|
return { seed, mirrorHead };
|
|
726
765
|
}
|
|
727
|
-
function preserveHead(checkoutPath) {
|
|
728
|
-
if (!
|
|
729
|
-
const head =
|
|
730
|
-
|
|
766
|
+
async function preserveHead(checkoutPath) {
|
|
767
|
+
if (!await tryGitAsync(checkoutPath, ["rev-parse", "--verify", "HEAD"])) return;
|
|
768
|
+
const head = await gitAsync(checkoutPath, ["rev-parse", "HEAD"], "read pre-reconciliation project head");
|
|
769
|
+
await gitAsync(checkoutPath, ["update-ref", `refs/r5d/pre-connect/${head}`, head], "preserve pre-reconciliation project head");
|
|
731
770
|
}
|
|
732
|
-
function initializeLinkedLayout(input) {
|
|
771
|
+
async function initializeLinkedLayout(input) {
|
|
733
772
|
for (const { branchName } of [...input.branches].sort((left, right) => right.branchName.length - left.branchName.length)) {
|
|
734
773
|
import_node_fs.default.rmSync(branchPath(input.projectRoot, branchName), { recursive: true, force: true });
|
|
735
774
|
}
|
|
736
775
|
const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
|
|
737
776
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(primaryPath), { recursive: true });
|
|
738
|
-
|
|
739
|
-
configureRepository({ ...input, primaryPath });
|
|
740
|
-
fetchProjectHeads({ ...input, primaryPath });
|
|
777
|
+
await gitAsync(void 0, ["init", `--initial-branch=${input.primaryBranchName}`, primaryPath], "initialize primary project clone");
|
|
778
|
+
await configureRepository({ ...input, primaryPath });
|
|
779
|
+
await fetchProjectHeads({ ...input, primaryPath });
|
|
741
780
|
const seeds = /* @__PURE__ */ new Map();
|
|
742
781
|
const mirrorHeads = /* @__PURE__ */ new Map();
|
|
743
782
|
for (const branch of input.branches) {
|
|
744
|
-
const resolved = seedForBranch({
|
|
783
|
+
const resolved = await seedForBranch({
|
|
745
784
|
...input,
|
|
746
785
|
primaryPath,
|
|
747
786
|
branch,
|
|
@@ -750,63 +789,71 @@ function initializeLinkedLayout(input) {
|
|
|
750
789
|
seeds.set(branch.branchName, resolved.seed);
|
|
751
790
|
mirrorHeads.set(branch.branchName, resolved.mirrorHead);
|
|
752
791
|
}
|
|
753
|
-
|
|
792
|
+
await gitAsync(
|
|
754
793
|
primaryPath,
|
|
755
794
|
["checkout", "--no-recurse-submodules", "-B", input.primaryBranchName, seeds.get(input.primaryBranchName)],
|
|
756
795
|
"anchor primary project worktree"
|
|
757
796
|
);
|
|
758
797
|
for (const branch of input.branches) {
|
|
759
798
|
if (branch.branchName === input.primaryBranchName) continue;
|
|
760
|
-
|
|
799
|
+
await gitAsync(
|
|
800
|
+
primaryPath,
|
|
801
|
+
["branch", "-f", branch.branchName, seeds.get(branch.branchName)],
|
|
802
|
+
`anchor project branch ${branch.branchName}`
|
|
803
|
+
);
|
|
761
804
|
const checkoutPath = branchPath(input.projectRoot, branch.branchName);
|
|
762
805
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
|
|
763
|
-
|
|
806
|
+
await gitAsync(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
|
|
764
807
|
}
|
|
765
808
|
return mirrorHeads;
|
|
766
809
|
}
|
|
767
|
-
function reconcileLinkedLayout(input) {
|
|
810
|
+
async function reconcileLinkedLayout(input) {
|
|
768
811
|
const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
|
|
769
|
-
configureRepository({ ...input, primaryPath });
|
|
770
|
-
|
|
771
|
-
fetchProjectHeads({ ...input, primaryPath });
|
|
812
|
+
await configureRepository({ ...input, primaryPath });
|
|
813
|
+
await tryGitAsync(primaryPath, ["worktree", "prune"]);
|
|
814
|
+
await fetchProjectHeads({ ...input, primaryPath });
|
|
772
815
|
const mirrorHeads = /* @__PURE__ */ new Map();
|
|
773
816
|
const mutatedBranches = /* @__PURE__ */ new Set();
|
|
774
817
|
for (const branch of input.branches) {
|
|
775
818
|
const checkoutPath = branchPath(input.projectRoot, branch.branchName);
|
|
776
|
-
const resolved = seedForBranch({
|
|
819
|
+
const resolved = await seedForBranch({
|
|
777
820
|
...input,
|
|
778
821
|
primaryPath,
|
|
779
822
|
branch,
|
|
780
823
|
requireMirrorHead: input.exactMirrorBranches?.has(branch.branchName)
|
|
781
824
|
});
|
|
782
825
|
mirrorHeads.set(branch.branchName, resolved.mirrorHead);
|
|
783
|
-
if (branch.branchName === input.primaryBranchName ||
|
|
784
|
-
const currentHead =
|
|
826
|
+
if (branch.branchName === input.primaryBranchName || await commonGitDirectoryAsync(checkoutPath) === await commonGitDirectoryAsync(primaryPath)) {
|
|
827
|
+
const currentHead = await gitAsync(checkoutPath, ["rev-parse", "HEAD"], `resolve linked worktree head ${branch.branchName}`);
|
|
785
828
|
if (currentHead === resolved.seed) continue;
|
|
786
829
|
input.beforeDestructiveMutation();
|
|
787
830
|
mutatedBranches.add(branch.branchName);
|
|
788
|
-
preserveHead(checkoutPath);
|
|
789
|
-
|
|
831
|
+
await preserveHead(checkoutPath);
|
|
832
|
+
await gitAsync(checkoutPath, ["reset", "--hard", resolved.seed], `anchor linked worktree ${branch.branchName}`);
|
|
790
833
|
continue;
|
|
791
834
|
}
|
|
792
835
|
input.beforeDestructiveMutation();
|
|
793
836
|
mutatedBranches.add(branch.branchName);
|
|
794
837
|
import_node_fs.default.rmSync(checkoutPath, { recursive: true, force: true });
|
|
795
|
-
|
|
838
|
+
await gitAsync(primaryPath, ["branch", "-f", branch.branchName, resolved.seed], `anchor project branch ${branch.branchName}`);
|
|
796
839
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
|
|
797
|
-
|
|
840
|
+
await gitAsync(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
|
|
798
841
|
}
|
|
799
842
|
return { mirrorHeads, mutatedBranches };
|
|
800
843
|
}
|
|
801
|
-
function aheadBehind(checkoutPath, branchName, primaryBranchName, defaultBranch) {
|
|
844
|
+
async function aheadBehind(checkoutPath, branchName, primaryBranchName, defaultBranch) {
|
|
802
845
|
const originBranchName = projectOriginBranchName({ branchName, primaryBranchName, defaultBranch });
|
|
803
846
|
const originRef = `refs/remotes/origin/${originBranchName}`;
|
|
804
|
-
if (!
|
|
805
|
-
const output =
|
|
847
|
+
if (!await tryGitAsync(checkoutPath, ["show-ref", "--verify", "--quiet", originRef])) return { ahead: null, behind: null };
|
|
848
|
+
const output = await gitAsync(
|
|
849
|
+
checkoutPath,
|
|
850
|
+
["rev-list", "--left-right", "--count", `${originRef}...HEAD`],
|
|
851
|
+
"measure project origin divergence"
|
|
852
|
+
);
|
|
806
853
|
const [behind, ahead] = output.split(/\s+/).map(Number);
|
|
807
854
|
return Number.isFinite(ahead) && Number.isFinite(behind) ? { ahead, behind } : { ahead: null, behind: null };
|
|
808
855
|
}
|
|
809
|
-
function ensureProjectWorktrees(input) {
|
|
856
|
+
async function ensureProjectWorktrees(input) {
|
|
810
857
|
const projectRoot = import_node_path.default.resolve(input.projectRoot);
|
|
811
858
|
const defaultBranch = input.defaultBranch ?? input.primaryBranchName;
|
|
812
859
|
assertNonOverlappingBranches(input.branches);
|
|
@@ -822,7 +869,7 @@ function ensureProjectWorktrees(input) {
|
|
|
822
869
|
};
|
|
823
870
|
try {
|
|
824
871
|
import_node_fs.default.mkdirSync(projectRoot, { recursive: true });
|
|
825
|
-
const linked = hasCompatibleLinkedProjectWorktreeLayout({
|
|
872
|
+
const linked = await hasCompatibleLinkedProjectWorktreeLayout({
|
|
826
873
|
projectRoot,
|
|
827
874
|
primaryBranchName: input.primaryBranchName,
|
|
828
875
|
branches: input.branches
|
|
@@ -830,7 +877,7 @@ function ensureProjectWorktrees(input) {
|
|
|
830
877
|
let mirrorHeads;
|
|
831
878
|
let mutatedBranches;
|
|
832
879
|
if (linked) {
|
|
833
|
-
({ mirrorHeads, mutatedBranches } = reconcileLinkedLayout({
|
|
880
|
+
({ mirrorHeads, mutatedBranches } = await reconcileLinkedLayout({
|
|
834
881
|
...input,
|
|
835
882
|
projectRoot,
|
|
836
883
|
defaultBranch,
|
|
@@ -838,7 +885,7 @@ function ensureProjectWorktrees(input) {
|
|
|
838
885
|
}));
|
|
839
886
|
} else {
|
|
840
887
|
ensureSnapshot();
|
|
841
|
-
mirrorHeads = initializeLinkedLayout({ ...input, projectRoot, defaultBranch });
|
|
888
|
+
mirrorHeads = await initializeLinkedLayout({ ...input, projectRoot, defaultBranch });
|
|
842
889
|
mutatedBranches = new Set(input.branches.map(({ branchName }) => branchName));
|
|
843
890
|
}
|
|
844
891
|
const preparedSnapshot = snapshot;
|
|
@@ -856,17 +903,19 @@ function ensureProjectWorktrees(input) {
|
|
|
856
903
|
deletionMode: "git"
|
|
857
904
|
});
|
|
858
905
|
}
|
|
859
|
-
const states =
|
|
906
|
+
const states = [];
|
|
907
|
+
for (const branch of input.branches) {
|
|
860
908
|
const checkoutPath = branchPath(projectRoot, branch.branchName);
|
|
861
|
-
const divergence = aheadBehind(checkoutPath, branch.branchName, input.primaryBranchName, defaultBranch);
|
|
862
|
-
|
|
909
|
+
const divergence = await aheadBehind(checkoutPath, branch.branchName, input.primaryBranchName, defaultBranch);
|
|
910
|
+
states.push({
|
|
863
911
|
branchName: branch.branchName,
|
|
864
912
|
branchPath: checkoutPath,
|
|
865
|
-
head:
|
|
913
|
+
head: await gitAsync(checkoutPath, ["rev-parse", "HEAD"], `resolve project head ${branch.branchName}`),
|
|
866
914
|
mirrorHead: mirrorHeads.get(branch.branchName) ?? null,
|
|
867
915
|
...divergence
|
|
868
|
-
};
|
|
869
|
-
}
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
states.sort((left, right) => left.branchName.localeCompare(right.branchName));
|
|
870
919
|
if (snapshot) persistReconciledProjectAndConsumeSnapshot(snapshot);
|
|
871
920
|
return states;
|
|
872
921
|
} catch (error) {
|
|
@@ -882,14 +931,31 @@ function ensureProjectWorktrees(input) {
|
|
|
882
931
|
throw error;
|
|
883
932
|
}
|
|
884
933
|
}
|
|
934
|
+
const WORKTREE_OPERATION_STATE_PATHS = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer"];
|
|
935
|
+
function perWorktreeGitDirectory(checkoutPath) {
|
|
936
|
+
const gitPath = import_node_path.default.join(checkoutPath, ".git");
|
|
937
|
+
let stat;
|
|
938
|
+
try {
|
|
939
|
+
stat = import_node_fs.default.lstatSync(gitPath);
|
|
940
|
+
} catch {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
if (stat.isDirectory()) return gitPath;
|
|
944
|
+
if (!stat.isFile()) return null;
|
|
945
|
+
let pointer;
|
|
946
|
+
try {
|
|
947
|
+
pointer = import_node_fs.default.readFileSync(gitPath, "utf8");
|
|
948
|
+
} catch {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
const match = /^gitdir:[ \t]*(.+?)[ \t\r\n]*$/m.exec(pointer);
|
|
952
|
+
if (!match?.[1]) return null;
|
|
953
|
+
return import_node_path.default.resolve(checkoutPath, match[1]);
|
|
954
|
+
}
|
|
885
955
|
function projectWorktreeOperationInProgress(checkoutPath) {
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (result.exitCode !== 0) return false;
|
|
890
|
-
const resolved = import_node_path.default.isAbsolute(result.stdout) ? result.stdout : import_node_path.default.resolve(checkoutPath, result.stdout);
|
|
891
|
-
return import_node_fs.default.existsSync(resolved);
|
|
892
|
-
});
|
|
956
|
+
const gitDirectory = perWorktreeGitDirectory(checkoutPath);
|
|
957
|
+
if (!gitDirectory) return false;
|
|
958
|
+
return WORKTREE_OPERATION_STATE_PATHS.some((statePath) => import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, statePath)));
|
|
893
959
|
}
|
|
894
960
|
function createLinkedProjectBranch(input) {
|
|
895
961
|
(0, import_managed_paths.validateManagedBranchName)(input.sourceBranchName);
|
|
@@ -1030,9 +1096,9 @@ function removeProjectWorktrees(input) {
|
|
|
1030
1096
|
}
|
|
1031
1097
|
import_node_fs.default.rmSync(projectRoot, { recursive: true, force: true });
|
|
1032
1098
|
}
|
|
1033
|
-
function deleteProjectMirrorBranch(input) {
|
|
1099
|
+
async function deleteProjectMirrorBranch(input) {
|
|
1034
1100
|
(0, import_managed_paths.validateManagedBranchName)(input.branchName);
|
|
1035
|
-
const deleted =
|
|
1101
|
+
const deleted = await gitResultAsync(input.gitDirectory, [
|
|
1036
1102
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
|
|
1037
1103
|
"push",
|
|
1038
1104
|
"--no-recurse-submodules",
|
|
@@ -1046,23 +1112,23 @@ ${deleted.stdout}`)) {
|
|
|
1046
1112
|
);
|
|
1047
1113
|
}
|
|
1048
1114
|
}
|
|
1049
|
-
function pushProjectMirrorHeads(input) {
|
|
1115
|
+
async function pushProjectMirrorHeads(input) {
|
|
1050
1116
|
const results = [];
|
|
1051
1117
|
for (const branchName of [...input.branchNames].sort()) {
|
|
1052
1118
|
if (input.onlyBranches && !input.onlyBranches.has(branchName)) continue;
|
|
1053
1119
|
const checkoutPath = branchPath(input.projectRoot, branchName);
|
|
1054
|
-
const head = input.publicationHeads ? input.publicationHeads.get(branchName) :
|
|
1120
|
+
const head = input.publicationHeads ? input.publicationHeads.get(branchName) : await gitAsync(checkoutPath, ["rev-parse", "HEAD"], `resolve mirror head for ${branchName}`);
|
|
1055
1121
|
if (!head || !/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(head)) {
|
|
1056
1122
|
throw new Error(`Missing exact publication head for project branch ${branchName}`);
|
|
1057
1123
|
}
|
|
1058
|
-
if (!
|
|
1124
|
+
if (!await tryGitAsync(checkoutPath, ["cat-file", "-e", `${head}^{commit}`])) {
|
|
1059
1125
|
throw new Error(`Exact publication head ${head} is unavailable for project branch ${branchName}`);
|
|
1060
1126
|
}
|
|
1061
1127
|
if (projectWorktreeOperationInProgress(checkoutPath)) {
|
|
1062
1128
|
results.push({ branchName, head, pushed: false, reason: "git_operation_in_progress" });
|
|
1063
1129
|
continue;
|
|
1064
1130
|
}
|
|
1065
|
-
|
|
1131
|
+
await gitAsync(
|
|
1066
1132
|
checkoutPath,
|
|
1067
1133
|
[
|
|
1068
1134
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
|
|
@@ -1083,9 +1149,9 @@ function assertProjectMirrorHeadsPushed(results) {
|
|
|
1083
1149
|
throw new Error(`Hidden project mirror publication skipped ${skipped.branchName}${skipped.reason ? ` (${skipped.reason})` : ""}`);
|
|
1084
1150
|
}
|
|
1085
1151
|
}
|
|
1086
|
-
function observeProjectMirrorHeads(input) {
|
|
1152
|
+
async function observeProjectMirrorHeads(input) {
|
|
1087
1153
|
const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
|
|
1088
|
-
const fetch =
|
|
1154
|
+
const fetch = await gitResultAsync(primaryPath, [
|
|
1089
1155
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
|
|
1090
1156
|
"fetch",
|
|
1091
1157
|
"--no-recurse-submodules",
|
|
@@ -1095,50 +1161,63 @@ function observeProjectMirrorHeads(input) {
|
|
|
1095
1161
|
]);
|
|
1096
1162
|
if (fetch.exitCode !== 0)
|
|
1097
1163
|
throw new Error(`Refresh project head mirror: ${fetch.stderr || fetch.stdout || `git exited ${fetch.exitCode}`}`);
|
|
1098
|
-
|
|
1164
|
+
const observations = [];
|
|
1165
|
+
for (const branchName of [...input.branchNames].sort()) {
|
|
1099
1166
|
const checkoutPath = branchPath(input.projectRoot, branchName);
|
|
1100
|
-
const previousHead =
|
|
1167
|
+
const previousHead = await gitAsync(checkoutPath, ["rev-parse", "HEAD"], `resolve local project head ${branchName}`);
|
|
1101
1168
|
const mirrorRef = `refs/r5d/mirror/${branchName}`;
|
|
1102
|
-
const mirrorHead =
|
|
1169
|
+
const mirrorHead = await tryGitAsync(primaryPath, ["show-ref", "--verify", "--quiet", mirrorRef]) ? await gitAsync(primaryPath, ["rev-parse", mirrorRef], `resolve refreshed mirror head ${branchName}`) : null;
|
|
1103
1170
|
const shouldMove = Boolean(
|
|
1104
|
-
mirrorHead && mirrorHead !== previousHead && (input.allowNonFastForward ||
|
|
1171
|
+
mirrorHead && mirrorHead !== previousHead && (input.allowNonFastForward || await tryGitAsync(checkoutPath, ["merge-base", "--is-ancestor", previousHead, mirrorHead]))
|
|
1105
1172
|
);
|
|
1106
|
-
|
|
1173
|
+
observations.push({
|
|
1107
1174
|
branchName,
|
|
1108
1175
|
previousHead,
|
|
1109
1176
|
mirrorHead,
|
|
1110
1177
|
mode: input.allowNonFastForward ? "reseed" : "fast_forward",
|
|
1111
1178
|
shouldMove
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
return observations;
|
|
1114
1182
|
}
|
|
1115
|
-
function applyObservedProjectMirrorHeads(input) {
|
|
1116
|
-
|
|
1183
|
+
async function applyObservedProjectMirrorHeads(input) {
|
|
1184
|
+
const results = [];
|
|
1185
|
+
for (const observation of [...input.observations].sort((left, right) => left.branchName.localeCompare(right.branchName))) {
|
|
1117
1186
|
const { branchName, mirrorHead } = observation;
|
|
1118
1187
|
const checkoutPath = branchPath(input.projectRoot, branchName);
|
|
1119
|
-
const currentHead =
|
|
1188
|
+
const currentHead = await gitAsync(checkoutPath, ["rev-parse", "HEAD"], `resolve local project head ${branchName}`);
|
|
1120
1189
|
if (!observation.shouldMove || !mirrorHead || currentHead === mirrorHead || currentHead !== observation.previousHead) {
|
|
1121
|
-
|
|
1190
|
+
results.push({ branchName, previousHead: currentHead, mirrorHead, moved: false });
|
|
1191
|
+
continue;
|
|
1122
1192
|
}
|
|
1123
|
-
if (!
|
|
1193
|
+
if (!await tryGitAsync(checkoutPath, ["cat-file", "-e", `${mirrorHead}^{commit}`])) {
|
|
1124
1194
|
throw new Error(`Observed hidden mirror head is no longer available for ${branchName}: ${mirrorHead}`);
|
|
1125
1195
|
}
|
|
1126
1196
|
const mutationAllowed = () => !projectWorktreeOperationInProgress(checkoutPath) && input.branchMutationAllowed?.(branchName, checkoutPath) !== false;
|
|
1127
1197
|
if (!mutationAllowed()) {
|
|
1128
|
-
|
|
1198
|
+
results.push({ branchName, previousHead: currentHead, mirrorHead, moved: false });
|
|
1199
|
+
continue;
|
|
1129
1200
|
}
|
|
1130
1201
|
if (observation.mode === "reseed") {
|
|
1131
|
-
preserveHead(checkoutPath);
|
|
1132
|
-
if (!mutationAllowed())
|
|
1202
|
+
await preserveHead(checkoutPath);
|
|
1203
|
+
if (!mutationAllowed()) {
|
|
1204
|
+
results.push({ branchName, previousHead: currentHead, mirrorHead, moved: false });
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1133
1207
|
}
|
|
1134
1208
|
const action = observation.mode === "reseed" ? "reseed" : "fast-forward";
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1209
|
+
await gitAsync(checkoutPath, ["read-tree", "--reset", mirrorHead], `${action} project index ${branchName}`);
|
|
1210
|
+
await gitAsync(
|
|
1211
|
+
checkoutPath,
|
|
1212
|
+
["update-ref", `refs/heads/${branchName}`, mirrorHead, currentHead],
|
|
1213
|
+
`${action} project branch ${branchName}`
|
|
1214
|
+
);
|
|
1215
|
+
results.push({ branchName, previousHead: currentHead, mirrorHead, moved: true });
|
|
1216
|
+
}
|
|
1217
|
+
return results;
|
|
1139
1218
|
}
|
|
1140
|
-
function updateProjectHeadsFromMirror(input) {
|
|
1141
|
-
const observations = observeProjectMirrorHeads(input);
|
|
1219
|
+
async function updateProjectHeadsFromMirror(input) {
|
|
1220
|
+
const observations = await observeProjectMirrorHeads(input);
|
|
1142
1221
|
return applyObservedProjectMirrorHeads({
|
|
1143
1222
|
projectRoot: input.projectRoot,
|
|
1144
1223
|
primaryBranchName: input.primaryBranchName,
|
|
@@ -1146,14 +1225,14 @@ function updateProjectHeadsFromMirror(input) {
|
|
|
1146
1225
|
branchMutationAllowed: input.branchMutationAllowed
|
|
1147
1226
|
});
|
|
1148
1227
|
}
|
|
1149
|
-
function fastForwardProjectHeadsFromMirror(input) {
|
|
1150
|
-
return updateProjectHeadsFromMirror({ ...input, allowNonFastForward: false }).map(({ moved, ...state }) => ({
|
|
1228
|
+
async function fastForwardProjectHeadsFromMirror(input) {
|
|
1229
|
+
return (await updateProjectHeadsFromMirror({ ...input, allowNonFastForward: false })).map(({ moved, ...state }) => ({
|
|
1151
1230
|
...state,
|
|
1152
1231
|
fastForwarded: moved
|
|
1153
1232
|
}));
|
|
1154
1233
|
}
|
|
1155
|
-
function reseedProjectHeadsFromMirror(input) {
|
|
1156
|
-
return updateProjectHeadsFromMirror({ ...input, allowNonFastForward: true }).map(({ moved, ...state }) => ({
|
|
1234
|
+
async function reseedProjectHeadsFromMirror(input) {
|
|
1235
|
+
return (await updateProjectHeadsFromMirror({ ...input, allowNonFastForward: true })).map(({ moved, ...state }) => ({
|
|
1157
1236
|
...state,
|
|
1158
1237
|
reseeded: moved
|
|
1159
1238
|
}));
|
|
@@ -23,7 +23,9 @@ __export(workspace_automatic_sync_policy_exports, {
|
|
|
23
23
|
pendingCreatedBranchAutomaticSyncDeferral: () => pendingCreatedBranchAutomaticSyncDeferral,
|
|
24
24
|
pendingCreatedBranchKey: () => pendingCreatedBranchKey,
|
|
25
25
|
projectBranchHasActiveWorkspaceTarget: () => projectBranchHasActiveWorkspaceTarget,
|
|
26
|
-
|
|
26
|
+
projectBranchMountBusyDuringExclusiveSync: () => projectBranchMountBusyDuringExclusiveSync,
|
|
27
|
+
shouldDeferAutomaticWorkspaceSyncForPendingBranch: () => shouldDeferAutomaticWorkspaceSyncForPendingBranch,
|
|
28
|
+
workspacePlansMountBusyDuringExclusiveSync: () => workspacePlansMountBusyDuringExclusiveSync
|
|
27
29
|
});
|
|
28
30
|
module.exports = __toCommonJS(workspace_automatic_sync_policy_exports);
|
|
29
31
|
const PENDING_CREATED_BRANCH_AUTOMATIC_SYNC_GRACE_MS = 12e4;
|
|
@@ -43,6 +45,12 @@ function projectBranchHasActiveWorkspaceTarget(projectId, branchName, activeTarg
|
|
|
43
45
|
}
|
|
44
46
|
return false;
|
|
45
47
|
}
|
|
48
|
+
function projectBranchMountBusyDuringExclusiveSync(input) {
|
|
49
|
+
return input.executionDisabled || input.worktreeOperationInProgress || input.retainedCanonicalProcessGroup || projectBranchHasActiveWorkspaceTarget(input.projectId, input.branchName, input.activeTargets);
|
|
50
|
+
}
|
|
51
|
+
function workspacePlansMountBusyDuringExclusiveSync(activeTargets, retainedCanonicalProcessGroup = false) {
|
|
52
|
+
return retainedCanonicalProcessGroup || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
|
|
53
|
+
}
|
|
46
54
|
function shouldDeferAutomaticWorkspaceSyncForPendingBranch(pendingCreatedBranches, activeTargets) {
|
|
47
55
|
if (pendingCreatedBranches.length === 0) return false;
|
|
48
56
|
const targets = [...activeTargets];
|
|
@@ -65,5 +73,7 @@ function pendingCreatedBranchAutomaticSyncDeferral(pendingCreatedBranches, activ
|
|
|
65
73
|
pendingCreatedBranchAutomaticSyncDeferral,
|
|
66
74
|
pendingCreatedBranchKey,
|
|
67
75
|
projectBranchHasActiveWorkspaceTarget,
|
|
68
|
-
|
|
76
|
+
projectBranchMountBusyDuringExclusiveSync,
|
|
77
|
+
shouldDeferAutomaticWorkspaceSyncForPendingBranch,
|
|
78
|
+
workspacePlansMountBusyDuringExclusiveSync
|
|
69
79
|
});
|
|
@@ -20,6 +20,7 @@ var workspace_command_sync_policy_exports = {};
|
|
|
20
20
|
__export(workspace_command_sync_policy_exports, {
|
|
21
21
|
acquireWorkspaceCommandMutation: () => acquireWorkspaceCommandMutation,
|
|
22
22
|
reserveWorkspaceCommandAfterCurrentSync: () => reserveWorkspaceCommandAfterCurrentSync,
|
|
23
|
+
runReservedWorkspaceCommand: () => runReservedWorkspaceCommand,
|
|
23
24
|
runWorkspaceCommand: () => runWorkspaceCommand,
|
|
24
25
|
shouldSerializeWorkspaceCommand: () => shouldSerializeWorkspaceCommand
|
|
25
26
|
});
|
|
@@ -46,6 +47,25 @@ function shouldSerializeWorkspaceCommand(target) {
|
|
|
46
47
|
function runWorkspaceCommand(target, coordinator, operation) {
|
|
47
48
|
return shouldSerializeWorkspaceCommand(target) ? coordinator.runMutation(operation) : Promise.resolve().then(operation);
|
|
48
49
|
}
|
|
50
|
+
async function runReservedWorkspaceCommand(target, coordinator, operation, releaseReservation) {
|
|
51
|
+
let released = false;
|
|
52
|
+
const releaseOnce = () => {
|
|
53
|
+
if (released) return;
|
|
54
|
+
released = true;
|
|
55
|
+
releaseReservation();
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
return await runWorkspaceCommand(target, coordinator, async () => {
|
|
59
|
+
try {
|
|
60
|
+
return await operation();
|
|
61
|
+
} finally {
|
|
62
|
+
releaseOnce();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
} finally {
|
|
66
|
+
releaseOnce();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
49
69
|
function acquireWorkspaceCommandMutation(target, coordinator) {
|
|
50
70
|
return shouldSerializeWorkspaceCommand(target) ? coordinator.acquireMutation() : Promise.resolve(void 0);
|
|
51
71
|
}
|
|
@@ -53,6 +73,7 @@ function acquireWorkspaceCommandMutation(target, coordinator) {
|
|
|
53
73
|
0 && (module.exports = {
|
|
54
74
|
acquireWorkspaceCommandMutation,
|
|
55
75
|
reserveWorkspaceCommandAfterCurrentSync,
|
|
76
|
+
runReservedWorkspaceCommand,
|
|
56
77
|
runWorkspaceCommand,
|
|
57
78
|
shouldSerializeWorkspaceCommand
|
|
58
79
|
});
|