scan reflogs independently from refs
[gitweb.git] / builtin-blame.c
index dc3ffeaff82377262f0ae47993cf0883b2552092..7a58ee303f43668aa513181d91c9f1e412b0ffdf 100644 (file)
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "quote.h"
 #include "xdiff-interface.h"
 
-#include <time.h>
-#include <sys/time.h>
-#include <regex.h>
-
 static char blame_usage[] =
 "git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
 "  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
 "  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  --root              Do not treat root commits as boundaries (Default: off)\n"
 "  -t, --time          Show raw timestamp (Default: off)\n"
 "  -f, --show-name     Show original filename (Default: auto)\n"
 "  -n, --show-number   Show original linenumber (Default: off)\n"
 "  -p, --porcelain     Show in a format designed for machine consumption\n"
 "  -L n,m              Process only line range n,m, counting from 1\n"
 "  -M, -C              Find line movements within and across files\n"
+"  --incremental       Show blame entries as we find them, incrementally\n"
 "  -S revs-file        Use revisions from revs-file instead of calling git-rev-list\n";
 
 static int longest_file;
@@ -36,6 +36,9 @@ static int longest_author;
 static int max_orig_digits;
 static int max_digits;
 static int max_score_digits;
+static int show_root;
+static int blank_boundary;
+static int incremental;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -1069,64 +1072,6 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                origin_decref(parent_origin[i]);
 }
 
-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
-{
-       while (1) {
-               struct blame_entry *ent;
-               struct commit *commit;
-               struct origin *suspect = NULL;
-
-               /* find one suspect to break down */
-               for (ent = sb->ent; !suspect && ent; ent = ent->next)
-                       if (!ent->guilty)
-                               suspect = ent->suspect;
-               if (!suspect)
-                       return; /* all done */
-
-               origin_incref(suspect);
-               commit = suspect->commit;
-               if (!commit->object.parsed)
-                       parse_commit(commit);
-               if (!(commit->object.flags & UNINTERESTING) &&
-                   !(revs->max_age != -1 && commit->date  < revs->max_age))
-                       pass_blame(sb, suspect, opt);
-
-               /* Take responsibility for the remaining entries */
-               for (ent = sb->ent; ent; ent = ent->next)
-                       if (!cmp_suspect(ent->suspect, suspect))
-                               ent->guilty = 1;
-               origin_decref(suspect);
-
-               if (DEBUG) /* sanity */
-                       sanity_check_refcnt(sb);
-       }
-}
-
-static const char *format_time(unsigned long time, const char *tz_str,
-                              int show_raw_time)
-{
-       static char time_buf[128];
-       time_t t = time;
-       int minutes, tz;
-       struct tm *tm;
-
-       if (show_raw_time) {
-               sprintf(time_buf, "%lu %s", time, tz_str);
-               return time_buf;
-       }
-
-       tz = atoi(tz_str);
-       minutes = tz < 0 ? -tz : tz;
-       minutes = (minutes / 100)*60 + (minutes % 100);
-       minutes = tz < 0 ? -minutes : minutes;
-       t = time + minutes * 60;
-       tm = gmtime(&t);
-
-       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
-       strcat(time_buf, tz_str);
-       return time_buf;
-}
-
 struct commit_info
 {
        char *author;
@@ -1237,6 +1182,110 @@ static void get_commit_info(struct commit *commit,
        summary_buf[len] = 0;
 }
 
+static void write_filename_info(const char *path)
+{
+       printf("filename ");
+       write_name_quoted(NULL, 0, path, 1, stdout);
+       putchar('\n');
+}
+
+static void found_guilty_entry(struct blame_entry *ent)
+{
+       if (ent->guilty)
+               return;
+       ent->guilty = 1;
+       if (incremental) {
+               struct origin *suspect = ent->suspect;
+
+               printf("%s %d %d %d\n",
+                      sha1_to_hex(suspect->commit->object.sha1),
+                      ent->s_lno + 1, ent->lno + 1, ent->num_lines);
+               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+                       struct commit_info ci;
+                       suspect->commit->object.flags |= METAINFO_SHOWN;
+                       get_commit_info(suspect->commit, &ci, 1);
+                       printf("author %s\n", ci.author);
+                       printf("author-mail %s\n", ci.author_mail);
+                       printf("author-time %lu\n", ci.author_time);
+                       printf("author-tz %s\n", ci.author_tz);
+                       printf("committer %s\n", ci.committer);
+                       printf("committer-mail %s\n", ci.committer_mail);
+                       printf("committer-time %lu\n", ci.committer_time);
+                       printf("committer-tz %s\n", ci.committer_tz);
+                       printf("summary %s\n", ci.summary);
+                       if (suspect->commit->object.flags & UNINTERESTING)
+                               printf("boundary\n");
+               }
+               write_filename_info(suspect->path);
+       }
+}
+
+static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+{
+       while (1) {
+               struct blame_entry *ent;
+               struct commit *commit;
+               struct origin *suspect = NULL;
+
+               /* find one suspect to break down */
+               for (ent = sb->ent; !suspect && ent; ent = ent->next)
+                       if (!ent->guilty)
+                               suspect = ent->suspect;
+               if (!suspect)
+                       return; /* all done */
+
+               origin_incref(suspect);
+               commit = suspect->commit;
+               if (!commit->object.parsed)
+                       parse_commit(commit);
+               if (!(commit->object.flags & UNINTERESTING) &&
+                   !(revs->max_age != -1 && commit->date  < revs->max_age))
+                       pass_blame(sb, suspect, opt);
+               else {
+                       commit->object.flags |= UNINTERESTING;
+                       if (commit->object.parsed)
+                               mark_parents_uninteresting(commit);
+               }
+               /* treat root commit as boundary */
+               if (!commit->parents && !show_root)
+                       commit->object.flags |= UNINTERESTING;
+
+               /* Take responsibility for the remaining entries */
+               for (ent = sb->ent; ent; ent = ent->next)
+                       if (!cmp_suspect(ent->suspect, suspect))
+                               found_guilty_entry(ent);
+               origin_decref(suspect);
+
+               if (DEBUG) /* sanity */
+                       sanity_check_refcnt(sb);
+       }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+                              int show_raw_time)
+{
+       static char time_buf[128];
+       time_t t = time;
+       int minutes, tz;
+       struct tm *tm;
+
+       if (show_raw_time) {
+               sprintf(time_buf, "%lu %s", time, tz_str);
+               return time_buf;
+       }
+
+       tz = atoi(tz_str);
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+
+       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+       strcat(time_buf, tz_str);
+       return time_buf;
+}
+
 #define OUTPUT_ANNOTATE_COMPAT 001
 #define OUTPUT_LONG_OBJECT_NAME        002
 #define OUTPUT_RAW_TIMESTAMP   004
@@ -1271,11 +1320,13 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
                printf("committer-mail %s\n", ci.committer_mail);
                printf("committer-time %lu\n", ci.committer_time);
                printf("committer-tz %s\n", ci.committer_tz);
-               printf("filename %s\n", suspect->path);
+               write_filename_info(suspect->path);
                printf("summary %s\n", ci.summary);
+               if (suspect->commit->object.flags & UNINTERESTING)
+                       printf("boundary\n");
        }
        else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
-               printf("filename %s\n", suspect->path);
+               write_filename_info(suspect->path);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@ -1308,8 +1359,18 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
+               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
+
+               if (suspect->commit->object.flags & UNINTERESTING) {
+                       if (!blank_boundary) {
+                               length--;
+                               putchar('^');
+                       }
+                       else
+                               memset(hex, ' ', length);
+               }
 
-               printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);
+               printf("%.*s", length, hex);
                if (opt & OUTPUT_ANNOTATE_COMPAT)
                        printf("\t(%10s\t%10s\t%d)", ci.author,
                               format_time(ci.author_time, ci.author_tz,
@@ -1626,6 +1687,19 @@ static void prepare_blame_range(struct scoreboard *sb,
                usage(blame_usage);
 }
 
+static int git_blame_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "blame.showroot")) {
+               show_root = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.blankboundary")) {
+               blank_boundary = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -1641,6 +1715,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        char type[10];
        const char *bottomtop = NULL;
 
+       git_config(git_blame_config);
        save_commit_buffer = 0;
 
        opt = 0;
@@ -1649,6 +1724,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                const char *arg = argv[i];
                if (*arg != '-')
                        break;
+               else if (!strcmp("-b", arg))
+                       blank_boundary = 1;
+               else if (!strcmp("--root", arg))
+                       show_root = 1;
                else if (!strcmp("-c", arg))
                        output_option |= OUTPUT_ANNOTATE_COMPAT;
                else if (!strcmp("-t", arg))
@@ -1679,6 +1758,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                                die("More than one '-L n,m' option given");
                        bottomtop = arg;
                }
+               else if (!strcmp("--incremental", arg))
+                       incremental = 1;
                else if (!strcmp("--score-debug", arg))
                        output_option |= OUTPUT_SHOW_SCORE;
                else if (!strcmp("-f", arg) ||
@@ -1869,6 +1950,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        assign_blame(&sb, &revs, opt);
 
+       if (incremental)
+               return 0;
+
        coalesce(&sb);
 
        if (!(output_option & OUTPUT_PORCELAIN))