Skip to content

feat(lanes): allow --squash on diverged components#10387

Open
davidfirst wants to merge 1 commit into
masterfrom
feat/squash-on-diverged-lanes
Open

feat(lanes): allow --squash on diverged components#10387
davidfirst wants to merge 1 commit into
masterfrom
feat/squash-on-diverged-lanes

Conversation

@davidfirst
Copy link
Copy Markdown
Member

Why

bit lane merge <other-lane> --squash previously threw when components were diverged in history, forcing users to either do a two-way merge dance or fall back to --no-squash. With --no-squash on large lanes hosted in different scopes, exporting after the merge pulls the other scope's full snap history into the merging scope, which can blow up memory on both client and remote.

What

Diverged components under --squash now produce a single-parent merge snap. The dropped foreign head is recorded in Version.squashed (previousParents + laneId), so:

  • Lane-b's intermediate snap objects stay on scope-b — scope-a only receives the merge snap.
  • bit log from a fresh consumer of scope-a is clean (no ParentNotFound walking into scope-b).
  • Re-merging lane-b after it advances works correctly: the existing VersionHistory graph treats squashed.previousParents as edges (version-history.ts:199, 263), so diverge calculation finds the last-merged head as the base instead of re-merging history that was already squashed.

No remote-side changes were needed.

Implementation

  • UnmergedComponent gains shouldSquash?: boolean.
  • mergeSnaps/applyVersionMultiple/applyVersion thread shouldSquash through and set the flag on the unmerged entry when divergent.
  • Squash guard in merge-lanes.main.runtime.ts no longer throws on diverged — it returns false so the snap-time path handles it.
  • At snap creation (snapping.main.runtime.ts), the unmerged-component branch checks shouldSquash: if true, calls setSquashed with both pre-squash parents and skips addParent of the foreign head.

Test plan

E2e tests in e2e/harmony/lanes/merge-lanes-squash-diverge.e2e.ts (21 cases, all passing):

  • single-scope diverged squash → single-parent snap, squash metadata, clean bit log
  • multi-scope diverged squash → scope-a does NOT contain lane-b snaps; scope-b still does
  • fresh consumer imports lane-a from scope-a → bit log and bit status don't throw
  • re-merge of lane-b after it advances → second merge snap records new dropped head, not the previously-squashed one; re-export succeeds
  • sanity: diverged without --squash still produces two-parent merge snap
  • sanity: squash on fast-forward unchanged

Previously `bit lane merge <other-lane> --squash` threw when components
were diverged in history. Now it produces a single-parent merge snap with
the dropped other-lane head captured in Version.squashed metadata. This
keeps the merged lane's history self-contained on its own scope, so
exporting after merging from a foreign scope does not pull that scope's
intermediate snap objects.

The existing VersionHistory graph already treats squashed.previousParents
as edges, so subsequent re-merges from the same source lane correctly
identify the previously-merged head as the diverge base.
Copilot AI review requested due to automatic review settings May 20, 2026 20:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables bit lane merge <other-lane> --squash to work even when component histories are diverged, by creating a single-parent merge snap and recording the dropped foreign head in Version.squashed metadata (preventing cross-scope history pull-in on export).

Changes:

  • Thread a shouldSquash flag through lane-merge → merge engine → snap creation, and persist it on UnmergedComponent.
  • Adjust squash handling so diverged components no longer throw in the pre-squash path; instead they’re handled at snap-creation time via squashed metadata.
  • Add comprehensive E2E coverage for single-scope and multi-scope diverged squash, re-merge behavior, and non-squash sanity checks.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scopes/lanes/merge-lanes/merge-lanes.main.runtime.ts Stops throwing on diverged --squash and passes shouldSquash through to the merge engine.
scopes/component/merging/merging.main.runtime.ts Propagates shouldSquash into UnmergedComponent entries for diverged merges.
scopes/component/snapping/snapping.main.runtime.ts On snap creation, uses shouldSquash to record Version.squashed metadata and avoid adding the foreign head as a second parent.
components/legacy/scope/lanes/unmerged-components.ts Extends UnmergedComponent schema with optional shouldSquash.
e2e/harmony/lanes/merge-lanes-squash-diverge.e2e.ts Adds E2E tests covering diverged squash scenarios and regressions.

Comment on lines +1116 to +1120
version.setSquashed({ previousParents, laneId: unmergedComponent.laneId }, version.log);
this.logger.debug(
`sources.addSource, unmerged component "${component.name}". squash-on-diverged: dropping parent ${unmergedComponent.head.hash}`
);
version.log.message = version.log.message || UnmergedComponents.buildSnapMessage(unmergedComponent);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants