@@ -264,7 +264,7 @@ def format_comment(report: dict, artifacts_url: str | None = None) -> str:
264264 "" ,
265265 ]
266266
267- if artifacts_url and content_diffs :
267+ if artifacts_url :
268268 lines .append (
269269 "Or download and apply the fix patch from the "
270270 f"[workflow artifacts]({ artifacts_url } #artifacts):"
@@ -355,15 +355,87 @@ def format_comment(report: dict, artifacts_url: str | None = None) -> str:
355355 return "\n " .join (lines )
356356
357357
358- def generate_patch (content_diffs : list [dict ]) -> str :
359- """Generate a git diff patch for files with real content diffs."""
360- if not content_diffs :
361- return ""
362- paths = [d ["path" ] for d in content_diffs ]
363- try :
364- return _git ("diff" , "--" , * paths )
365- except subprocess .CalledProcessError :
366- return ""
358+ def generate_patch (
359+ content_diffs : list [dict ],
360+ extra_files : list [str ],
361+ missing_files : list [str ],
362+ ) -> str :
363+ """Generate a git patch covering all detected drift.
364+
365+ - Modified files: standard git diff
366+ - Extra (untracked) files: added via git diff --no-index /dev/null <file>
367+ - Missing (deleted) files: removal diffs via diff header with empty content
368+ """
369+ parts : list [str ] = []
370+
371+ # Modified files
372+ if content_diffs :
373+ paths = [d ["path" ] for d in content_diffs ]
374+ try :
375+ parts .append (_git ("diff" , "--" , * paths ))
376+ except subprocess .CalledProcessError :
377+ pass
378+
379+ # Extra files — generate "new file" diffs
380+ for path_str in extra_files :
381+ file_path = Path (path_str )
382+ try :
383+ content = file_path .read_bytes ()
384+ except (FileNotFoundError , OSError ):
385+ continue
386+ # Check if binary
387+ try :
388+ text = content .decode ("utf-8" )
389+ except UnicodeDecodeError :
390+ parts .append (
391+ f"diff --git a/{ path_str } b/{ path_str } \n "
392+ f"new file mode 100644\n "
393+ f"Binary files /dev/null and b/{ path_str } differ\n "
394+ )
395+ continue
396+ lines = text .splitlines (keepends = True )
397+ n = len (lines )
398+ diff_body = "" .join (f"+{ line } " if line .endswith ("\n " ) else f"+{ line } \n \\ No newline at end of file\n " for line in lines )
399+ parts .append (
400+ f"diff --git a/{ path_str } b/{ path_str } \n "
401+ f"new file mode 100644\n "
402+ f"--- /dev/null\n "
403+ f"+++ b/{ path_str } \n "
404+ f"@@ -0,0 +1,{ n } @@\n "
405+ f"{ diff_body } "
406+ )
407+
408+ # Missing files — generate deletion diffs
409+ for path_str in missing_files :
410+ try :
411+ committed_bytes = subprocess .run (
412+ ["git" , "show" , f"HEAD:{ path_str } " ],
413+ capture_output = True , check = True ,
414+ ).stdout
415+ except subprocess .CalledProcessError :
416+ continue
417+ try :
418+ text = committed_bytes .decode ("utf-8" )
419+ except UnicodeDecodeError :
420+ parts .append (
421+ f"diff --git a/{ path_str } b/{ path_str } \n "
422+ f"deleted file mode 100644\n "
423+ f"Binary files a/{ path_str } and /dev/null differ\n "
424+ )
425+ continue
426+ lines = text .splitlines (keepends = True )
427+ n = len (lines )
428+ diff_body = "" .join (f"-{ line } " if line .endswith ("\n " ) else f"-{ line } \n \\ No newline at end of file\n " for line in lines )
429+ parts .append (
430+ f"diff --git a/{ path_str } b/{ path_str } \n "
431+ f"deleted file mode 100644\n "
432+ f"--- a/{ path_str } \n "
433+ f"+++ /dev/null\n "
434+ f"@@ -1,{ n } +0,0 @@\n "
435+ f"{ diff_body } "
436+ )
437+
438+ return "" .join (parts )
367439
368440
369441# ---------------------------------------------------------------------------
@@ -492,16 +564,16 @@ def main() -> int:
492564 json .dump (report , f , indent = 2 )
493565 print (f"Report written to { args .report } " )
494566
495- # 3b. Generate patch for real content diffs
496- if args .patch and content_diffs :
497- patch_content = generate_patch (content_diffs )
567+ total = len (content_diffs ) + len (extra ) + len (missing )
568+
569+ # 3b. Generate patch for all drift
570+ if args .patch and total > 0 :
571+ patch_content = generate_patch (content_diffs , extra , missing )
498572 if patch_content :
499573 args .patch .parent .mkdir (parents = True , exist_ok = True )
500574 args .patch .write_text (patch_content , encoding = "utf-8" )
501575 print (f"Patch written to { args .patch } " )
502576
503- total = len (content_diffs ) + len (extra ) + len (missing )
504-
505577 # 4. Post comment or clean up (best-effort — exit code is authoritative)
506578 if args .repo and args .pr :
507579 try :
0 commit comments