@@ -743,4 +743,79 @@ public async Task ForwardflowDoesntFlowOlderBuilds()
743743 var exception = await act . Should ( ) . ThrowAsync < NonLinearCodeflowException > ( ) ;
744744 exception . Which . FlowingOldBuild . Should ( ) . BeTrue ( ) ;
745745 }
746+
747+ [ Test ]
748+ public async Task ReflowAfterRevertedForwardflowAppliesCleanlyTest ( )
749+ {
750+ await EnsureTestRepoIsInitialized ( ) ;
751+
752+ const string branchName = nameof ( ReflowAfterRevertedForwardflowAppliesCleanlyTest ) ;
753+ const string vmrSideFileName = "vmr-side-file.txt" ;
754+
755+ // 1. Forward flow a first change and merge it
756+ var codeFlowResult = await ChangeRepoFileAndFlowIt ( "First change in the individual repo" , branchName ) ;
757+ codeFlowResult . ShouldHaveUpdates ( ) ;
758+ await FinalizeForwardFlow ( branchName ) ;
759+ CheckFileContents ( _productRepoVmrFilePath , "First change in the individual repo" ) ;
760+
761+ // 2. Forward flow a second change and merge it - keep a reference to the build so we can re-flow it later
762+ await GitOperations . Checkout ( ProductRepoPath , "main" ) ;
763+ await File . WriteAllTextAsync ( _productRepoFilePath , "Second change in the individual repo" ) ;
764+ await GitOperations . CommitAll ( ProductRepoPath , "Second change in the individual repo" ) ;
765+ var secondBuild = await CreateNewRepoBuild ( [ ] ) ;
766+
767+ codeFlowResult = await CallForwardflow ( Constants . ProductRepoName , ProductRepoPath , branchName , buildToFlow : secondBuild ) ;
768+ codeFlowResult . ShouldHaveUpdates ( ) ;
769+ await FinalizeForwardFlow ( branchName ) ;
770+ CheckFileContents ( _productRepoVmrFilePath , "Second change in the individual repo" ) ;
771+
772+ // Capture the squash-merge commit for the second forward flow so we can revert it later
773+ await GitOperations . Checkout ( VmrPath , "main" ) ;
774+ var secondFlowMergeCommit = await GitOperations . GetRepoLastCommit ( VmrPath ) ;
775+
776+ // 3. Backflow a VMR change to the product repo and merge it
777+ await GitOperations . Checkout ( VmrPath , "main" ) ;
778+ await File . WriteAllTextAsync ( _productRepoVmrPath / "backflowed-file.txt" , "File added in the VMR to be backflowed" ) ;
779+ await GitOperations . CommitAll ( VmrPath , "Add a file in the VMR to be backflowed" ) ;
780+
781+ codeFlowResult = await CallBackflow ( Constants . ProductRepoName , ProductRepoPath , branchName ) ;
782+ codeFlowResult . ShouldHaveUpdates ( ) ;
783+ await FinalizeBackFlow ( branchName ) ;
784+ CheckFileContents ( ProductRepoPath / "backflowed-file.txt" , "File added in the VMR to be backflowed" ) ;
785+
786+ // 4. Make an unrelated change in the VMR in the same product repo (must survive the revert + reflow)
787+ await GitOperations . Checkout ( VmrPath , "main" ) ;
788+ await File . WriteAllTextAsync ( _productRepoVmrPath / vmrSideFileName , "VMR-only change in the product repo" ) ;
789+ await GitOperations . CommitAll ( VmrPath , "Direct VMR change in the product repo" ) ;
790+
791+ // 5. Revert the second codeflow's merge commit in the VMR
792+ var revertResult = await GitOperations . ExecuteGitCommand ( VmrPath , "revert" , "--no-edit" , secondFlowMergeCommit ) ;
793+ revertResult . ThrowIfFailed ( "Failed to revert the second forward flow merge commit" ) ;
794+
795+ // After the revert, the second forward flow's content should be gone, but the VMR-only change should remain
796+ CheckFileContents ( _productRepoVmrFilePath , "First change in the individual repo" ) ;
797+ File . Exists ( _productRepoVmrPath / vmrSideFileName ) . Should ( ) . BeTrue ( ) ;
798+ CheckFileContents ( _productRepoVmrPath / vmrSideFileName , "VMR-only change in the product repo" ) ;
799+
800+ // 6. Backflow without merging - the product repo's file shouldn't be reverted back to the first state
801+ codeFlowResult = await CallBackflow ( Constants . ProductRepoName , ProductRepoPath , branchName ) ;
802+ codeFlowResult . ShouldHaveUpdates ( ) ;
803+ CheckFileContents ( _productRepoFilePath , "Second change in the individual repo" ) ;
804+
805+ // Reset the product repo back to main so the re-flow uses the latest repo commit
806+ await GitOperations . Checkout ( ProductRepoPath , "main" ) ;
807+
808+ // 7. Flow the same build again - it should cleanly apply just like the first time
809+ codeFlowResult = await CallForwardflow ( Constants . ProductRepoName , ProductRepoPath , branchName , buildToFlow : secondBuild ) ;
810+ codeFlowResult . ShouldHaveUpdates ( ) ;
811+ codeFlowResult . ConflictedFiles . Should ( ) . BeEmpty (
812+ "Re-flowing a build whose previous forward flow was reverted should apply cleanly without conflicts" ) ;
813+
814+ await FinalizeForwardFlow ( branchName ) ;
815+
816+ // The second change should be re-applied and the VMR-only change should still be present
817+ CheckFileContents ( _productRepoVmrFilePath , "Second change in the individual repo" ) ;
818+ File . Exists ( _productRepoVmrPath / vmrSideFileName ) . Should ( ) . BeTrue ( ) ;
819+ CheckFileContents ( _productRepoVmrPath / vmrSideFileName , "VMR-only change in the product repo" ) ;
820+ }
746821}
0 commit comments