7
7
8
8
#include "cache.h"
9
9
#include "config.h"
10
+ #include "color.h"
10
11
#include "builtin.h"
11
12
#include "commit.h"
12
13
#include "diff.h"
23
24
#include "dir.h"
24
25
#include "progress.h"
25
26
#include "blame.h"
27
+ #include "string-list.h"
26
28
27
29
static char blame_usage [] = N_ ("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>" );
28
30
@@ -46,6 +48,8 @@ static int xdl_opts;
46
48
static int abbrev = -1 ;
47
49
static int no_whole_file_rename ;
48
50
static int show_progress ;
51
+ static char repeated_meta_color [COLOR_MAXLEN ];
52
+ static int coloring_mode ;
49
53
50
54
static struct date_mode blame_date_mode = { DATE_ISO8601 };
51
55
static size_t blame_date_width ;
@@ -316,10 +320,12 @@ static const char *format_time(timestamp_t time, const char *tz_str,
316
320
#define OUTPUT_PORCELAIN 010
317
321
#define OUTPUT_SHOW_NAME 020
318
322
#define OUTPUT_SHOW_NUMBER 040
319
- #define OUTPUT_SHOW_SCORE 0100
320
- #define OUTPUT_NO_AUTHOR 0200
323
+ #define OUTPUT_SHOW_SCORE 0100
324
+ #define OUTPUT_NO_AUTHOR 0200
321
325
#define OUTPUT_SHOW_EMAIL 0400
322
- #define OUTPUT_LINE_PORCELAIN 01000
326
+ #define OUTPUT_LINE_PORCELAIN 01000
327
+ #define OUTPUT_COLOR_LINE 02000
328
+ #define OUTPUT_SHOW_AGE_WITH_COLOR 04000
323
329
324
330
static void emit_porcelain_details (struct blame_origin * suspect , int repeat )
325
331
{
@@ -367,6 +373,63 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
367
373
putchar ('\n' );
368
374
}
369
375
376
+ static struct color_field {
377
+ timestamp_t hop ;
378
+ char col [COLOR_MAXLEN ];
379
+ } * colorfield ;
380
+ static int colorfield_nr , colorfield_alloc ;
381
+
382
+ static void parse_color_fields (const char * s )
383
+ {
384
+ struct string_list l = STRING_LIST_INIT_DUP ;
385
+ struct string_list_item * item ;
386
+ enum { EXPECT_DATE , EXPECT_COLOR } next = EXPECT_COLOR ;
387
+
388
+ colorfield_nr = 0 ;
389
+
390
+ /* Ideally this would be stripped and split at the same time? */
391
+ string_list_split (& l , s , ',' , -1 );
392
+ ALLOC_GROW (colorfield , colorfield_nr + 1 , colorfield_alloc );
393
+
394
+ for_each_string_list_item (item , & l ) {
395
+ switch (next ) {
396
+ case EXPECT_DATE :
397
+ colorfield [colorfield_nr ].hop = approxidate (item -> string );
398
+ next = EXPECT_COLOR ;
399
+ colorfield_nr ++ ;
400
+ ALLOC_GROW (colorfield , colorfield_nr + 1 , colorfield_alloc );
401
+ break ;
402
+ case EXPECT_COLOR :
403
+ if (color_parse (item -> string , colorfield [colorfield_nr ].col ))
404
+ die (_ ("expecting a color: %s" ), item -> string );
405
+ next = EXPECT_DATE ;
406
+ break ;
407
+ }
408
+ }
409
+
410
+ if (next == EXPECT_COLOR )
411
+ die (_ ("must end with a color" ));
412
+
413
+ colorfield [colorfield_nr ].hop = TIME_MAX ;
414
+ }
415
+
416
+ static void setup_default_color_by_age (void )
417
+ {
418
+ parse_color_fields ("blue,12 month ago,white,1 month ago,red" );
419
+ }
420
+
421
+ static void determine_line_heat (struct blame_entry * ent , const char * * dest_color )
422
+ {
423
+ int i = 0 ;
424
+ struct commit_info ci ;
425
+ get_commit_info (ent -> suspect -> commit , & ci , 1 );
426
+
427
+ while (i < colorfield_nr && ci .author_time > colorfield [i ].hop )
428
+ i ++ ;
429
+
430
+ * dest_color = colorfield [i ].col ;
431
+ }
432
+
370
433
static void emit_other (struct blame_scoreboard * sb , struct blame_entry * ent , int opt )
371
434
{
372
435
int cnt ;
@@ -375,15 +438,35 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
375
438
struct commit_info ci ;
376
439
char hex [GIT_MAX_HEXSZ + 1 ];
377
440
int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP );
441
+ const char * default_color = NULL , * color = NULL , * reset = NULL ;
378
442
379
443
get_commit_info (suspect -> commit , & ci , 1 );
380
444
oid_to_hex_r (hex , & suspect -> commit -> object .oid );
381
445
382
446
cp = blame_nth_line (sb , ent -> lno );
447
+
448
+ if (opt & OUTPUT_SHOW_AGE_WITH_COLOR ) {
449
+ determine_line_heat (ent , & default_color );
450
+ color = default_color ;
451
+ reset = GIT_COLOR_RESET ;
452
+ }
453
+
383
454
for (cnt = 0 ; cnt < ent -> num_lines ; cnt ++ ) {
384
455
char ch ;
385
456
int length = (opt & OUTPUT_LONG_OBJECT_NAME ) ? GIT_SHA1_HEXSZ : abbrev ;
386
457
458
+ if (opt & OUTPUT_COLOR_LINE ) {
459
+ if (cnt > 0 ) {
460
+ color = repeated_meta_color ;
461
+ reset = GIT_COLOR_RESET ;
462
+ } else {
463
+ color = default_color ? default_color : NULL ;
464
+ reset = default_color ? GIT_COLOR_RESET : NULL ;
465
+ }
466
+ }
467
+ if (color )
468
+ fputs (color , stdout );
469
+
387
470
if (suspect -> commit -> object .flags & UNINTERESTING ) {
388
471
if (blank_boundary )
389
472
memset (hex , ' ' , length );
@@ -433,6 +516,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
433
516
printf (" %*d) " ,
434
517
max_digits , ent -> lno + 1 + cnt );
435
518
}
519
+ if (reset )
520
+ fputs (reset , stdout );
436
521
do {
437
522
ch = * cp ++ ;
438
523
putchar (ch );
@@ -607,6 +692,30 @@ static int git_blame_config(const char *var, const char *value, void *cb)
607
692
parse_date_format (value , & blame_date_mode );
608
693
return 0 ;
609
694
}
695
+ if (!strcmp (var , "color.blame.repeatedlines" )) {
696
+ if (color_parse_mem (value , strlen (value ), repeated_meta_color ))
697
+ warning (_ ("invalid color '%s' in color.blame.repeatedLines" ),
698
+ value );
699
+ return 0 ;
700
+ }
701
+ if (!strcmp (var , "color.blame.highlightrecent" )) {
702
+ parse_color_fields (value );
703
+ return 0 ;
704
+ }
705
+
706
+ if (!strcmp (var , "blame.coloring" )) {
707
+ if (!strcmp (value , "repeatedLines" )) {
708
+ coloring_mode |= OUTPUT_COLOR_LINE ;
709
+ } else if (!strcmp (value , "highlightRecent" )) {
710
+ coloring_mode |= OUTPUT_SHOW_AGE_WITH_COLOR ;
711
+ } else if (!strcmp (value , "none" )) {
712
+ coloring_mode &= ~(OUTPUT_COLOR_LINE |
713
+ OUTPUT_SHOW_AGE_WITH_COLOR );
714
+ } else {
715
+ warning (_ ("invalid value for blame.coloring" ));
716
+ return 0 ;
717
+ }
718
+ }
610
719
611
720
if (git_diff_heuristic_config (var , value , cb ) < 0 )
612
721
return -1 ;
@@ -690,6 +799,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
690
799
OPT_BIT ('s' , NULL , & output_option , N_ ("Suppress author name and timestamp (Default: off)" ), OUTPUT_NO_AUTHOR ),
691
800
OPT_BIT ('e' , "show-email" , & output_option , N_ ("Show author email instead of name (Default: off)" ), OUTPUT_SHOW_EMAIL ),
692
801
OPT_BIT ('w' , NULL , & xdl_opts , N_ ("Ignore whitespace differences" ), XDF_IGNORE_WHITESPACE ),
802
+ OPT_BIT (0 , "color-lines" , & output_option , N_ ("color redundant metadata from previous line differently" ), OUTPUT_COLOR_LINE ),
803
+ OPT_BIT (0 , "color-by-age" , & output_option , N_ ("color lines by age" ), OUTPUT_SHOW_AGE_WITH_COLOR ),
693
804
694
805
/*
695
806
* The following two options are parsed by parse_revision_opt()
@@ -714,6 +825,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
714
825
unsigned int range_i ;
715
826
long anchor ;
716
827
828
+ setup_default_color_by_age ();
717
829
git_config (git_blame_config , & output_option );
718
830
init_revisions (& revs , NULL );
719
831
revs .date_mode = blame_date_mode ;
@@ -949,8 +1061,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
949
1061
950
1062
blame_coalesce (& sb );
951
1063
952
- if (!(output_option & OUTPUT_PORCELAIN ))
1064
+ if (!(output_option & (OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR )))
1065
+ output_option |= coloring_mode ;
1066
+
1067
+ if (!(output_option & OUTPUT_PORCELAIN )) {
953
1068
find_alignment (& sb , & output_option );
1069
+ if (!* repeated_meta_color &&
1070
+ (output_option & OUTPUT_COLOR_LINE ))
1071
+ strcpy (repeated_meta_color , GIT_COLOR_CYAN );
1072
+ }
1073
+ if (output_option & OUTPUT_ANNOTATE_COMPAT )
1074
+ output_option &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR );
954
1075
955
1076
output (& sb , output_option );
956
1077
free ((void * )sb .final_buf );
0 commit comments