#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] [-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"
" -L n,m Process only line range n,m, counting from 1\n"
" -M, -C Find line movements within and across files\n"
static int blank_boundary;
static int incremental;
static int cmd_is_annotate;
+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
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,
#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 {
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("-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)
#include "pkt-line.h"
#include "utf8.h"
#include "interpolate.h"
+#include "diff.h"
+#include "revision.h"
int save_commit_buffer = 1;
struct commit *lookup_commit(const unsigned char *sha1)
{
struct object *obj = lookup_object(sha1);
- if (!obj) {
- struct commit *ret = alloc_commit_node();
- created_object(sha1, &ret->object);
- ret->object.type = OBJ_COMMIT;
- return ret;
- }
+ if (!obj)
+ return create_object(sha1, OBJ_COMMIT, alloc_commit_node());
if (!obj->type)
obj->type = OBJ_COMMIT;
return check_commit(obj, sha1, 0);
}
static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
- const char *line, int relative_date,
+ const char *line, enum date_mode dmode,
const char *encoding)
{
char *date;
switch (fmt) {
case CMIT_FMT_MEDIUM:
ret += sprintf(buf + ret, "Date: %s\n",
- show_date(time, tz, relative_date));
+ show_date(time, tz, dmode));
break;
case CMIT_FMT_EMAIL:
ret += sprintf(buf + ret, "Date: %s\n",
break;
case CMIT_FMT_FULLER:
ret += sprintf(buf + ret, "%sDate: %s\n", what,
- show_date(time, tz, relative_date));
+ show_date(time, tz, dmode));
break;
default:
/* notin' */
next = NULL;
} else
next = eol + 1;
- if (!strncmp(line, key, key_len) && line[key_len] == ' ') {
+ if (eol - line > key_len &&
+ !strncmp(line, key, key_len) &&
+ line[key_len] == ' ') {
int len = eol - line - key_len;
char *ret = xmalloc(len);
memcpy(ret, line + key_len + 1, len - 1);
{ "%Cgreen" }, /* green */
{ "%Cblue" }, /* blue */
{ "%Creset" }, /* reset color */
- { "%n" } /* newline */
+ { "%n" }, /* newline */
+ { "%m" }, /* left/right/bottom */
};
enum interp_index {
IHASH = 0, IHASH_ABBREV,
ISUBJECT,
IBODY,
IRED, IGREEN, IBLUE, IRESET_COLOR,
- INEWLINE
+ INEWLINE,
+ ILEFT_RIGHT,
};
struct commit_list *p;
char parents[1024];
int i;
enum { HEADER, SUBJECT, BODY } state;
- if (INEWLINE + 1 != ARRAY_SIZE(table))
+ if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
die("invalid interp table!");
/* these are independent of the commit */
interp_set_entry(table, ITREE_ABBREV,
find_unique_abbrev(commit->tree->object.sha1,
DEFAULT_ABBREV));
+ interp_set_entry(table, ILEFT_RIGHT,
+ (commit->object.flags & BOUNDARY)
+ ? "-"
+ : (commit->object.flags & SYMMETRIC_LEFT)
+ ? "<"
+ : ">");
parents[1] = 0;
for (i = 0, p = commit->parents;
char *buf, unsigned long space,
int abbrev, const char *subject,
const char *after_subject,
- int relative_date)
+ enum date_mode dmode)
{
int hdr = 1, body = 0, seen_title = 0;
unsigned long offset = 0;
offset += add_user_info("Author", fmt,
buf + offset,
line + 7,
- relative_date,
+ dmode,
encoding);
if (!memcmp(line, "committer ", 10) &&
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
offset += add_user_info("Commit", fmt,
buf + offset,
line + 10,
- relative_date,
+ dmode,
encoding);
continue;
}
#include "log-tree.h"
#include "reflog-walk.h"
+struct decoration name_decoration = { "object names" };
+
static void show_parents(struct commit *commit, int abbrev)
{
struct commit_list *p;
}
}
+static void show_decorations(struct commit *commit)
+{
+ const char *prefix;
+ struct name_decoration *decoration;
+
+ decoration = lookup_decoration(&name_decoration, &commit->object);
+ if (!decoration)
+ return;
+ prefix = " (";
+ while (decoration) {
+ printf("%s%s", prefix, decoration->name);
+ prefix = ", ";
+ decoration = decoration->next;
+ }
+ putchar(')');
+}
+
/*
* Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
* Signed-off-by: and Acked-by: lines.
fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
if (opt->parents)
show_parents(commit, abbrev_commit);
+ show_decorations(commit);
putchar(opt->diffopt.line_termination);
return;
}
if (opt->total > 0) {
static char buffer[64];
snprintf(buffer, sizeof(buffer),
- "Subject: [PATCH %0*d/%d] ",
+ "Subject: [%s %0*d/%d] ",
+ opt->subject_prefix,
digits_in_number(opt->total),
opt->nr, opt->total);
subject = buffer;
- } else if (opt->total == 0)
- subject = "Subject: [PATCH] ";
- else
+ } else if (opt->total == 0) {
+ static char buffer[256];
+ snprintf(buffer, sizeof(buffer),
+ "Subject: [%s] ",
+ opt->subject_prefix);
+ subject = buffer;
+ } else {
subject = "Subject: ";
+ }
printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
if (opt->message_id)
stdout);
if (opt->commit_format != CMIT_FMT_ONELINE)
fputs("commit ", stdout);
- if (opt->left_right) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & SYMMETRIC_LEFT)
+ if (commit->object.flags & BOUNDARY)
+ putchar('-');
+ else if (opt->left_right) {
+ if (commit->object.flags & SYMMETRIC_LEFT)
putchar('<');
else
putchar('>');
printf(" (from %s)",
diff_unique_abbrev(parent->object.sha1,
abbrev_commit));
+ show_decorations(commit);
printf("%s",
diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
if (opt->reflog_info) {
show_reflog_message(opt->reflog_info,
opt->commit_format == CMIT_FMT_ONELINE,
- opt->relative_date);
+ opt->date_mode);
if (opt->commit_format == CMIT_FMT_ONELINE) {
printf("%s", sep);
return;
*/
len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
sizeof(this_header), abbrev, subject,
- extra_headers, opt->relative_date);
+ extra_headers, opt->date_mode);
if (opt->add_signoff)
len = append_signoff(this_header, sizeof(this_header), len,