@@ -1835,7 +1835,7 @@ func TestRunFixList(t *testing.T) {
18351835 // serverAddr is patched by daemonFromHandler called inside Build()
18361836
18371837 out , err := runWithOutput (t , repo .Dir , func (cmd * cobra.Command ) error {
1838- return runFixList (cmd , "" , false )
1838+ return runFixList (cmd , "" , true , false , false )
18391839 })
18401840 require .NoError (t , err , "runFixList" )
18411841
@@ -1863,7 +1863,7 @@ func TestRunFixList(t *testing.T) {
18631863 Build ()
18641864
18651865 out , err := runWithOutput (t , repo .Dir , func (cmd * cobra.Command ) error {
1866- return runFixList (cmd , "" , false )
1866+ return runFixList (cmd , "" , true , false , false )
18671867 })
18681868 require .NoError (t , err , "runFixList" )
18691869
@@ -1904,7 +1904,7 @@ func TestRunFixList(t *testing.T) {
19041904
19051905 gotIDs = nil
19061906 _ , err := runWithOutput (t , repo .Dir , func (cmd * cobra.Command ) error {
1907- return runFixList (cmd , "" , true )
1907+ return runFixList (cmd , "" , true , false , true )
19081908 })
19091909 require .NoError (t , err , "runFixList: %v" )
19101910
@@ -1981,7 +1981,7 @@ func TestFixWorktreeRepoResolution(t *testing.T) {
19811981 cmd := & cobra.Command {}
19821982 var buf bytes.Buffer
19831983 cmd .SetOut (& buf )
1984- if err := runFixList (cmd , "" , false ); err != nil {
1984+ if err := runFixList (cmd , "" , true , false , false ); err != nil {
19851985 require .NoError (t , err , "runFixList: %v" )
19861986 }
19871987
@@ -2793,18 +2793,32 @@ func TestFilterReachableJobs(t *testing.T) {
27932793 wantIDs : []int64 {1 },
27942794 },
27952795 {
2796- name : "unreachable commit excluded" ,
2796+ name : "unreachable SHA different branch excluded" ,
27972797 jobs : []storage.ReviewJob {
2798- {ID : 2 , GitRef : otherSHA },
2798+ {ID : 2 , GitRef : otherSHA , Branch : "other-branch" },
27992799 },
28002800 wantIDs : nil ,
28012801 },
28022802 {
2803- name : "mixed reachable and unreachable" ,
2803+ name : "unreachable SHA same branch included (rebase)" ,
2804+ jobs : []storage.ReviewJob {
2805+ {ID : 2 , GitRef : otherSHA , Branch : defaultBranch },
2806+ },
2807+ wantIDs : []int64 {2 },
2808+ },
2809+ {
2810+ name : "unreachable SHA no branch fails open" ,
28042811 jobs : []storage.ReviewJob {
2805- {ID : 1 , GitRef : mainSHA },
28062812 {ID : 2 , GitRef : otherSHA },
28072813 },
2814+ wantIDs : []int64 {2 },
2815+ },
2816+ {
2817+ name : "mixed reachable and unreachable different branch" ,
2818+ jobs : []storage.ReviewJob {
2819+ {ID : 1 , GitRef : mainSHA },
2820+ {ID : 2 , GitRef : otherSHA , Branch : "other-branch" },
2821+ },
28082822 wantIDs : []int64 {1 },
28092823 },
28102824 {
@@ -2857,12 +2871,21 @@ func TestFilterReachableJobs(t *testing.T) {
28572871 wantIDs : []int64 {5 },
28582872 },
28592873 {
2860- name : "range ref with unreachable end excluded" ,
2874+ name : "range ref unreachable end different branch excluded" ,
28612875 jobs : []storage.ReviewJob {
2862- {ID : 5 , GitRef : mainSHA + ".." + otherSHA },
2876+ {ID : 5 , GitRef : mainSHA + ".." + otherSHA ,
2877+ Branch : "other-branch" },
28632878 },
28642879 wantIDs : nil ,
28652880 },
2881+ {
2882+ name : "range ref unreachable end same branch included (rebase)" ,
2883+ jobs : []storage.ReviewJob {
2884+ {ID : 5 , GitRef : mainSHA + ".." + otherSHA ,
2885+ Branch : defaultBranch },
2886+ },
2887+ wantIDs : []int64 {5 },
2888+ },
28662889 {
28672890 name : "range ref with bad end fails open" ,
28682891 jobs : []storage.ReviewJob {
@@ -3141,3 +3164,105 @@ func TestRunFixOpenFiltersUnreachableJobs(t *testing.T) {
31413164 assert .NotContains (t , ids , int64 (100 ),
31423165 "main-only job should be filtered out" )
31433166}
3167+
3168+ // TestRunFixOpenFindsMergedBranchJobs verifies that roborev fix on the
3169+ // main branch discovers jobs whose commits were originally reviewed on
3170+ // a feature branch and later merged to main. The API query must NOT
3171+ // filter by branch when the branch was auto-resolved (not --branch),
3172+ // so that filterReachableJobs can accept the job via commit-graph
3173+ // reachability.
3174+ func TestRunFixOpenFindsMergedBranchJobs (t * testing.T ) {
3175+ repo := newTestGitRepo (t )
3176+ repo .CommitFile ("base.txt" , "base" , "initial commit" )
3177+
3178+ defaultBranch := strings .TrimSpace (
3179+ repo .Run ("rev-parse" , "--abbrev-ref" , "HEAD" ),
3180+ )
3181+
3182+ // Create a feature branch and commit
3183+ repo .Run ("checkout" , "-b" , "feature/widget" )
3184+ featureSHA := repo .CommitFile (
3185+ "widget.txt" , "code" , "add widget" ,
3186+ )
3187+
3188+ // Switch back to main and merge the feature branch
3189+ repo .Run ("checkout" , defaultBranch )
3190+ repo .Run ("merge" , "--no-ff" , "feature/widget" , "-m" , "merge feature" )
3191+
3192+ var processedJobIDs []int64
3193+ var mu sync.Mutex
3194+ var apiQueries []string
3195+
3196+ _ = newMockDaemonBuilder (t ).
3197+ WithHandler ("/api/jobs" , func (w http.ResponseWriter , r * http.Request ) {
3198+ q := r .URL .Query ()
3199+ apiQueries = append (apiQueries , r .URL .RawQuery )
3200+ if q .Get ("closed" ) == "false" && q .Get ("limit" ) == "0" {
3201+ // Return job created on the feature branch
3202+ writeJSON (w , map [string ]any {
3203+ "jobs" : []storage.ReviewJob {
3204+ {
3205+ ID : 300 ,
3206+ Status : storage .JobStatusDone ,
3207+ Agent : "test" ,
3208+ GitRef : featureSHA ,
3209+ Branch : "feature/widget" ,
3210+ },
3211+ },
3212+ "has_more" : false ,
3213+ })
3214+ } else if q .Get ("id" ) != "" {
3215+ var id int64
3216+ fmt .Sscanf (q .Get ("id" ), "%d" , & id )
3217+ mu .Lock ()
3218+ processedJobIDs = append (processedJobIDs , id )
3219+ mu .Unlock ()
3220+ writeJSON (w , map [string ]any {
3221+ "jobs" : []storage.ReviewJob {
3222+ {
3223+ ID : id ,
3224+ Status : storage .JobStatusDone ,
3225+ Agent : "test" ,
3226+ },
3227+ },
3228+ "has_more" : false ,
3229+ })
3230+ }
3231+ }).
3232+ WithHandler ("/api/review" , func (w http.ResponseWriter , r * http.Request ) {
3233+ writeJSON (w , storage.Review {Output : "findings" })
3234+ }).
3235+ WithHandler ("/api/comment" , func (w http.ResponseWriter , r * http.Request ) {
3236+ w .WriteHeader (http .StatusCreated )
3237+ }).
3238+ WithHandler ("/api/review/close" , func (w http.ResponseWriter , r * http.Request ) {
3239+ w .WriteHeader (http .StatusOK )
3240+ }).
3241+ WithHandler ("/api/enqueue" , func (w http.ResponseWriter , r * http.Request ) {
3242+ w .WriteHeader (http .StatusOK )
3243+ }).
3244+ Build ()
3245+
3246+ // Run from main with auto-resolved branch (explicitBranch=false).
3247+ // The feature SHA is reachable from HEAD via the merge commit.
3248+ _ , runErr := runWithOutput (t , repo .Dir , func (cmd * cobra.Command ) error {
3249+ return runFixOpen (
3250+ cmd , defaultBranch , false , false , false ,
3251+ fixOptions {agentName : "test" , reasoning : "fast" },
3252+ )
3253+ })
3254+ require .NoError (t , runErr , "runFixOpen" )
3255+
3256+ // The API query should NOT include a branch filter
3257+ require .NotEmpty (t , apiQueries ,
3258+ "expected at least one API query" )
3259+ assert .NotContains (t , apiQueries [0 ], "branch=" ,
3260+ "auto-resolved branch should not filter API query" )
3261+
3262+ // The merged feature job should be processed
3263+ mu .Lock ()
3264+ ids := processedJobIDs
3265+ mu .Unlock ()
3266+ assert .Contains (t , ids , int64 (300 ),
3267+ "merged feature branch job should be found via commit-graph" )
3268+ }
0 commit comments