#include "quote.h"
#include "xdiff-interface.h"
#include "cache-tree.h"
+#include "path-list.h"
+#include "mailmap.h"
static char blame_usage[] =
-"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
+"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"
" -c Use the same output mode as git-annotate (Default: off)\n"
" -b Show blank SHA-1 for boundary commits (Default: off)\n"
" -l Show long commit SHA1 (Default: off)\n"
" -t Show raw timestamp (Default: off)\n"
" -f, --show-name Show original filename (Default: auto)\n"
" -n, --show-number Show original linenumber (Default: off)\n"
+" -s Suppress author name and timestamp (Default: off)\n"
" -p, --porcelain Show in a format designed for machine consumption\n"
+" -w Ignore whitespace differences\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"
static int blank_boundary;
static int incremental;
static int cmd_is_annotate;
+static int xdl_opts = XDF_NEED_MINIMAL;
+static struct path_list mailmap;
#ifndef DEBUG
#define DEBUG 0
#define PICKAXE_BLAME_MOVE 01
#define PICKAXE_BLAME_COPY 02
#define PICKAXE_BLAME_COPY_HARDER 04
+#define PICKAXE_BLAME_COPY_HARDEST 010
/*
* blame for a blame_entry with score lower than these thresholds
xdemitconf_t xecfg;
xdemitcb_t ecb;
- xpp.flags = XDF_NEED_MINIMAL;
+ xpp.flags = xdl_opts;
+ memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = context;
- xecfg.flags = 0;
ecb.outf = xdiff_outf;
ecb.priv = &state;
memset(&state, 0, sizeof(state));
memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
}
+/*
+ * We are looking at a part of the final image represented by
+ * ent (tlno and same are offset by ent->s_lno).
+ * tlno is where we are looking at in the final image.
+ * up to (but not including) same match preimage.
+ * plno is where we are looking at in the preimage.
+ *
+ * <-------------- final image ---------------------->
+ * <------ent------>
+ * ^tlno ^same
+ * <---------preimage----->
+ * ^plno
+ *
+ * All line numbers are 0-based.
+ */
+static void handle_split(struct scoreboard *sb,
+ struct blame_entry *ent,
+ int tlno, int plno, int same,
+ struct origin *parent,
+ struct blame_entry *split)
+{
+ if (ent->num_lines <= tlno)
+ return;
+ if (tlno < same) {
+ struct blame_entry this[3];
+ tlno += ent->s_lno;
+ same += ent->s_lno;
+ split_overlap(this, ent, tlno, plno, same, parent);
+ copy_split_if_better(sb, split, this);
+ decref_split(this);
+ }
+}
+
/*
* Find the lines from parent that are the same as ent so that
* we can pass blames to it. file_p has the blob contents for
patch = compare_buffer(file_p, &file_o, 1);
+ /*
+ * file_o is a part of final image we are annotating.
+ * file_p partially may match that image.
+ */
memset(split, 0, sizeof(struct blame_entry [3]));
plno = tlno = 0;
for (i = 0; i < patch->num; i++) {
struct chunk *chunk = &patch->chunks[i];
- /* tlno to chunk->same are the same as ent */
- if (ent->num_lines <= tlno)
- break;
- if (tlno < chunk->same) {
- struct blame_entry this[3];
- split_overlap(this, ent,
- tlno + ent->s_lno, plno,
- chunk->same + ent->s_lno,
- parent);
- copy_split_if_better(sb, split, this);
- decref_split(this);
- }
+ handle_split(sb, ent, tlno, plno, chunk->same, parent, split);
plno = chunk->p_next;
tlno = chunk->t_next;
}
+ /* remainder, if any, all match the preimage */
+ handle_split(sb, ent, tlno, plno, ent->num_lines, parent, split);
free_patch(patch);
}
* and this code needs to be after diff_setup_done(), which
* usually makes find-copies-harder imply copy detection.
*/
- if ((opt & PICKAXE_BLAME_COPY_HARDER) &&
- (!porigin || strcmp(target->path, porigin->path)))
+ if ((opt & PICKAXE_BLAME_COPY_HARDEST)
+ || ((opt & PICKAXE_BLAME_COPY_HARDER)
+ && (!porigin || strcmp(target->path, porigin->path))))
diff_opts.find_copies_harder = 1;
if (is_null_sha1(target->commit->object.sha1))
int bufsz, char *person, const char **mail,
unsigned long *time, const char **tz)
{
- int len;
- char *tmp, *endp;
+ int len, tzlen, maillen;
+ char *tmp, *endp, *timepos;
tmp = strstr(inbuf, what);
if (!tmp)
while (*tmp != ' ')
tmp--;
*tz = tmp+1;
+ tzlen = (person+len)-(tmp+1);
*tmp = 0;
while (*tmp != ' ')
tmp--;
*time = strtoul(tmp, NULL, 10);
+ timepos = tmp;
*tmp = 0;
while (*tmp != ' ')
tmp--;
*mail = tmp + 1;
*tmp = 0;
+ maillen = timepos - tmp;
+
+ if (!mailmap.nr)
+ return;
+
+ /*
+ * mailmap expansion may make the name longer.
+ * make room by pushing stuff down.
+ */
+ tmp = person + bufsz - (tzlen + 1);
+ memmove(tmp, *tz, tzlen);
+ tmp[tzlen] = 0;
+ *tz = tmp;
+
+ tmp = tmp - (maillen + 1);
+ memmove(tmp, *mail, maillen);
+ tmp[maillen] = 0;
+ *mail = tmp;
+
+ /*
+ * Now, convert e-mail using mailmap
+ */
+ map_email(&mailmap, tmp + 1, person, tmp-person-1);
}
static void get_commit_info(struct commit *commit,
printf("boundary\n");
}
write_filename_info(suspect->path);
+ maybe_flush_or_die(stdout, "stdout");
}
}
#define OUTPUT_SHOW_NAME 020
#define OUTPUT_SHOW_NUMBER 040
#define OUTPUT_SHOW_SCORE 0100
+#define OUTPUT_NO_AUTHOR 0200
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
{
if (opt & OUTPUT_SHOW_NUMBER)
printf(" %*d", max_orig_digits,
ent->s_lno + 1 + cnt);
- printf(" (%-*.*s %10s %*d) ",
- longest_author, longest_author, ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
+
+ if (!(opt & OUTPUT_NO_AUTHOR))
+ printf(" (%-*.*s %10s",
+ longest_author, longest_author,
+ ci.author,
+ format_time(ci.author_time,
+ ci.author_tz,
+ show_raw_time));
+ printf(" %*d) ",
max_digits, ent->lno + 1 + cnt);
}
do {
*/
static int lineno_width(int lines)
{
- int i, width;
+ int i, width;
- for (width = 1, i = 10; i <= lines + 1; width++)
- i *= 10;
- return width;
+ for (width = 1, i = 10; i <= lines + 1; width++)
+ i *= 10;
+ return width;
}
/*
output_option |= OUTPUT_RAW_TIMESTAMP;
else if (!strcmp("-l", arg))
output_option |= OUTPUT_LONG_OBJECT_NAME;
+ else if (!strcmp("-s", arg))
+ output_option |= OUTPUT_NO_AUTHOR;
+ else if (!strcmp("-w", arg))
+ xdl_opts |= XDF_IGNORE_WHITESPACE;
else if (!strcmp("-S", arg) && ++i < argc)
revs_file = argv[i];
else if (!prefixcmp(arg, "-M")) {
blame_move_score = parse_score(arg+2);
}
else if (!prefixcmp(arg, "-C")) {
+ /*
+ * -C enables copy from removed files;
+ * -C -C enables copy from existing files, but only
+ * when blaming a new file;
+ * -C -C -C enables copy from existing files for
+ * everybody
+ */
+ if (opt & PICKAXE_BLAME_COPY_HARDER)
+ opt |= PICKAXE_BLAME_COPY_HARDEST;
if (opt & PICKAXE_BLAME_COPY)
opt |= PICKAXE_BLAME_COPY_HARDER;
opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
die("reading graft file %s failed: %s",
revs_file, strerror(errno));
+ read_mailmap(&mailmap, ".mailmap", NULL);
+
assign_blame(&sb, &revs, opt);
if (incremental)