1818#include "cache-tree.h"
1919#include "path-list.h"
2020#include "mailmap.h"
21+ #include "parse-options.h"
2122
22- static char blame_usage [] =
23- "git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
24- " -c Use the same output mode as git-annotate (Default: off)\n"
25- " -b Show blank SHA-1 for boundary commits (Default: off)\n"
26- " -l Show long commit SHA1 (Default: off)\n"
27- " --root Do not treat root commits as boundaries (Default: off)\n"
28- " -t Show raw timestamp (Default: off)\n"
29- " -f, --show-name Show original filename (Default: auto)\n"
30- " -n, --show-number Show original linenumber (Default: off)\n"
31- " -s Suppress author name and timestamp (Default: off)\n"
32- " -p, --porcelain Show in a format designed for machine consumption\n"
33- " -w Ignore whitespace differences\n"
34- " -L n,m Process only line range n,m, counting from 1\n"
35- " -M, -C Find line movements within and across files\n"
36- " --incremental Show blame entries as we find them, incrementally\n"
37- " --contents file Use <file>'s contents as the final image\n"
38- " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n" ;
23+ static char blame_usage [] = "git-blame [options] [rev-opts] [rev] [--] file" ;
24+
25+ static const char * blame_opt_usage [] = {
26+ blame_usage ,
27+ "" ,
28+ "[rev-opts] are documented in git-rev-parse(1)" ,
29+ NULL
30+ };
3931
4032static int longest_file ;
4133static int longest_author ;
@@ -2219,105 +2211,130 @@ static const char *prepare_initial(struct scoreboard *sb)
22192211 return final_commit_name ;
22202212}
22212213
2214+ static int blame_copy_callback (const struct option * option , const char * arg , int unset )
2215+ {
2216+ int * opt = option -> value ;
2217+
2218+ /*
2219+ * -C enables copy from removed files;
2220+ * -C -C enables copy from existing files, but only
2221+ * when blaming a new file;
2222+ * -C -C -C enables copy from existing files for
2223+ * everybody
2224+ */
2225+ if (* opt & PICKAXE_BLAME_COPY_HARDER )
2226+ * opt |= PICKAXE_BLAME_COPY_HARDEST ;
2227+ if (* opt & PICKAXE_BLAME_COPY )
2228+ * opt |= PICKAXE_BLAME_COPY_HARDER ;
2229+ * opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE ;
2230+
2231+ if (arg )
2232+ blame_copy_score = parse_score (arg );
2233+ return 0 ;
2234+ }
2235+
2236+ static int blame_move_callback (const struct option * option , const char * arg , int unset )
2237+ {
2238+ int * opt = option -> value ;
2239+
2240+ * opt |= PICKAXE_BLAME_MOVE ;
2241+
2242+ if (arg )
2243+ blame_move_score = parse_score (arg );
2244+ return 0 ;
2245+ }
2246+
2247+ static int blame_bottomtop_callback (const struct option * option , const char * arg , int unset )
2248+ {
2249+ const char * * bottomtop = option -> value ;
2250+ if (!arg )
2251+ return -1 ;
2252+ if (* bottomtop )
2253+ die ("More than one '-L n,m' option given" );
2254+ * bottomtop = arg ;
2255+ return 0 ;
2256+ }
2257+
22222258int cmd_blame (int argc , const char * * argv , const char * prefix )
22232259{
22242260 struct rev_info revs ;
22252261 const char * path ;
22262262 struct scoreboard sb ;
22272263 struct origin * o ;
22282264 struct blame_entry * ent ;
2229- int i , seen_dashdash , unk , opt ;
2265+ int i , seen_dashdash , unk ;
22302266 long bottom , top , lno ;
2231- int output_option = 0 ;
2232- int show_stats = 0 ;
2233- const char * revs_file = NULL ;
22342267 const char * final_commit_name = NULL ;
22352268 enum object_type type ;
2236- const char * bottomtop = NULL ;
2237- const char * contents_from = NULL ;
2269+
2270+ static const char * bottomtop = NULL ;
2271+ static int output_option = 0 , opt = 0 ;
2272+ static int show_stats = 0 ;
2273+ static const char * revs_file = NULL ;
2274+ static const char * contents_from = NULL ;
2275+ static const struct option options [] = {
2276+ OPT_BOOLEAN (0 , "incremental" , & incremental , "Show blame entries as we find them, incrementally" ),
2277+ OPT_BOOLEAN ('b' , NULL , & blank_boundary , "Show blank SHA-1 for boundary commits (Default: off)" ),
2278+ OPT_BOOLEAN (0 , "root" , & show_root , "Do not treat root commits as boundaries (Default: off)" ),
2279+ OPT_BOOLEAN (0 , "show-stats" , & show_stats , "Show work cost statistics" ),
2280+ OPT_BIT (0 , "score-debug" , & output_option , "Show output score for blame entries" , OUTPUT_SHOW_SCORE ),
2281+ OPT_BIT ('f' , "show-name" , & output_option , "Show original filename (Default: auto)" , OUTPUT_SHOW_NAME ),
2282+ OPT_BIT ('n' , "show-number" , & output_option , "Show original linenumber (Default: off)" , OUTPUT_SHOW_NUMBER ),
2283+ OPT_BIT ('p' , "porcelain" , & output_option , "Show in a format designed for machine consumption" , OUTPUT_PORCELAIN ),
2284+ OPT_BIT ('c' , NULL , & output_option , "Use the same output mode as git-annotate (Default: off)" , OUTPUT_ANNOTATE_COMPAT ),
2285+ OPT_BIT ('t' , NULL , & output_option , "Show raw timestamp (Default: off)" , OUTPUT_RAW_TIMESTAMP ),
2286+ OPT_BIT ('l' , NULL , & output_option , "Show long commit SHA1 (Default: off)" , OUTPUT_LONG_OBJECT_NAME ),
2287+ OPT_BIT ('s' , NULL , & output_option , "Suppress author name and timestamp (Default: off)" , OUTPUT_NO_AUTHOR ),
2288+ OPT_BIT ('w' , NULL , & xdl_opts , "Ignore whitespace differences" , XDF_IGNORE_WHITESPACE ),
2289+ OPT_STRING ('S' , NULL , & revs_file , "file" , "Use revisions from <file> instead of calling git-rev-list" ),
2290+ OPT_STRING (0 , "contents" , & contents_from , "file" , "Use <file>'s contents as the final image" ),
2291+ { OPTION_CALLBACK , 'C' , NULL , & opt , "score" , "Find line copies within and across files" , PARSE_OPT_OPTARG , blame_copy_callback },
2292+ { OPTION_CALLBACK , 'M' , NULL , & opt , "score" , "Find line movements within and across files" , PARSE_OPT_OPTARG , blame_move_callback },
2293+ OPT_CALLBACK ('L' , NULL , & bottomtop , "n,m" , "Process only line range n,m, counting from 1" , blame_bottomtop_callback ),
2294+ OPT_END ()
2295+ };
2296+
2297+ struct parse_opt_ctx_t ctx ;
22382298
22392299 cmd_is_annotate = !strcmp (argv [0 ], "annotate" );
22402300
22412301 git_config (git_blame_config , NULL );
2302+ init_revisions (& revs , NULL );
22422303 save_commit_buffer = 0 ;
22432304
2244- opt = 0 ;
2305+ parse_options_start (& ctx , argc , argv , PARSE_OPT_KEEP_DASHDASH |
2306+ PARSE_OPT_KEEP_ARGV0 );
2307+ for (;;) {
2308+ int n ;
2309+
2310+ switch (parse_options_step (& ctx , options , blame_opt_usage )) {
2311+ case PARSE_OPT_HELP :
2312+ exit (129 );
2313+ case PARSE_OPT_DONE :
2314+ goto parse_done ;
2315+ }
2316+
2317+ if (!strcmp (ctx .argv [0 ], "--reverse" )) {
2318+ ctx .argv [0 ] = "--children" ;
2319+ reverse = 1 ;
2320+ }
2321+ n = handle_revision_opt (& revs , ctx .argc , ctx .argv ,
2322+ & ctx .cpidx , ctx .out );
2323+ if (n <= 0 ) {
2324+ error ("unknown option `%s'" , ctx .argv [0 ]);
2325+ usage_with_options (blame_opt_usage , options );
2326+ }
2327+ ctx .argv += n ;
2328+ ctx .argc -= n ;
2329+ }
2330+ parse_done :
2331+ argc = parse_options_end (& ctx );
2332+
22452333 seen_dashdash = 0 ;
22462334 for (unk = i = 1 ; i < argc ; i ++ ) {
22472335 const char * arg = argv [i ];
22482336 if (* arg != '-' )
22492337 break ;
2250- else if (!strcmp ("-b" , arg ))
2251- blank_boundary = 1 ;
2252- else if (!strcmp ("--root" , arg ))
2253- show_root = 1 ;
2254- else if (!strcmp ("--reverse" , arg )) {
2255- argv [unk ++ ] = "--children" ;
2256- reverse = 1 ;
2257- }
2258- else if (!strcmp (arg , "--show-stats" ))
2259- show_stats = 1 ;
2260- else if (!strcmp ("-c" , arg ))
2261- output_option |= OUTPUT_ANNOTATE_COMPAT ;
2262- else if (!strcmp ("-t" , arg ))
2263- output_option |= OUTPUT_RAW_TIMESTAMP ;
2264- else if (!strcmp ("-l" , arg ))
2265- output_option |= OUTPUT_LONG_OBJECT_NAME ;
2266- else if (!strcmp ("-s" , arg ))
2267- output_option |= OUTPUT_NO_AUTHOR ;
2268- else if (!strcmp ("-w" , arg ))
2269- xdl_opts |= XDF_IGNORE_WHITESPACE ;
2270- else if (!strcmp ("-S" , arg ) && ++ i < argc )
2271- revs_file = argv [i ];
2272- else if (!prefixcmp (arg , "-M" )) {
2273- opt |= PICKAXE_BLAME_MOVE ;
2274- blame_move_score = parse_score (arg + 2 );
2275- }
2276- else if (!prefixcmp (arg , "-C" )) {
2277- /*
2278- * -C enables copy from removed files;
2279- * -C -C enables copy from existing files, but only
2280- * when blaming a new file;
2281- * -C -C -C enables copy from existing files for
2282- * everybody
2283- */
2284- if (opt & PICKAXE_BLAME_COPY_HARDER )
2285- opt |= PICKAXE_BLAME_COPY_HARDEST ;
2286- if (opt & PICKAXE_BLAME_COPY )
2287- opt |= PICKAXE_BLAME_COPY_HARDER ;
2288- opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE ;
2289- blame_copy_score = parse_score (arg + 2 );
2290- }
2291- else if (!prefixcmp (arg , "-L" )) {
2292- if (!arg [2 ]) {
2293- if (++ i >= argc )
2294- usage (blame_usage );
2295- arg = argv [i ];
2296- }
2297- else
2298- arg += 2 ;
2299- if (bottomtop )
2300- die ("More than one '-L n,m' option given" );
2301- bottomtop = arg ;
2302- }
2303- else if (!strcmp ("--contents" , arg )) {
2304- if (++ i >= argc )
2305- usage (blame_usage );
2306- contents_from = argv [i ];
2307- }
2308- else if (!strcmp ("--incremental" , arg ))
2309- incremental = 1 ;
2310- else if (!strcmp ("--score-debug" , arg ))
2311- output_option |= OUTPUT_SHOW_SCORE ;
2312- else if (!strcmp ("-f" , arg ) ||
2313- !strcmp ("--show-name" , arg ))
2314- output_option |= OUTPUT_SHOW_NAME ;
2315- else if (!strcmp ("-n" , arg ) ||
2316- !strcmp ("--show-number" , arg ))
2317- output_option |= OUTPUT_SHOW_NUMBER ;
2318- else if (!strcmp ("-p" , arg ) ||
2319- !strcmp ("--porcelain" , arg ))
2320- output_option |= OUTPUT_PORCELAIN ;
23212338 else if (!strcmp ("--" , arg )) {
23222339 seen_dashdash = 1 ;
23232340 i ++ ;
@@ -2364,16 +2381,16 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
23642381 if (seen_dashdash ) {
23652382 /* (1) */
23662383 if (argc <= i )
2367- usage ( blame_usage );
2384+ usage_with_options ( blame_opt_usage , options );
23682385 path = add_prefix (prefix , argv [i ]);
23692386 if (i + 1 == argc - 1 ) {
23702387 if (unk != 1 )
2371- usage ( blame_usage );
2388+ usage_with_options ( blame_opt_usage , options );
23722389 argv [unk ++ ] = argv [i + 1 ];
23732390 }
23742391 else if (i + 1 != argc )
23752392 /* garbage at end */
2376- usage ( blame_usage );
2393+ usage_with_options ( blame_opt_usage , options );
23772394 }
23782395 else {
23792396 int j ;
@@ -2383,15 +2400,15 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
23832400 if (seen_dashdash ) {
23842401 /* (2) */
23852402 if (seen_dashdash + 1 != argc - 1 )
2386- usage ( blame_usage );
2403+ usage_with_options ( blame_opt_usage , options );
23872404 path = add_prefix (prefix , argv [seen_dashdash + 1 ]);
23882405 for (j = i ; j < seen_dashdash ; j ++ )
23892406 argv [unk ++ ] = argv [j ];
23902407 }
23912408 else {
23922409 /* (3) */
23932410 if (argc <= i )
2394- usage ( blame_usage );
2411+ usage_with_options ( blame_opt_usage , options );
23952412 path = add_prefix (prefix , argv [i ]);
23962413 if (i + 1 == argc - 1 ) {
23972414 final_commit_name = argv [i + 1 ];
@@ -2405,7 +2422,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
24052422 }
24062423 }
24072424 else if (i != argc - 1 )
2408- usage ( blame_usage ); /* garbage at end */
2425+ usage_with_options ( blame_opt_usage , options );
24092426
24102427 setup_work_tree ();
24112428 if (!has_path_in_work_tree (path ))
@@ -2424,7 +2441,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
24242441 argv [unk ++ ] = "--" ; /* terminate the rev name */
24252442 argv [unk ] = NULL ;
24262443
2427- init_revisions (& revs , NULL );
24282444 setup_revisions (unk , argv , & revs , NULL );
24292445 memset (& sb , 0 , sizeof (sb ));
24302446
0 commit comments