2424#include "dir.h"
2525#include "progress.h"
2626#include "blame.h"
27+ #include "string-list.h"
2728
2829static char blame_usage [] = N_ ("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>" );
2930
@@ -323,6 +324,7 @@ static const char *format_time(timestamp_t time, const char *tz_str,
323324#define OUTPUT_SHOW_EMAIL 0400
324325#define OUTPUT_LINE_PORCELAIN 01000
325326#define OUTPUT_COLOR_LINE 02000
327+ #define OUTPUT_SHOW_AGE_WITH_COLOR 04000
326328
327329static void emit_porcelain_details (struct blame_origin * suspect , int repeat )
328330{
@@ -370,6 +372,63 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
370372 putchar ('\n' );
371373}
372374
375+ static struct color_field {
376+ timestamp_t hop ;
377+ char col [COLOR_MAXLEN ];
378+ } * colorfield ;
379+ static int colorfield_nr , colorfield_alloc ;
380+
381+ static void parse_color_fields (const char * s )
382+ {
383+ struct string_list l = STRING_LIST_INIT_DUP ;
384+ struct string_list_item * item ;
385+ enum { EXPECT_DATE , EXPECT_COLOR } next = EXPECT_COLOR ;
386+
387+ colorfield_nr = 0 ;
388+
389+ /* Ideally this would be stripped and split at the same time? */
390+ string_list_split (& l , s , ',' , -1 );
391+ ALLOC_GROW (colorfield , colorfield_nr + 1 , colorfield_alloc );
392+
393+ for_each_string_list_item (item , & l ) {
394+ switch (next ) {
395+ case EXPECT_DATE :
396+ colorfield [colorfield_nr ].hop = approxidate (item -> string );
397+ next = EXPECT_COLOR ;
398+ colorfield_nr ++ ;
399+ ALLOC_GROW (colorfield , colorfield_nr + 1 , colorfield_alloc );
400+ break ;
401+ case EXPECT_COLOR :
402+ if (color_parse (item -> string , colorfield [colorfield_nr ].col ))
403+ die (_ ("expecting a color: %s" ), item -> string );
404+ next = EXPECT_DATE ;
405+ break ;
406+ }
407+ }
408+
409+ if (next == EXPECT_COLOR )
410+ die (_ ("must end with a color" ));
411+
412+ colorfield [colorfield_nr ].hop = TIME_MAX ;
413+ }
414+
415+ static void setup_default_color_by_age (void )
416+ {
417+ parse_color_fields ("blue,12 month ago,white,1 month ago,red" );
418+ }
419+
420+ static void determine_line_heat (struct blame_entry * ent , const char * * dest_color )
421+ {
422+ int i = 0 ;
423+ struct commit_info ci ;
424+ get_commit_info (ent -> suspect -> commit , & ci , 1 );
425+
426+ while (i < colorfield_nr && ci .author_time > colorfield [i ].hop )
427+ i ++ ;
428+
429+ * dest_color = colorfield [i ].col ;
430+ }
431+
373432static void emit_other (struct blame_scoreboard * sb , struct blame_entry * ent , int opt )
374433{
375434 int cnt ;
@@ -378,12 +437,19 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
378437 struct commit_info ci ;
379438 char hex [GIT_MAX_HEXSZ + 1 ];
380439 int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP );
381- const char * color = NULL , * reset = NULL ;
440+ const char * default_color = NULL , * color = NULL , * reset = NULL ;
382441
383442 get_commit_info (suspect -> commit , & ci , 1 );
384443 oid_to_hex_r (hex , & suspect -> commit -> object .oid );
385444
386445 cp = blame_nth_line (sb , ent -> lno );
446+
447+ if (opt & OUTPUT_SHOW_AGE_WITH_COLOR ) {
448+ determine_line_heat (ent , & default_color );
449+ color = default_color ;
450+ reset = GIT_COLOR_RESET ;
451+ }
452+
387453 for (cnt = 0 ; cnt < ent -> num_lines ; cnt ++ ) {
388454 char ch ;
389455 int length = (opt & OUTPUT_LONG_OBJECT_NAME ) ? GIT_SHA1_HEXSZ : abbrev ;
@@ -393,8 +459,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
393459 color = repeated_meta_color ;
394460 reset = GIT_COLOR_RESET ;
395461 } else {
396- color = NULL ;
397- reset = NULL ;
462+ color = default_color ? default_color : NULL ;
463+ reset = default_color ? GIT_COLOR_RESET : NULL ;
398464 }
399465 }
400466 if (color )
@@ -631,6 +697,10 @@ static int git_blame_config(const char *var, const char *value, void *cb)
631697 value );
632698 return 0 ;
633699 }
700+ if (!strcmp (var , "color.blame.highlightrecent" )) {
701+ parse_color_fields (value );
702+ return 0 ;
703+ }
634704
635705 if (git_diff_heuristic_config (var , value , cb ) < 0 )
636706 return -1 ;
@@ -706,6 +776,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
706776 OPT_BIT ('e' , "show-email" , & output_option , N_ ("Show author email instead of name (Default: off)" ), OUTPUT_SHOW_EMAIL ),
707777 OPT_BIT ('w' , NULL , & xdl_opts , N_ ("Ignore whitespace differences" ), XDF_IGNORE_WHITESPACE ),
708778 OPT_BIT (0 , "color-lines" , & output_option , N_ ("color redundant metadata from previous line differently" ), OUTPUT_COLOR_LINE ),
779+ OPT_BIT (0 , "color-by-age" , & output_option , N_ ("color lines by age" ), OUTPUT_SHOW_AGE_WITH_COLOR ),
709780
710781 /*
711782 * The following two options are parsed by parse_revision_opt()
@@ -730,6 +801,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
730801 unsigned int range_i ;
731802 long anchor ;
732803
804+ setup_default_color_by_age ();
733805 git_config (git_blame_config , & output_option );
734806 init_revisions (& revs , NULL );
735807 revs .date_mode = blame_date_mode ;
@@ -972,7 +1044,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
9721044 strcpy (repeated_meta_color , GIT_COLOR_CYAN );
9731045 }
9741046 if (output_option & OUTPUT_ANNOTATE_COMPAT )
975- output_option &= ~OUTPUT_COLOR_LINE ;
1047+ output_option &= ~( OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR ) ;
9761048
9771049 output (& sb , output_option );
9781050 free ((void * )sb .final_buf );
0 commit comments