diff --git a/packages/adt-flow/src/service.ts b/packages/adt-flow/src/service.ts index d022d4c8..537e8cb6 100644 --- a/packages/adt-flow/src/service.ts +++ b/packages/adt-flow/src/service.ts @@ -242,11 +242,18 @@ function groupEntries(entries: readonly TransportSourceManifestEntry[]): Array<{ ); } +const ACTIVE_VERSION_ID = '00000'; + +function isMutableVersion(versionId: string): boolean { + return versionId === ACTIVE_VERSION_ID; +} + function descriptorSelectionMatches( descriptor: ObjectDescriptor, component: string, version: SourceVersionRef, ): boolean { + if (isMutableVersion(version.id)) return false; return descriptor.selections.some( (selection) => selection.component === component && @@ -368,7 +375,10 @@ async function exactHeadFastPath( if ( !descriptor || descriptor.configDigest !== configDigest || - descriptor.formatDigest !== formatDigest + descriptor.formatDigest !== formatDigest || + descriptor.selections.some((selection) => + isMutableVersion(selection.versionId), + ) ) { return undefined; } diff --git a/packages/adt-flow/tests/service.test.ts b/packages/adt-flow/tests/service.test.ts index 4db5ff6b..cc2861ff 100644 --- a/packages/adt-flow/tests/service.test.ts +++ b/packages/adt-flow/tests/service.test.ts @@ -497,6 +497,89 @@ describe('transport checkout', () => { ).toBe('source indexed\n'); }); + it('re-reads an indexed active 00000 head instead of reusing it', async () => { + const workspace = await root(); + let current = manifest( + 'modified', + version('before'), + version('00000'), + 'DEVK900001', + ); + const ports = dependencies(() => current); + let liveSource = 'source first\n'; + ports.readSource.mockImplementation(async () => liveSource); + const flow = createAdtFlowService(ports); + await flow.checkout({ + root: workspace, + transports: ['DEVK900001'], + config, + }); + ports.readSource.mockClear(); + ports.loadObject.mockClear(); + liveSource = 'source second\n'; + current = manifest( + 'modified', + version('before'), + version('00000'), + 'DEVK900002', + ); + + const result = await flow.checkout({ + root: workspace, + transports: ['DEVK900002'], + config, + }); + + expect(result.fastPath).toBe('none'); + expect(result.sapCalls).toEqual({ manifest: 1, metadata: 1, source: 1 }); + expect(ports.loadObject).toHaveBeenCalledTimes(1); + expect(result.changed).toEqual(['src/feature/zcl_sample.clas.abap']); + expect( + await readFile( + join(workspace, 'src/feature/zcl_sample.clas.abap'), + 'utf8', + ), + ).toBe('source second\n'); + }); + + it('does not repeat an active 00000 head through the exact-head fast path', async () => { + const workspace = await root(); + const ports = dependencies(() => + manifest('modified', version('before'), version('00000')), + ); + let liveSource = 'source first\n'; + ports.readSource.mockImplementation(async () => liveSource); + const flow = createAdtFlowService(ports); + await flow.checkout({ + root: workspace, + transports: ['DEVK900001'], + config, + }); + + const unchanged = await flow.checkout({ + root: workspace, + transports: ['DEVK900001'], + config, + }); + expect(unchanged.fastPath).toBe('none'); + expect(unchanged.changed).toEqual([]); + + liveSource = 'source second\n'; + const changed = await flow.checkout({ + root: workspace, + transports: ['DEVK900001'], + config, + }); + expect(changed.fastPath).toBe('none'); + expect(changed.changed).toEqual(['src/feature/zcl_sample.clas.abap']); + expect( + await readFile( + join(workspace, 'src/feature/zcl_sample.clas.abap'), + 'utf8', + ), + ).toBe('source second\n'); + }); + it('preserves an indexed object as the base of a later parent-attributed task', async () => { const workspace = await root(); let current = manifest(