@@ -267,118 +267,125 @@ export async function runPull(
267267 }
268268 writeOperationRecord ( wsDir , record ) ;
269269 } ;
270- const markConflicting = ( repo : string ) => {
270+ const markConflicting = ( repo : string , stderr ?: string ) => {
271271 const existing = record . repos [ repo ] ;
272272 if ( existing ) {
273- record . repos [ repo ] = { ...existing , status : "conflicting" } ;
273+ const errorOutput = stderr ?. trim ( ) . slice ( 0 , 4000 ) || undefined ;
274+ record . repos [ repo ] = { ...existing , status : "conflicting" , errorOutput } ;
274275 }
275276 writeOperationRecord ( wsDir , record ) ;
276277 } ;
277278
278- for ( const a of willPull ) {
279- const strategy = a . pullStrategy ?? ( a . pullMode === "rebase" ? "rebase-pull" : "merge-pull" ) ;
280- inlineStart ( a . repo , `pulling (${ pullStrategyLabel ( strategy ) } )` ) ;
281- const pullRemote = remotesMap . get ( a . repo ) ?. share ;
282- if ( ! pullRemote ) continue ;
283-
284- if ( strategy === "rebase-pull" ) {
285- const pullArgs = a . needsStash
286- ? [ "pull" , "--rebase" , "--autostash" , pullRemote , a . branch ]
287- : [ "pull" , "--rebase" , pullRemote , a . branch ] ;
288- const pullResult = await gitNetwork ( a . repoDir , pullTimeout , pullArgs ) ;
289- if ( pullResult . exitCode === 0 ) {
290- await markCompleted ( a . repo ) ;
291- inlineResult ( a . repo , `pulled ${ plural ( a . behind , "commit" ) } (${ a . pullMode } )` ) ;
292- pullOk ++ ;
293- } else {
294- if ( isConflictResult ( pullResult . stdout , pullResult . stderr ) ) {
295- markConflicting ( a . repo ) ;
296- inlineResult ( a . repo , yellow ( "conflict" ) ) ;
297- conflicted . push ( { assessment : a , stdout : pullResult . stdout , stderr : pullResult . stderr } ) ;
279+ try {
280+ process . env . GIT_REFLOG_ACTION = "arb-pull" ;
281+ for ( const a of willPull ) {
282+ const strategy = a . pullStrategy ?? ( a . pullMode === "rebase" ? "rebase-pull" : "merge-pull" ) ;
283+ inlineStart ( a . repo , `pulling (${ pullStrategyLabel ( strategy ) } )` ) ;
284+ const pullRemote = remotesMap . get ( a . repo ) ?. share ;
285+ if ( ! pullRemote ) continue ;
286+
287+ if ( strategy === "rebase-pull" ) {
288+ const pullArgs = a . needsStash
289+ ? [ "pull" , "--rebase" , "--autostash" , pullRemote , a . branch ]
290+ : [ "pull" , "--rebase" , pullRemote , a . branch ] ;
291+ const pullResult = await gitNetwork ( a . repoDir , pullTimeout , pullArgs ) ;
292+ if ( pullResult . exitCode === 0 ) {
293+ await markCompleted ( a . repo ) ;
294+ inlineResult ( a . repo , `pulled ${ plural ( a . behind , "commit" ) } (${ a . pullMode } )` ) ;
295+ pullOk ++ ;
298296 } else {
299- inlineResult ( a . repo , yellow ( "failed" ) ) ;
300- failed . push ( {
301- assessment : a ,
302- exitCode : pullResult . exitCode ,
303- stdout : pullResult . stdout ,
304- stderr : pullResult . stderr ,
305- action : "pull --rebase" ,
306- } ) ;
307- }
308- }
309- } else if ( strategy === "safe-reset" || strategy === "forced-reset" ) {
310- if ( a . needsStash ) {
311- await gitLocal ( a . repoDir , "stash" , "push" , "-m" , "arb: autostash before pull" ) ;
312- }
313- const target = a . safeReset ?. target ?? `${ pullRemote } /${ a . branch } ` ;
314- const resetLabel = strategy === "forced-reset" ? "forced reset" : "safe reset" ;
315- const resetResult = await gitLocal ( a . repoDir , "reset" , "--hard" , target ) ;
316- if ( resetResult . exitCode === 0 ) {
317- let stashPopOk = true ;
318- if ( a . needsStash ) {
319- const popResult = await gitLocal ( a . repoDir , "stash" , "pop" ) ;
320- if ( popResult . exitCode !== 0 ) {
321- stashPopOk = false ;
322- stashPopFailed . push ( a ) ;
297+ if ( isConflictResult ( pullResult . stdout , pullResult . stderr ) ) {
298+ markConflicting ( a . repo , pullResult . stderr ) ;
299+ inlineResult ( a . repo , yellow ( "conflict" ) ) ;
300+ conflicted . push ( { assessment : a , stdout : pullResult . stdout , stderr : pullResult . stderr } ) ;
301+ } else {
302+ inlineResult ( a . repo , yellow ( "failed" ) ) ;
303+ failed . push ( {
304+ assessment : a ,
305+ exitCode : pullResult . exitCode ,
306+ stdout : pullResult . stdout ,
307+ stderr : pullResult . stderr ,
308+ action : "pull --rebase" ,
309+ } ) ;
323310 }
324311 }
325- await markCompleted ( a . repo ) ;
326- let doneMsg = `${ resetLabel } to ${ target } ` ;
327- if ( ! stashPopOk ) {
328- doneMsg += ` ${ yellow ( "(stash pop failed)" ) } ` ;
329- }
330- inlineResult ( a . repo , doneMsg ) ;
331- pullOk ++ ;
332- } else {
333- markConflicting ( a . repo ) ;
334- inlineResult ( a . repo , yellow ( "failed" ) ) ;
335- failed . push ( {
336- assessment : a ,
337- exitCode : resetResult . exitCode ,
338- stdout : resetResult . stdout ,
339- stderr : resetResult . stderr ,
340- action : `reset --hard ${ target } ` ,
341- } ) ;
342- }
343- } else {
344- if ( a . needsStash ) {
345- await gitLocal ( a . repoDir , "stash" , "push" , "-m" , "arb: autostash before pull" ) ;
346- }
347- const pullResult = await gitNetwork ( a . repoDir , pullTimeout , [ "pull" , "--no-rebase" , pullRemote , a . branch ] ) ;
348- if ( pullResult . exitCode === 0 ) {
349- let stashPopOk = true ;
312+ } else if ( strategy === "safe-reset" || strategy === "forced-reset" ) {
350313 if ( a . needsStash ) {
351- const popResult = await gitLocal ( a . repoDir , "stash" , "pop" ) ;
352- if ( popResult . exitCode !== 0 ) {
353- stashPopOk = false ;
354- stashPopFailed . push ( a ) ;
355- }
356- }
357- await markCompleted ( a . repo ) ;
358- let doneMsg = `pulled ${ plural ( a . behind , "commit" ) } (${ a . pullMode } )` ;
359- if ( ! stashPopOk ) {
360- doneMsg += ` ${ yellow ( "(stash pop failed)" ) } ` ;
314+ await gitLocal ( a . repoDir , "stash" , "push" , "-m" , "arb: autostash before pull" ) ;
361315 }
362- inlineResult ( a . repo , doneMsg ) ;
363- pullOk ++ ;
364- } else {
365- if ( isConflictResult ( pullResult . stdout , pullResult . stderr ) ) {
366- markConflicting ( a . repo ) ;
367- inlineResult ( a . repo , yellow ( "conflict" ) ) ;
368- conflicted . push ( { assessment : a , stdout : pullResult . stdout , stderr : pullResult . stderr } ) ;
316+ const target = a . safeReset ?. target ?? `${ pullRemote } /${ a . branch } ` ;
317+ const resetLabel = strategy === "forced-reset" ? "forced reset" : "safe reset" ;
318+ const resetResult = await gitLocal ( a . repoDir , "reset" , "--hard" , target ) ;
319+ if ( resetResult . exitCode === 0 ) {
320+ let stashPopOk = true ;
321+ if ( a . needsStash ) {
322+ const popResult = await gitLocal ( a . repoDir , "stash" , "pop" ) ;
323+ if ( popResult . exitCode !== 0 ) {
324+ stashPopOk = false ;
325+ stashPopFailed . push ( a ) ;
326+ }
327+ }
328+ await markCompleted ( a . repo ) ;
329+ let doneMsg = `${ resetLabel } to ${ target } ` ;
330+ if ( ! stashPopOk ) {
331+ doneMsg += ` ${ yellow ( "(stash pop failed)" ) } ` ;
332+ }
333+ inlineResult ( a . repo , doneMsg ) ;
334+ pullOk ++ ;
369335 } else {
370- markConflicting ( a . repo ) ;
336+ markConflicting ( a . repo , resetResult . stderr ) ;
371337 inlineResult ( a . repo , yellow ( "failed" ) ) ;
372338 failed . push ( {
373339 assessment : a ,
374- exitCode : pullResult . exitCode ,
375- stdout : pullResult . stdout ,
376- stderr : pullResult . stderr ,
377- action : "pull --no-rebase" ,
340+ exitCode : resetResult . exitCode ,
341+ stdout : resetResult . stdout ,
342+ stderr : resetResult . stderr ,
343+ action : `reset --hard ${ target } ` ,
378344 } ) ;
379345 }
346+ } else {
347+ if ( a . needsStash ) {
348+ await gitLocal ( a . repoDir , "stash" , "push" , "-m" , "arb: autostash before pull" ) ;
349+ }
350+ const pullResult = await gitNetwork ( a . repoDir , pullTimeout , [ "pull" , "--no-rebase" , pullRemote , a . branch ] ) ;
351+ if ( pullResult . exitCode === 0 ) {
352+ let stashPopOk = true ;
353+ if ( a . needsStash ) {
354+ const popResult = await gitLocal ( a . repoDir , "stash" , "pop" ) ;
355+ if ( popResult . exitCode !== 0 ) {
356+ stashPopOk = false ;
357+ stashPopFailed . push ( a ) ;
358+ }
359+ }
360+ await markCompleted ( a . repo ) ;
361+ let doneMsg = `pulled ${ plural ( a . behind , "commit" ) } (${ a . pullMode } )` ;
362+ if ( ! stashPopOk ) {
363+ doneMsg += ` ${ yellow ( "(stash pop failed)" ) } ` ;
364+ }
365+ inlineResult ( a . repo , doneMsg ) ;
366+ pullOk ++ ;
367+ } else {
368+ if ( isConflictResult ( pullResult . stdout , pullResult . stderr ) ) {
369+ markConflicting ( a . repo , pullResult . stderr ) ;
370+ inlineResult ( a . repo , yellow ( "conflict" ) ) ;
371+ conflicted . push ( { assessment : a , stdout : pullResult . stdout , stderr : pullResult . stderr } ) ;
372+ } else {
373+ markConflicting ( a . repo , pullResult . stderr ) ;
374+ inlineResult ( a . repo , yellow ( "failed" ) ) ;
375+ failed . push ( {
376+ assessment : a ,
377+ exitCode : pullResult . exitCode ,
378+ stdout : pullResult . stdout ,
379+ stderr : pullResult . stderr ,
380+ action : "pull --no-rebase" ,
381+ } ) ;
382+ }
383+ }
380384 }
381385 }
386+ } finally {
387+ // biome-ignore lint/performance/noDelete: must truly unset env var, not coerce to string
388+ delete process . env . GIT_REFLOG_ACTION ;
382389 }
383390
384391 // Consolidated conflict report
@@ -405,6 +412,7 @@ export async function runPull(
405412 // Finalize operation record
406413 if ( conflicted . length === 0 && failed . length === 0 ) {
407414 record . status = "completed" ;
415+ record . completedAt = new Date ( ) . toISOString ( ) ;
408416 writeOperationRecord ( wsDir , record ) ;
409417 } else {
410418 info ( "Use 'arb pull --continue' to resume or 'arb pull --abort' to cancel" ) ;
0 commit comments