@@ -68,6 +68,8 @@ static enum output_mode output_mode = OUTPUT_LIST;
6868static enum match_filter match_filter = MATCH_ALL ;
6969static enum numbered_mode numbered_mode = NUMBERED_NONE ;
7070static int extended_regexp = 0 ; /* -E, --extended-regexp */
71+ static int show_status = 0 ; /* -s, --status */
72+ static int empty_files_as_absent = 0 ; /* --empty-files-as-absent */
7173
7274/* Grep patterns */
7375static regex_t * grep_patterns = NULL ;
@@ -109,6 +111,9 @@ struct buffered_file {
109111 int max_hunks ;
110112 int has_match ; /* Does this file have any matching hunks? */
111113 int is_context_diff ;
114+ char initial_status ; /* Initial file status from headers (+, -, !) */
115+ int orig_is_empty ; /* Is original file empty (from first hunk)? */
116+ int new_is_empty ; /* Is new file empty (from first hunk)? */
112117};
113118
114119/* Forward declarations */
@@ -134,6 +139,7 @@ static void syntax(int err)
134139 fprintf (f , "Usage: %s [OPTION]... PATTERN [FILE]...\n" , "grepdiff" );
135140 fprintf (f , "Show files modified by patches containing a regexp.\n\n" );
136141 fprintf (f , "Options:\n" );
142+ fprintf (f , " -s, --status show file additions (+), removals (-), and modifications (!)\n" );
137143 fprintf (f , " -n, --line-number show line numbers\n" );
138144 fprintf (f , " -N, --number-files show file numbers (for use with filterdiff --files)\n" );
139145 fprintf (f , " -H, --with-filename show patch file names\n" );
@@ -156,6 +162,7 @@ static void syntax(int err)
156162 fprintf (f , " (PCRE regexes are used by default)\n" );
157163#endif
158164 fprintf (f , " -f FILE, --file=FILE read regular expressions from FILE\n" );
165+ fprintf (f , " --empty-files-as-absent treat empty files as absent (with -s)\n" );
159166 fprintf (f , " --help display this help and exit\n" );
160167 fprintf (f , " --version output version information and exit\n" );
161168 fprintf (
f ,
"\nReport bugs to <[email protected] >.\n" );
@@ -407,6 +414,14 @@ static void process_patch_file(FILE *fp, const char *filename)
407414 current_file .header_line = global_line_offset + content -> data .headers -> start_line ;
408415 current_file .is_context_diff = (content -> data .headers -> type == PATCH_TYPE_CONTEXT );
409416
417+ /* Determine initial status from headers (for -s/--status) */
418+ if (show_status ) {
419+ current_file .initial_status = determine_file_status (content -> data .headers , empty_files_as_absent );
420+ /* Initialize empty file tracking - assume empty until we see hunks */
421+ current_file .orig_is_empty = 1 ;
422+ current_file .new_is_empty = 1 ;
423+ }
424+
410425 /* Copy header lines for file/hunk output modes */
411426 if (output_mode != OUTPUT_LIST ) {
412427 const struct patch_headers * hdrs = content -> data .headers ;
@@ -442,6 +457,16 @@ static void process_patch_file(FILE *fp, const char *filename)
442457 current_hunk -> context = xstrdup (hunk -> context );
443458 }
444459
460+ /* Track empty files from first hunk only (for --empty-files-as-absent) */
461+ if (show_status && current_file .num_hunks == 1 ) {
462+ if (hunk -> orig_count > 0 ) {
463+ current_file .orig_is_empty = 0 ;
464+ }
465+ if (hunk -> new_count > 0 ) {
466+ current_file .new_is_empty = 0 ;
467+ }
468+ }
469+
445470 /* Initialize line number tracking */
446471 orig_line = hunk -> orig_offset ;
447472 new_line = hunk -> new_offset ;
@@ -542,7 +567,34 @@ static void output_buffered_file(struct buffered_file *file)
542567 /* In list mode, just print filename if it has matches */
543568 if (output_mode == OUTPUT_LIST ) {
544569 if (file -> has_match ) {
545- display_filename (file -> best_filename , file -> patchname , file -> header_line );
570+ /* Calculate final status for -s/--status */
571+ if (show_status ) {
572+ char final_status = file -> initial_status ;
573+
574+ /* Adjust status based on --empty-files-as-absent */
575+ if (empty_files_as_absent ) {
576+ int orig_absent = (file -> orig_is_empty != 0 );
577+ int new_absent = (file -> new_is_empty != 0 );
578+
579+ if (orig_absent && !new_absent ) {
580+ final_status = '+' ; /* Treat as file addition */
581+ } else if (!orig_absent && new_absent ) {
582+ final_status = '-' ; /* Treat as file deletion */
583+ } else if (!orig_absent && !new_absent ) {
584+ final_status = '!' ; /* Treat as modification */
585+ }
586+ /* If both absent, skip the file (shouldn't normally happen) */
587+ if (orig_absent && new_absent ) {
588+ return ;
589+ }
590+ }
591+
592+ /* Display with status prefix */
593+ display_filename_extended (file -> best_filename , file -> patchname , file -> header_line ,
594+ final_status , show_status );
595+ } else {
596+ display_filename (file -> best_filename , file -> patchname , file -> header_line );
597+ }
546598
547599 /* In verbose mode with line numbers, show hunk information */
548600 if (verbose > 0 && show_line_numbers ) {
@@ -878,11 +930,13 @@ int run_grep_mode(int argc, char *argv[])
878930 /* Add tool-specific long options */
879931 long_options [next_idx ++ ] = (struct option ){"help" , 0 , 0 , 1000 + 'H' };
880932 long_options [next_idx ++ ] = (struct option ){"version" , 0 , 0 , 1000 + 'V' };
933+ long_options [next_idx ++ ] = (struct option ){"status" , 0 , 0 , 's' };
881934 long_options [next_idx ++ ] = (struct option ){"extended-regexp" , 0 , 0 , 'E' };
882935 long_options [next_idx ++ ] = (struct option ){"file" , 1 , 0 , 'f' };
883936 long_options [next_idx ++ ] = (struct option ){"output-matching" , 1 , 0 , 1000 + 'M' };
884937 long_options [next_idx ++ ] = (struct option ){"only-match" , 1 , 0 , 1000 + 'm' };
885938 long_options [next_idx ++ ] = (struct option ){"as-numbered-lines" , 1 , 0 , 1000 + 'L' };
939+ long_options [next_idx ++ ] = (struct option ){"empty-files-as-absent" , 0 , 0 , 1000 + 'e' };
886940 /* Mode options (handled by patchfilter, but need to be recognized) */
887941 long_options [next_idx ++ ] = (struct option ){"list" , 0 , 0 , 1000 + 'l' };
888942 long_options [next_idx ++ ] = (struct option ){"filter" , 0 , 0 , 1000 + 'F' };
@@ -897,7 +951,7 @@ int run_grep_mode(int argc, char *argv[])
897951
898952 /* Combine common and tool-specific short options */
899953 char short_options [64 ];
900- snprintf (short_options , sizeof (short_options ), "%sEf :" , get_common_short_options ());
954+ snprintf (short_options , sizeof (short_options ), "%ssEf :" , get_common_short_options ());
901955
902956 int c = getopt_long (argc , argv , short_options , long_options , NULL );
903957 if (c == -1 )
@@ -916,12 +970,18 @@ int run_grep_mode(int argc, char *argv[])
916970 case 1000 + 'V' :
917971 printf ("grepdiff - patchutils version %s\n" , VERSION );
918972 exit (0 );
973+ case 's' :
974+ show_status = 1 ;
975+ break ;
919976 case 'E' :
920977 extended_regexp = 1 ;
921978 break ;
922979 case 'f' :
923980 add_patterns_from_file (optarg );
924981 break ;
982+ case 1000 + 'e' :
983+ empty_files_as_absent = 1 ;
984+ break ;
925985 case 1000 + 'M' :
926986 if (!strncmp (optarg , "file" , 4 )) {
927987 output_mode = OUTPUT_FILE ;
0 commit comments