Merge branches zj/decimal-width, zj/term-columns and jc/diff-stat-scaler
[gitweb.git] / builtin / blame.c
index f6b03f750a0b69ef835caa0a2e4988f9a08abb85..f028e8aec82d0936951418f936123041c3b516ad 100644 (file)
@@ -41,6 +41,7 @@ static int reverse;
 static int blank_boundary;
 static int incremental;
 static int xdl_opts;
+static int abbrev = -1;
 
 static enum date_mode blame_date_mode = DATE_ISO8601;
 static size_t blame_date_width;
@@ -1377,7 +1378,7 @@ static void get_ac_line(const char *inbuf, const char *what,
        timepos = tmp;
 
        *tmp = 0;
-       while (person < tmp && *tmp != ' ')
+       while (person < tmp && !(*tmp == ' ' && tmp[1] == '<'))
                tmp--;
        if (tmp <= person)
                return;
@@ -1483,13 +1484,14 @@ static void write_filename_info(const char *path)
 /*
  * Porcelain/Incremental format wants to show a lot of details per
  * commit.  Instead of repeating this every line, emit it only once,
- * the first time each commit appears in the output.
+ * the first time each commit appears in the output (unless the
+ * user has specifically asked for us to repeat).
  */
-static int emit_one_suspect_detail(struct origin *suspect)
+static int emit_one_suspect_detail(struct origin *suspect, int repeat)
 {
        struct commit_info ci;
 
-       if (suspect->commit->object.flags & METAINFO_SHOWN)
+       if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
                return 0;
 
        suspect->commit->object.flags |= METAINFO_SHOWN;
@@ -1528,7 +1530,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                printf("%s %d %d %d\n",
                       sha1_to_hex(suspect->commit->object.sha1),
                       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
-               emit_one_suspect_detail(suspect);
+               emit_one_suspect_detail(suspect, 0);
                write_filename_info(suspect->path);
                maybe_flush_or_die(stdout, "stdout");
        }
@@ -1596,7 +1598,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
        int tz;
 
        if (show_raw_time) {
-               sprintf(time_buf, "%lu %s", time, tz_str);
+               snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
        }
        else {
                tz = atoi(tz_str);
@@ -1617,9 +1619,19 @@ static const char *format_time(unsigned long time, const char *tz_str,
 #define OUTPUT_SHOW_SCORE      0100
 #define OUTPUT_NO_AUTHOR       0200
 #define OUTPUT_SHOW_EMAIL      0400
+#define OUTPUT_LINE_PORCELAIN 01000
+
+static void emit_porcelain_details(struct origin *suspect, int repeat)
+{
+       if (emit_one_suspect_detail(suspect, repeat) ||
+           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
+               write_filename_info(suspect->path);
+}
 
-static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
+                          int opt)
 {
+       int repeat = opt & OUTPUT_LINE_PORCELAIN;
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
@@ -1632,17 +1644,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
               ent->s_lno + 1,
               ent->lno + 1,
               ent->num_lines);
-       if (emit_one_suspect_detail(suspect) ||
-           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
-               write_filename_info(suspect->path);
+       emit_porcelain_details(suspect, repeat);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
-               if (cnt)
+               if (cnt) {
                        printf("%s %d %d\n", hex,
                               ent->s_lno + 1 + cnt,
                               ent->lno + 1 + cnt);
+                       if (repeat)
+                               emit_porcelain_details(suspect, 1);
+               }
                putchar('\t');
                do {
                        ch = *cp++;
@@ -1670,7 +1683,7 @@ 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;
+               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev;
 
                if (suspect->commit->object.flags & UNINTERESTING) {
                        if (blank_boundary)
@@ -1755,7 +1768,7 @@ static void output(struct scoreboard *sb, int option)
 
        for (ent = sb->ent; ent; ent = ent->next) {
                if (option & OUTPUT_PORCELAIN)
-                       emit_porcelain(sb, ent);
+                       emit_porcelain(sb, ent, option);
                else {
                        emit_other(sb, ent, option);
                }
@@ -1815,18 +1828,6 @@ static int read_ancestry(const char *graft_file)
        return 0;
 }
 
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
-{
-       int i, width;
-
-       for (width = 1, i = 10; i <= lines; width++)
-               i *= 10;
-       return width;
-}
-
 /*
  * How many columns do we need to show line numbers, authors,
  * and filenames?
@@ -1867,9 +1868,9 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (largest_score < ent_score(sb, e))
                        largest_score = ent_score(sb, e);
        }
-       max_orig_digits = lineno_width(longest_src_lines);
-       max_digits = lineno_width(longest_dst_lines);
-       max_score_digits = lineno_width(largest_score);
+       max_orig_digits = decimal_width(longest_src_lines);
+       max_digits = decimal_width(longest_dst_lines);
+       max_score_digits = decimal_width(largest_score);
 }
 
 /*
@@ -2083,6 +2084,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
+               char *buf_ptr;
                unsigned long buf_len;
 
                if (contents_from) {
@@ -2100,8 +2102,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
                        if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
-                           textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len))
-                               buf.len = buf_len;
+                           textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
+                               strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
                        else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
                                die_errno("cannot open or read '%s'", read_from);
                        break;
@@ -2299,6 +2301,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
                OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
                OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
+               OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
                OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
                OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
                OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
@@ -2310,6 +2313,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
                { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
                OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+               OPT__ABBREV(&abbrev),
                OPT_END()
        };
 
@@ -2345,6 +2349,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 parse_done:
        argc = parse_options_end(&ctx);
 
+       if (abbrev == -1)
+               abbrev = default_abbrev;
+       /* one more abbrev length is needed for the boundary commit */
+       abbrev++;
+
        if (revs_file && read_ancestry(revs_file))
                die_errno("reading graft file '%s' failed", revs_file);