@teambit/ci 1.0.495 → 1.0.500
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/ci.docs.mdx +39 -9
- package/dist/ci.docs.mdx +39 -9
- package/dist/ci.main.runtime.d.ts +6 -0
- package/dist/ci.main.runtime.js +24 -3
- package/dist/ci.main.runtime.js.map +1 -1
- package/dist/{preview-1786461108763.js → preview-1786731350011.js} +1 -1
- package/dist/sync/lane-sync-executor.d.ts +56 -14
- package/dist/sync/lane-sync-executor.js +196 -37
- package/dist/sync/lane-sync-executor.js.map +1 -1
- package/dist/sync/lane-sync-executor.spec.js +127 -3
- package/dist/sync/lane-sync-executor.spec.js.map +1 -1
- package/dist/sync/sync-planner.d.ts +10 -1
- package/dist/sync/sync-planner.js +37 -13
- package/dist/sync/sync-planner.js.map +1 -1
- package/dist/sync/sync-planner.spec.js +44 -2
- package/dist/sync/sync-planner.spec.js.map +1 -1
- package/dist/sync/sync-state.d.ts +33 -1
- package/dist/sync/sync-state.js +60 -3
- package/dist/sync/sync-state.js.map +1 -1
- package/dist/sync/sync-state.spec.js +28 -0
- package/dist/sync/sync-state.spec.js.map +1 -1
- package/package.json +21 -21
- package/sync/lane-sync-executor.spec.ts +110 -3
- package/sync/lane-sync-executor.ts +234 -32
- package/sync/sync-planner.spec.ts +36 -1
- package/sync/sync-planner.ts +36 -16
- package/sync/sync-state.spec.ts +33 -0
- package/sync/sync-state.ts +62 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
;
|
|
2
|
-
import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.git_ci@1.0.
|
|
2
|
+
import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.git_ci@1.0.500/dist/ci.docs.mdx';
|
|
3
3
|
|
|
4
4
|
export const compositions = [];
|
|
5
5
|
export const overview = [overview_0];
|
|
@@ -26,6 +26,12 @@ export declare const REFUSED_SUMMARY_PREFIX = "REFUSED";
|
|
|
26
26
|
* summary just because the ledger push raced.
|
|
27
27
|
*/
|
|
28
28
|
export declare function racedLedgerPushSummary(laneName: string, policyClause?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* `executeAdoptBranch`'s ledger-commit push lost the race. Unlike the export paths, adoption never
|
|
31
|
+
* snaps or exports — the LANE is untouched; only the branch's pointer commit failed to land. The next
|
|
32
|
+
* run re-plans first contact against the winner's tip.
|
|
33
|
+
*/
|
|
34
|
+
export declare function racedAdoptionPushSummary(laneName: string): string;
|
|
29
35
|
export type LaneSyncDeps = {
|
|
30
36
|
lanes: LanesMain;
|
|
31
37
|
/** for snapPrCommit + getDefaultBranchName + switchToLaneForSync */
|
|
@@ -131,6 +137,16 @@ export declare class LaneSyncExecutor {
|
|
|
131
137
|
* resulting `.bitmap` back onto the branch so the next run sees the two sides as converged.
|
|
132
138
|
*/
|
|
133
139
|
private executeExportBranch;
|
|
140
|
+
/**
|
|
141
|
+
* First contact: the lane exists, the branch maps to it by name, but the branch's committed
|
|
142
|
+
* `.bitmap` has no state for it (typically `bit ci pr --keep-lane` created the lane from this very
|
|
143
|
+
* branch without recording the pointer in git). Adoption only ever RECORDS a pointer, never resolves
|
|
144
|
+
* content: switch onto the lane keeping the branch's files, then ask `bit status` whether anything
|
|
145
|
+
* differs. Nothing → ledger commit; something → halt, lane untouched. Deliberately a status READ,
|
|
146
|
+
* never a snap: `snapping.snap()` writes an unexported Version object even under `dryRun`, and that
|
|
147
|
+
* residue can trip a later lane in the same `--all` run.
|
|
148
|
+
*/
|
|
149
|
+
private executeAdoptBranch;
|
|
134
150
|
/**
|
|
135
151
|
* Both sides moved since the last sync. Order is load-bearing: (1) merge the lane into the branch's
|
|
136
152
|
* working tree (`bit checkout head`; conflicts halt or auto-resolve per `sync.onConflict`), (2) snap
|
|
@@ -141,13 +157,12 @@ export declare class LaneSyncExecutor {
|
|
|
141
157
|
*/
|
|
142
158
|
private executeMergeDiverged;
|
|
143
159
|
/**
|
|
144
|
-
* Snap the workspace's current tree onto the lane and export it
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* fetch, so it never warms a cold scope.
|
|
160
|
+
* Snap the workspace's current tree onto the lane and export it. Snaps WHATEVER is in the workspace
|
|
161
|
+
* (the switch uses `forceOurs` and never merges files), so a diverged tree must already hold the
|
|
162
|
+
* merged content. `keepLane` preserves the lane's history, `skipCleanup` keeps the snap's `.bitmap`
|
|
163
|
+
* for the caller to commit, `noDestructiveRecovery` turns stale-lane recovery (delete + re-fork the
|
|
164
|
+
* remote lane) into a throw. The lane object must be imported BEFORE delegating: a switch onto the
|
|
165
|
+
* lane the workspace is already on no-ops before any fetch, so it never warms a cold scope.
|
|
151
166
|
*/
|
|
152
167
|
private snapAndExportOntoLane;
|
|
153
168
|
/**
|
|
@@ -155,7 +170,7 @@ export declare class LaneSyncExecutor {
|
|
|
155
170
|
* so any earlier fingerprint is stale), commit — crucially the `.bitmap` the snap rewrote — and push.
|
|
156
171
|
* `unreadable` means the lane could no longer be read back, in which case the caller halts;
|
|
157
172
|
* `raced` means a concurrent run's push already got there first, in which case the caller reports
|
|
158
|
-
* a benign noop instead.
|
|
173
|
+
* a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`.
|
|
159
174
|
*/
|
|
160
175
|
private recordLaneHeadOnBranch;
|
|
161
176
|
/**
|
|
@@ -181,6 +196,30 @@ export declare class LaneSyncExecutor {
|
|
|
181
196
|
* direction: left alone, never retired.
|
|
182
197
|
*/
|
|
183
198
|
private assessBranchOwnership;
|
|
199
|
+
/**
|
|
200
|
+
* Gates `hasIndependentHistoryBelowStateCommit`'s git call to the one case it can change the outcome
|
|
201
|
+
* for: an `own-live` claim (the only ownership that would otherwise license an immediate delete) with
|
|
202
|
+
* a reachable state commit to inspect. Extracted out of `reconcileLane` to keep the branching there.
|
|
203
|
+
*/
|
|
204
|
+
private computeHasIndependentHistory;
|
|
205
|
+
/**
|
|
206
|
+
* Whether the OLDEST commit unique to the branch is NOT bit-authored — durable, ancestry-based proof
|
|
207
|
+
* a human created the branch before this reconciler touched it (a trailer would not survive later
|
|
208
|
+
* ledger commits; see `oldestCommitIsNonSync` for the oldest-only rationale). Unreadable answers
|
|
209
|
+
* `true`: a deletion guard errs toward keeping.
|
|
210
|
+
*/
|
|
211
|
+
private hasIndependentHistoryBelowStateCommit;
|
|
212
|
+
/** Gates `differentLaneStillClaims` to the planner's first-contact path, the only one that reads it. */
|
|
213
|
+
private computeBranchNamesDifferentLane;
|
|
214
|
+
/**
|
|
215
|
+
* Whether adoption must refuse `branch` because its `.bitmap` names a DIFFERENT lane's still-live
|
|
216
|
+
* claim. Blob equality with the fork-point `.bitmap`, not commit reachability: an `own-superseded`
|
|
217
|
+
* branch is reachable yet still carries live work; only a `.bitmap` the branch never changed proves
|
|
218
|
+
* the pointer is inherited. Unreadable stays conservative and blocks adoption.
|
|
219
|
+
*/
|
|
220
|
+
private differentLaneStillClaims;
|
|
221
|
+
/** Seam over `branchBitmapUnchangedSinceFork`, so `differentLaneStillClaims` is unit-testable without a real remote. */
|
|
222
|
+
private branchInheritsBitmapFromDefault;
|
|
184
223
|
/**
|
|
185
224
|
* The lane is gone from bit.cloud — close its PR, and retire the branch if the claim on it allows.
|
|
186
225
|
* Deleting the branch is the one irreversible thing this command does; `keepReason` distinguishes
|
|
@@ -202,12 +241,15 @@ export declare class LaneSyncExecutor {
|
|
|
202
241
|
*/
|
|
203
242
|
private executeHalt;
|
|
204
243
|
/**
|
|
205
|
-
* Drive the workspace's filesystem
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
244
|
+
* Drive the workspace's filesystem onto the remote lane; returns the error so the caller can halt.
|
|
245
|
+
* Two traps make a plain `switchToLane` insufficient: (1) its default `forceOurs` never touches the
|
|
246
|
+
* filesystem, so the caller's commit would misrepresent whichever side it claims mirrors the other;
|
|
247
|
+
* (2) switching onto the lane the workspace already sits on throws "already checked out", which
|
|
248
|
+
* `switchToLane` swallows as success and nothing materializes — so step off to main first.
|
|
249
|
+
*
|
|
250
|
+
* `theirsWins` picks the direction: `true` (import-lane) checks the lane's files out over the disk;
|
|
251
|
+
* `false` (adopt-branch) keeps the workspace's files — the branch's content is what the lane is
|
|
252
|
+
* being asked to reflect.
|
|
211
253
|
*/
|
|
212
254
|
private materializeLane;
|
|
213
255
|
private fetchOnce;
|
|
@@ -17,6 +17,7 @@ exports.haltCommentBody = haltCommentBody;
|
|
|
17
17
|
exports.isProtectedBranch = isProtectedBranch;
|
|
18
18
|
exports.laneHeadFingerprint = laneHeadFingerprint;
|
|
19
19
|
exports.laneSyncPrBody = laneSyncPrBody;
|
|
20
|
+
exports.racedAdoptionPushSummary = racedAdoptionPushSummary;
|
|
20
21
|
exports.racedLedgerPushSummary = racedLedgerPushSummary;
|
|
21
22
|
function _chalk() {
|
|
22
23
|
const data = _interopRequireDefault(require("chalk"));
|
|
@@ -137,6 +138,15 @@ function racedBranchPushSummary(laneName) {
|
|
|
137
138
|
function racedLedgerPushSummary(laneName, policyClause = '') {
|
|
138
139
|
return racedSummary(laneName, `${policyClause}lane updated; branch ledger commit lost the push race — next run re-plans`);
|
|
139
140
|
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* `executeAdoptBranch`'s ledger-commit push lost the race. Unlike the export paths, adoption never
|
|
144
|
+
* snaps or exports — the LANE is untouched; only the branch's pointer commit failed to land. The next
|
|
145
|
+
* run re-plans first contact against the winner's tip.
|
|
146
|
+
*/
|
|
147
|
+
function racedAdoptionPushSummary(laneName) {
|
|
148
|
+
return racedSummary(laneName, 'lane untouched; adoption ledger commit lost the push race — next run re-plans');
|
|
149
|
+
}
|
|
140
150
|
/**
|
|
141
151
|
* Fingerprint of a lane's content — same primitive as `branchStateFingerprint`, so "equal" means
|
|
142
152
|
* exactly "the branch records every lane component at the lane's head".
|
|
@@ -261,7 +271,8 @@ function dryRunSummaryLine(laneName, action) {
|
|
|
261
271
|
const KEPT_BECAUSE = exports.KEPT_BECAUSE = {
|
|
262
272
|
'unmerged-commits': `it carries commits that are not in the default branch`,
|
|
263
273
|
'tip-not-a-sync-commit': `its tip is not one of this reconciler's own commits, so the work in it may exist nowhere else`,
|
|
264
|
-
'tip-advanced-during-run': `its tip advanced after the ownership evidence was read, so it may carry work this run never saw
|
|
274
|
+
'tip-advanced-during-run': `its tip advanced after the ownership evidence was read, so it may carry work this run never saw`,
|
|
275
|
+
'adopted-branch': `it was adopted from a pre-existing branch, and only merging (not deleting) proves its history is safe to retire`
|
|
265
276
|
};
|
|
266
277
|
class LaneSyncExecutor {
|
|
267
278
|
constructor(deps) {
|
|
@@ -438,13 +449,17 @@ class LaneSyncExecutor {
|
|
|
438
449
|
// `isSyncAuthoredMessage`, NOT `hasSyncMarker`: this feeds a branch deletion, and a message that
|
|
439
450
|
// merely quotes the marker must not read as one we wrote.
|
|
440
451
|
const tipIsSyncCommit = (0, _syncState().isSyncAuthoredMessage)(branchState.tipMessage);
|
|
452
|
+
const hasIndependentHistory = await this.computeHasIndependentHistory(ownership, branchState.stateCommit, defaultBranch);
|
|
453
|
+
const branchNamesDifferentLane = await this.computeBranchNamesDifferentLane(lastSyncedHead, branchState.hasDevCommits, mirroredLane, laneIdStr, branch, defaultBranch);
|
|
441
454
|
const action = (0, _syncPlanner().planLaneSync)({
|
|
442
455
|
laneHead,
|
|
443
456
|
branchExists,
|
|
444
457
|
lastSyncedHead,
|
|
445
458
|
hasDevCommits: branchState.hasDevCommits,
|
|
446
459
|
tipIsSyncCommit,
|
|
460
|
+
hasIndependentHistory,
|
|
447
461
|
conflictLabelPresent,
|
|
462
|
+
branchNamesDifferentLane,
|
|
448
463
|
ownership
|
|
449
464
|
});
|
|
450
465
|
logger.console(_chalk().default.blue(`${laneName} -> ${action.type} (branch: ${branch}, lane head: ${laneHead?.slice(0, 9) ?? 'none'}, ` + `branch state: ${lastSyncedHead?.slice(0, 9) ?? 'none'}, ` + `dev commits: ${branchState.hasDevCommits}${laneIsGone ? `, branch claim: ${ownership}` : ''})`));
|
|
@@ -501,6 +516,14 @@ class LaneSyncExecutor {
|
|
|
501
516
|
branch,
|
|
502
517
|
defaultBranch
|
|
503
518
|
});
|
|
519
|
+
case 'adopt-branch':
|
|
520
|
+
return this.executeAdoptBranch({
|
|
521
|
+
target,
|
|
522
|
+
laneIdStr,
|
|
523
|
+
branch,
|
|
524
|
+
defaultBranch,
|
|
525
|
+
pr
|
|
526
|
+
});
|
|
504
527
|
case 'close-pr':
|
|
505
528
|
return this.executeClosePr({
|
|
506
529
|
laneName,
|
|
@@ -513,13 +536,18 @@ class LaneSyncExecutor {
|
|
|
513
536
|
expectedTipSha: branchState.tipSha
|
|
514
537
|
});
|
|
515
538
|
case 'halt':
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
laneIdStr
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
539
|
+
{
|
|
540
|
+
// The planner only has the boolean; the different-lane id and its PR advice live here.
|
|
541
|
+
const commentNote = branchNamesDifferentLane && mirroredLane ? branchMirrorsOtherLaneNote(mirroredLane, laneIdStr) : undefined;
|
|
542
|
+
return this.executeHalt({
|
|
543
|
+
laneName,
|
|
544
|
+
laneIdStr,
|
|
545
|
+
branch,
|
|
546
|
+
reason: action.reason,
|
|
547
|
+
pr,
|
|
548
|
+
commentNote
|
|
549
|
+
});
|
|
550
|
+
}
|
|
523
551
|
default:
|
|
524
552
|
{
|
|
525
553
|
// exhaustiveness guard
|
|
@@ -660,14 +688,14 @@ class LaneSyncExecutor {
|
|
|
660
688
|
const message = await this.lastNonSyncCommitMessage(branch, defaultBranch);
|
|
661
689
|
await this.checkoutFromRemote(branch, `origin/${branch}`);
|
|
662
690
|
try {
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
691
|
+
const exported = await this.snapAndExportOntoLane(laneIdStr, message);
|
|
692
|
+
if (exported.status === 'error') {
|
|
665
693
|
// Halt rather than propagate: one lane's failed snap/export must not abort the lanes after it.
|
|
666
694
|
return await this.executeHalt({
|
|
667
695
|
laneName,
|
|
668
696
|
laneIdStr,
|
|
669
697
|
branch,
|
|
670
|
-
reason: `failed to snap and export branch ${branch} onto lane ${laneIdStr}: ${
|
|
698
|
+
reason: `failed to snap and export branch ${branch} onto lane ${laneIdStr}: ${exported.error.message}`,
|
|
671
699
|
pr: await this.findPr(branch)
|
|
672
700
|
});
|
|
673
701
|
}
|
|
@@ -688,6 +716,71 @@ class LaneSyncExecutor {
|
|
|
688
716
|
}
|
|
689
717
|
}
|
|
690
718
|
|
|
719
|
+
/**
|
|
720
|
+
* First contact: the lane exists, the branch maps to it by name, but the branch's committed
|
|
721
|
+
* `.bitmap` has no state for it (typically `bit ci pr --keep-lane` created the lane from this very
|
|
722
|
+
* branch without recording the pointer in git). Adoption only ever RECORDS a pointer, never resolves
|
|
723
|
+
* content: switch onto the lane keeping the branch's files, then ask `bit status` whether anything
|
|
724
|
+
* differs. Nothing → ledger commit; something → halt, lane untouched. Deliberately a status READ,
|
|
725
|
+
* never a snap: `snapping.snap()` writes an unexported Version object even under `dryRun`, and that
|
|
726
|
+
* residue can trip a later lane in the same `--all` run.
|
|
727
|
+
*/
|
|
728
|
+
async executeAdoptBranch({
|
|
729
|
+
target,
|
|
730
|
+
laneIdStr,
|
|
731
|
+
branch,
|
|
732
|
+
defaultBranch,
|
|
733
|
+
pr
|
|
734
|
+
}) {
|
|
735
|
+
const {
|
|
736
|
+
logger
|
|
737
|
+
} = this.deps;
|
|
738
|
+
const laneName = target.name;
|
|
739
|
+
logger.console((0, _cli().formatWarningSummary)(`Adopting branch ${branch}: lane ${laneIdStr} exists, but the branch's committed .bitmap has no state for it (first contact)`));
|
|
740
|
+
await this.checkoutFromRemote(branch, `origin/${branch}`);
|
|
741
|
+
try {
|
|
742
|
+
const switchErr = await this.materializeLane(laneIdStr, false);
|
|
743
|
+
if (switchErr) {
|
|
744
|
+
return await this.executeHalt({
|
|
745
|
+
laneName,
|
|
746
|
+
laneIdStr,
|
|
747
|
+
branch,
|
|
748
|
+
reason: `failed to check branch ${branch} against lane ${laneIdStr} for adoption: ${switchErr.message}`,
|
|
749
|
+
pr: pr ?? (await this.findPr(branch))
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
const wouldChangeLane = await this.deps.ci.hasUnsyncedWorkChanges();
|
|
753
|
+
if (wouldChangeLane) {
|
|
754
|
+
// Nothing was snapped or exported — the lane is untouched.
|
|
755
|
+
return await this.executeHalt({
|
|
756
|
+
laneName,
|
|
757
|
+
laneIdStr,
|
|
758
|
+
branch,
|
|
759
|
+
reason: 'branch has commits but its .bitmap records no state for this lane; cannot tell which side is ' + `newer (adopting would change lane ${laneIdStr}'s content, not just record a pointer)`,
|
|
760
|
+
pr: pr ?? (await this.findPr(branch))
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Branch content already matches the lane — the ledger commit is all that is missing.
|
|
765
|
+
const recorded = await this.recordLaneHeadOnBranch(target, laneIdStr, branch, {
|
|
766
|
+
adopted: true
|
|
767
|
+
});
|
|
768
|
+
if (recorded.status === 'raced') return racedAdoptionPushSummary(laneName);
|
|
769
|
+
if (recorded.status === 'unreadable') {
|
|
770
|
+
return await this.executeHalt({
|
|
771
|
+
laneName,
|
|
772
|
+
laneIdStr,
|
|
773
|
+
branch,
|
|
774
|
+
reason: `lane ${laneIdStr} could not be read back from the remote after adopting branch ${branch}`,
|
|
775
|
+
pr: pr ?? (await this.findPr(branch))
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return `${laneName} -> adopt-branch (branch ${branch} had no lane state; adopted onto lane ${laneIdStr} @ ` + `${recorded.laneHead.slice(0, 9)})`;
|
|
779
|
+
} finally {
|
|
780
|
+
await this.restoreWorkspace(defaultBranch);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
691
784
|
/**
|
|
692
785
|
* Both sides moved since the last sync. Order is load-bearing: (1) merge the lane into the branch's
|
|
693
786
|
* working tree (`bit checkout head`; conflicts halt or auto-resolve per `sync.onConflict`), (2) snap
|
|
@@ -756,9 +849,9 @@ class LaneSyncExecutor {
|
|
|
756
849
|
}
|
|
757
850
|
|
|
758
851
|
// ---- step 2: snap + export the merged tree onto the lane ---------------------------------
|
|
759
|
-
const
|
|
760
|
-
if (
|
|
761
|
-
return await halt(`merged lane ${laneIdStr} into branch ${branch} cleanly, but snapping/exporting the merged ` + `result failed: ${
|
|
852
|
+
const exported = await this.snapAndExportOntoLane(laneIdStr, `merge remote lane ${laneIdStr} into ${branch} ${_syncState().SYNC_COMMIT_MARKER}`);
|
|
853
|
+
if (exported.status === 'error') {
|
|
854
|
+
return await halt(`merged lane ${laneIdStr} into branch ${branch} cleanly, but snapping/exporting the merged ` + `result failed: ${exported.error.message}`);
|
|
762
855
|
}
|
|
763
856
|
|
|
764
857
|
// ---- step 3: record the new lane head on the branch --------------------------------------
|
|
@@ -777,18 +870,17 @@ class LaneSyncExecutor {
|
|
|
777
870
|
}
|
|
778
871
|
|
|
779
872
|
/**
|
|
780
|
-
* Snap the workspace's current tree onto the lane and export it
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
* fetch, so it never warms a cold scope.
|
|
873
|
+
* Snap the workspace's current tree onto the lane and export it. Snaps WHATEVER is in the workspace
|
|
874
|
+
* (the switch uses `forceOurs` and never merges files), so a diverged tree must already hold the
|
|
875
|
+
* merged content. `keepLane` preserves the lane's history, `skipCleanup` keeps the snap's `.bitmap`
|
|
876
|
+
* for the caller to commit, `noDestructiveRecovery` turns stale-lane recovery (delete + re-fork the
|
|
877
|
+
* remote lane) into a throw. The lane object must be imported BEFORE delegating: a switch onto the
|
|
878
|
+
* lane the workspace is already on no-ops before any fetch, so it never warms a cold scope.
|
|
787
879
|
*/
|
|
788
880
|
async snapAndExportOntoLane(laneIdStr, message) {
|
|
789
881
|
try {
|
|
790
882
|
await (0, _workspaceLane().ensureCurrentLaneObject)(this.deps.lanes);
|
|
791
|
-
await this.deps.ci.snapPrCommit({
|
|
883
|
+
const result = await this.deps.ci.snapPrCommit({
|
|
792
884
|
laneIdStr,
|
|
793
885
|
message,
|
|
794
886
|
build: undefined,
|
|
@@ -797,9 +889,17 @@ class LaneSyncExecutor {
|
|
|
797
889
|
skipCleanup: true,
|
|
798
890
|
noDestructiveRecovery: true
|
|
799
891
|
});
|
|
800
|
-
return
|
|
892
|
+
if (result === _syncState().NO_CHANGES_TO_SNAP) return {
|
|
893
|
+
status: 'noop'
|
|
894
|
+
};
|
|
895
|
+
return {
|
|
896
|
+
status: 'exported'
|
|
897
|
+
};
|
|
801
898
|
} catch (e) {
|
|
802
|
-
return
|
|
899
|
+
return {
|
|
900
|
+
status: 'error',
|
|
901
|
+
error: e instanceof Error ? e : new Error(String(e?.message ?? e))
|
|
902
|
+
};
|
|
803
903
|
}
|
|
804
904
|
}
|
|
805
905
|
|
|
@@ -808,15 +908,15 @@ class LaneSyncExecutor {
|
|
|
808
908
|
* so any earlier fingerprint is stale), commit — crucially the `.bitmap` the snap rewrote — and push.
|
|
809
909
|
* `unreadable` means the lane could no longer be read back, in which case the caller halts;
|
|
810
910
|
* `raced` means a concurrent run's push already got there first, in which case the caller reports
|
|
811
|
-
* a benign noop instead.
|
|
911
|
+
* a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`.
|
|
812
912
|
*/
|
|
813
|
-
async recordLaneHeadOnBranch(target, laneIdStr, branch) {
|
|
913
|
+
async recordLaneHeadOnBranch(target, laneIdStr, branch, opts = {}) {
|
|
814
914
|
const remoteLane = await this.getRemoteLane(target);
|
|
815
915
|
if (!remoteLane) return {
|
|
816
916
|
status: 'unreadable'
|
|
817
917
|
};
|
|
818
918
|
const laneHead = laneHeadFingerprint(remoteLane.components);
|
|
819
|
-
const pushResult = await this.commitAllAndPush(branch, (0, _syncState().buildSyncCommitMessage)(laneIdStr, laneHead));
|
|
919
|
+
const pushResult = await this.commitAllAndPush(branch, (0, _syncState().buildSyncCommitMessage)(laneIdStr, laneHead, opts));
|
|
820
920
|
if (pushResult.raced) return {
|
|
821
921
|
status: 'raced'
|
|
822
922
|
};
|
|
@@ -925,6 +1025,55 @@ class LaneSyncExecutor {
|
|
|
925
1025
|
}
|
|
926
1026
|
}
|
|
927
1027
|
|
|
1028
|
+
/**
|
|
1029
|
+
* Gates `hasIndependentHistoryBelowStateCommit`'s git call to the one case it can change the outcome
|
|
1030
|
+
* for: an `own-live` claim (the only ownership that would otherwise license an immediate delete) with
|
|
1031
|
+
* a reachable state commit to inspect. Extracted out of `reconcileLane` to keep the branching there.
|
|
1032
|
+
*/
|
|
1033
|
+
async computeHasIndependentHistory(ownership, stateCommit, defaultBranch) {
|
|
1034
|
+
if (ownership !== 'own-live' || !stateCommit) return false;
|
|
1035
|
+
return this.hasIndependentHistoryBelowStateCommit(stateCommit, defaultBranch);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Whether the OLDEST commit unique to the branch is NOT bit-authored — durable, ancestry-based proof
|
|
1040
|
+
* a human created the branch before this reconciler touched it (a trailer would not survive later
|
|
1041
|
+
* ledger commits; see `oldestCommitIsNonSync` for the oldest-only rationale). Unreadable answers
|
|
1042
|
+
* `true`: a deletion guard errs toward keeping.
|
|
1043
|
+
*/
|
|
1044
|
+
async hasIndependentHistoryBelowStateCommit(stateCommit, defaultBranch) {
|
|
1045
|
+
try {
|
|
1046
|
+
const log = await _git().git.raw(['log', `origin/${defaultBranch}..${stateCommit}`, '--first-parent', '--reverse', '--format=%B%x1e']);
|
|
1047
|
+
return (0, _syncState().oldestCommitIsNonSync)(log);
|
|
1048
|
+
} catch (e) {
|
|
1049
|
+
this.deps.logger.consoleWarning(`Could not read ${stateCommit}'s history against ${defaultBranch} to check for independent, ` + `pre-existing branch history; treating it as present (the branch is kept, not deleted): ${e?.message || e}`);
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** Gates `differentLaneStillClaims` to the planner's first-contact path, the only one that reads it. */
|
|
1055
|
+
async computeBranchNamesDifferentLane(lastSyncedHead, hasDevCommits, mirroredLane, laneIdStr, branch, defaultBranch) {
|
|
1056
|
+
if (lastSyncedHead || !hasDevCommits) return false;
|
|
1057
|
+
return this.differentLaneStillClaims(mirroredLane, laneIdStr, branch, defaultBranch);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Whether adoption must refuse `branch` because its `.bitmap` names a DIFFERENT lane's still-live
|
|
1062
|
+
* claim. Blob equality with the fork-point `.bitmap`, not commit reachability: an `own-superseded`
|
|
1063
|
+
* branch is reachable yet still carries live work; only a `.bitmap` the branch never changed proves
|
|
1064
|
+
* the pointer is inherited. Unreadable stays conservative and blocks adoption.
|
|
1065
|
+
*/
|
|
1066
|
+
async differentLaneStillClaims(mirroredLane, laneIdStr, branch, defaultBranch) {
|
|
1067
|
+
if (!mirroredLane || mirroredLane === laneIdStr) return false;
|
|
1068
|
+
const inherited = await this.branchInheritsBitmapFromDefault(branch, defaultBranch);
|
|
1069
|
+
return !inherited;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/** Seam over `branchBitmapUnchangedSinceFork`, so `differentLaneStillClaims` is unit-testable without a real remote. */
|
|
1073
|
+
branchInheritsBitmapFromDefault(branch, defaultBranch) {
|
|
1074
|
+
return (0, _syncState().branchBitmapUnchangedSinceFork)(branch, defaultBranch);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
928
1077
|
/**
|
|
929
1078
|
* The lane is gone from bit.cloud — close its PR, and retire the branch if the claim on it allows.
|
|
930
1079
|
* Deleting the branch is the one irreversible thing this command does; `keepReason` distinguishes
|
|
@@ -1084,14 +1233,17 @@ class LaneSyncExecutor {
|
|
|
1084
1233
|
}
|
|
1085
1234
|
|
|
1086
1235
|
/**
|
|
1087
|
-
* Drive the workspace's filesystem
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1236
|
+
* Drive the workspace's filesystem onto the remote lane; returns the error so the caller can halt.
|
|
1237
|
+
* Two traps make a plain `switchToLane` insufficient: (1) its default `forceOurs` never touches the
|
|
1238
|
+
* filesystem, so the caller's commit would misrepresent whichever side it claims mirrors the other;
|
|
1239
|
+
* (2) switching onto the lane the workspace already sits on throws "already checked out", which
|
|
1240
|
+
* `switchToLane` swallows as success and nothing materializes — so step off to main first.
|
|
1241
|
+
*
|
|
1242
|
+
* `theirsWins` picks the direction: `true` (import-lane) checks the lane's files out over the disk;
|
|
1243
|
+
* `false` (adopt-branch) keeps the workspace's files — the branch's content is what the lane is
|
|
1244
|
+
* being asked to reflect.
|
|
1093
1245
|
*/
|
|
1094
|
-
async materializeLane(laneIdStr) {
|
|
1246
|
+
async materializeLane(laneIdStr, theirsWins = true) {
|
|
1095
1247
|
const {
|
|
1096
1248
|
logger
|
|
1097
1249
|
} = this.deps;
|
|
@@ -1108,9 +1260,16 @@ class LaneSyncExecutor {
|
|
|
1108
1260
|
}
|
|
1109
1261
|
}
|
|
1110
1262
|
const switchErr = await this.deps.ci.switchToLaneForSync(laneIdStr, {
|
|
1111
|
-
forceOurs:
|
|
1112
|
-
forceTheirs:
|
|
1113
|
-
|
|
1263
|
+
forceOurs: !theirsWins,
|
|
1264
|
+
forceTheirs: theirsWins,
|
|
1265
|
+
// The whole lane has to land in the workspace here, including components it has that this
|
|
1266
|
+
// workspace doesn't track yet - materializing the lane is the point of this step. That is the
|
|
1267
|
+
// opposite of `bit ci pr`, where the git checkout is the source of truth and `switchToLane`'s
|
|
1268
|
+
// `workspaceOnly` default keeps the switch from writing lane-only components.
|
|
1269
|
+
workspaceOnly: false,
|
|
1270
|
+
// Follows the direction: in adopt mode the missing-component retry must not write the lane's
|
|
1271
|
+
// files over the branch's — the ledger commit would then push the lane's content onto the branch.
|
|
1272
|
+
writeAdoptedFiles: theirsWins
|
|
1114
1273
|
});
|
|
1115
1274
|
if (switchErr) return switchErr;
|
|
1116
1275
|
|