@@ -715,6 +715,147 @@ void repositoryRefreshAllPromptsWhenMergedActiveProfileFilesHaveLocalChangesAndC
715715 assertEquals ("edit" , unchanged .get ("local" ));
716716 }
717717
718+ @ Test
719+ void repositoryRefreshSinglePromptsWhenMergedActiveProfileFilesHaveLocalChangesAndCanDiscardThenRefresh () throws IOException , InterruptedException {
720+ RemoteRepositoryState state = createRemoteInheritedProfileRepository ("oca" , "oca-personal" );
721+
722+ writeOcpConfig (
723+ new OcpConfigFile (
724+ new OcpConfigOptions (),
725+ List .of (new RepositoryEntry ("repo-refresh-inherited" , state .remoteUri (), null ))
726+ )
727+ );
728+
729+ runCommand (List .of ("git" , "clone" , state .remoteUri (), state .localClone ().toString ()));
730+
731+ CommandResult useResult = execute ("profile" , "use" , "oca-personal" );
732+ assertEquals (0 , useResult .exitCode ());
733+
734+ Path opencodeFile = Path .of (System .getProperty ("ocp.opencode.config.dir" )).resolve ("opencode.json" );
735+ Files .writeString (opencodeFile , "{\" some_parent\" :\" local\" ,\" shared\" :\" child\" ,\" local\" :\" edit\" }" );
736+
737+ CommandResult refreshResult = executeWithInput ("1\n " , "repository" , "refresh" , "repo-refresh-inherited" );
738+
739+ assertEquals (0 , refreshResult .exitCode ());
740+ assertTrue (refreshResult .stdout ().contains ("Local changes detected in merged active profile files for profile `oca-personal`" ));
741+ assertTrue (refreshResult .stdout ().contains ("Discarding local changes in merged user config files and retrying refresh" ));
742+ assertTrue (refreshResult .stdout ().contains ("Reapplied active profile and refreshed repository `repo-refresh-inherited`." ));
743+ Map <String , Object > refreshed = readJsonMap (opencodeFile );
744+ assertEquals ("v1" , refreshed .get ("some_parent" ));
745+ assertEquals ("child" , refreshed .get ("shared" ));
746+ assertFalse (refreshed .containsKey ("local" ));
747+ }
748+
749+ @ Test
750+ void repositoryRefreshSinglePromptsWhenMergedActiveProfileFilesHaveLocalChangesAndCanAbortWithoutChanges () throws IOException , InterruptedException {
751+ RemoteRepositoryState state = createRemoteInheritedProfileRepository ("oca" , "oca-personal" );
752+
753+ writeOcpConfig (
754+ new OcpConfigFile (
755+ new OcpConfigOptions (),
756+ List .of (new RepositoryEntry ("repo-refresh-inherited" , state .remoteUri (), null ))
757+ )
758+ );
759+
760+ runCommand (List .of ("git" , "clone" , state .remoteUri (), state .localClone ().toString ()));
761+
762+ CommandResult useResult = execute ("profile" , "use" , "oca-personal" );
763+ assertEquals (0 , useResult .exitCode ());
764+
765+ Path opencodeFile = Path .of (System .getProperty ("ocp.opencode.config.dir" )).resolve ("opencode.json" );
766+ Files .writeString (opencodeFile , "{\" some_parent\" :\" local\" ,\" shared\" :\" child\" ,\" local\" :\" edit\" }" );
767+
768+ CommandResult refreshResult = executeWithInput ("2\n " , "repository" , "refresh" , "repo-refresh-inherited" );
769+
770+ assertEquals (1 , refreshResult .exitCode ());
771+ assertTrue (refreshResult .stdout ().contains ("Local changes detected in merged active profile files for profile `oca-personal`" ));
772+ assertTrue (refreshResult .stderr ().contains ("Refresh cancelled" ));
773+ Map <String , Object > unchanged = readJsonMap (opencodeFile );
774+ assertEquals ("local" , unchanged .get ("some_parent" ));
775+ assertEquals ("child" , unchanged .get ("shared" ));
776+ assertEquals ("edit" , unchanged .get ("local" ));
777+ }
778+
779+ @ Test
780+ void repositoryRefreshSingleHandlesMergedAndRepositoryConflictsWhenBothDiscarded () throws IOException , InterruptedException {
781+ RemoteRepositoryState state = createRemoteInheritedProfileRepository ("oca" , "oca-personal" );
782+
783+ writeOcpConfig (
784+ new OcpConfigFile (
785+ new OcpConfigOptions (),
786+ List .of (new RepositoryEntry ("repo-refresh-inherited" , state .remoteUri (), null ))
787+ )
788+ );
789+
790+ runCommand (List .of ("git" , "clone" , state .remoteUri (), state .localClone ().toString ()));
791+
792+ CommandResult useResult = execute ("profile" , "use" , "oca-personal" );
793+ assertEquals (0 , useResult .exitCode ());
794+
795+ Path opencodeFile = Path .of (System .getProperty ("ocp.opencode.config.dir" )).resolve ("opencode.json" );
796+ Files .writeString (opencodeFile , "{\" some_parent\" :\" drift\" ,\" shared\" :\" child\" ,\" local\" :\" edit\" }" );
797+
798+ Path repositoryScratchFile = state .localClone ().resolve ("local-change.txt" );
799+ Files .writeString (repositoryScratchFile , "local-repository-change" );
800+
801+ CommandResult refreshResult = executeWithInput ("1\n 1\n " , "repository" , "refresh" , "repo-refresh-inherited" );
802+
803+ assertEquals (0 , refreshResult .exitCode ());
804+ assertTrue (refreshResult .stdout ().contains ("Local changes detected in merged active profile files for profile `oca-personal`" ));
805+ assertTrue (refreshResult .stdout ().contains ("Local uncommitted changes detected in repository `repo-refresh-inherited`" ));
806+ assertTrue (
807+ refreshResult
808+ .stdout ()
809+ .contains ("Reapplied active profile, discarded repository local changes, and refreshed repository `repo-refresh-inherited`." )
810+ );
811+
812+ Map <String , Object > refreshed = readJsonMap (opencodeFile );
813+ assertEquals ("v1" , refreshed .get ("some_parent" ));
814+ assertEquals ("child" , refreshed .get ("shared" ));
815+ assertFalse (refreshed .containsKey ("local" ));
816+ assertTrue (Files .notExists (repositoryScratchFile ));
817+ }
818+
819+ @ Test
820+ void repositoryRefreshSingleHandlesMergedAndRepositoryConflictsWhenRepositoryIsCommitted () throws IOException , InterruptedException {
821+ RemoteRepositoryState state = createRemoteInheritedProfileRepository ("oca" , "oca-personal" );
822+
823+ writeOcpConfig (
824+ new OcpConfigFile (
825+ new OcpConfigOptions (),
826+ List .of (new RepositoryEntry ("repo-refresh-inherited" , state .remoteUri (), null ))
827+ )
828+ );
829+
830+ runCommand (List .of ("git" , "clone" , state .remoteUri (), state .localClone ().toString ()));
831+
832+ CommandResult useResult = execute ("profile" , "use" , "oca-personal" );
833+ assertEquals (0 , useResult .exitCode ());
834+
835+ Path opencodeFile = Path .of (System .getProperty ("ocp.opencode.config.dir" )).resolve ("opencode.json" );
836+ Files .writeString (opencodeFile , "{\" some_parent\" :\" drift\" ,\" shared\" :\" child\" ,\" local\" :\" edit\" }" );
837+
838+ Path repositoryScratchFile = state .localClone ().resolve ("local-change.txt" );
839+ Files .writeString (repositoryScratchFile , "local-repository-change" );
840+
841+ CommandResult refreshResult = executeWithInput ("1\n 2\n " , "repository" , "refresh" , "repo-refresh-inherited" );
842+
843+ assertEquals (0 , refreshResult .exitCode ());
844+ assertTrue (refreshResult .stdout ().contains ("Local changes detected in merged active profile files for profile `oca-personal`" ));
845+ assertTrue (refreshResult .stdout ().contains ("Local uncommitted changes detected in repository `repo-refresh-inherited`" ));
846+ assertTrue (
847+ refreshResult
848+ .stdout ()
849+ .contains ("Reapplied active profile, committed local changes, force-pushed, and refreshed repository `repo-refresh-inherited`." )
850+ );
851+
852+ Map <String , Object > refreshed = readJsonMap (opencodeFile );
853+ assertEquals ("v1" , refreshed .get ("some_parent" ));
854+ assertEquals ("child" , refreshed .get ("shared" ));
855+ assertFalse (refreshed .containsKey ("local" ));
856+ assertEquals ("local-repository-change" , Files .readString (repositoryScratchFile ));
857+ }
858+
718859 @ Test
719860 void repositoryRefreshPromptsWhenRepositoryHasLocalChangesAndCanDiscardThenRefresh () throws IOException , InterruptedException {
720861 RemoteRepositoryState state = createRemoteProfileRepository ("ops" );
0 commit comments