Code

blame: don't overflow time buffer
[git.git] / builtin / blame.c
index 41525f1ea1fff29fae56d7c8b3db41db7d928fd5..3e1f7e1e453f6ecc28fb910686c6580e77929556 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;
@@ -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(struct scoreboard *sb, struct blame_entry *ent)
+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,
+                          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);
                }
@@ -2299,6 +2312,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 +2324,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 +2360,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);