diff --git a/builtin-blame.c b/builtin-blame.c
index 593b539f1ed97a442b80999b363916b36c836dd1..114a214ed3fef40ae5cc13737d037f29d6f8acfd 100644 (file)
--- a/builtin-blame.c
+++ b/builtin-blame.c
#include "string-list.h"
#include "mailmap.h"
#include "parse-options.h"
+#include "utf8.h"
static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
return porigin;
}
-/*
- * Parsing of patch chunks...
- */
-struct chunk {
- /* line number in postimage; up to but not including this
- * line is the same as preimage
- */
- int same;
-
- /* preimage line number after this chunk */
- int p_next;
-
- /* postimage line number after this chunk */
- int t_next;
-};
-
-struct patch {
- struct chunk *chunks;
- int num;
-};
-
-struct blame_diff_state {
- struct patch *ret;
- unsigned hunk_post_context;
- unsigned hunk_in_pre_context : 1;
-};
-
-static void process_u_diff(void *state_, char *line, unsigned long len)
-{
- struct blame_diff_state *state = state_;
- struct chunk *chunk;
- int off1, off2, len1, len2, num;
-
- num = state->ret->num;
- if (len < 4 || line[0] != '@' || line[1] != '@') {
- if (state->hunk_in_pre_context && line[0] == ' ')
- state->ret->chunks[num - 1].same++;
- else {
- state->hunk_in_pre_context = 0;
- if (line[0] == ' ')
- state->hunk_post_context++;
- else
- state->hunk_post_context = 0;
- }
- return;
- }
-
- if (num && state->hunk_post_context) {
- chunk = &state->ret->chunks[num - 1];
- chunk->p_next -= state->hunk_post_context;
- chunk->t_next -= state->hunk_post_context;
- }
- state->ret->num = ++num;
- state->ret->chunks = xrealloc(state->ret->chunks,
- sizeof(struct chunk) * num);
- chunk = &state->ret->chunks[num - 1];
- if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) {
- state->ret->num--;
- return;
- }
-
- /* Line numbers in patch output are one based. */
- off1--;
- off2--;
-
- chunk->same = len2 ? off2 : (off2 + 1);
-
- chunk->p_next = off1 + (len1 ? len1 : 1);
- chunk->t_next = chunk->same + len2;
- state->hunk_in_pre_context = 1;
- state->hunk_post_context = 0;
-}
-
-static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
- int context)
-{
- struct blame_diff_state state;
- xpparam_t xpp;
- xdemitconf_t xecfg;
- xdemitcb_t ecb;
-
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = context;
- memset(&state, 0, sizeof(state));
- state.ret = xmalloc(sizeof(struct patch));
- state.ret->chunks = NULL;
- state.ret->num = 0;
-
- xdi_diff_outf(file_p, file_o, process_u_diff, &state, &xpp, &xecfg, &ecb);
-
- if (state.ret->num) {
- struct chunk *chunk;
- chunk = &state.ret->chunks[state.ret->num - 1];
- chunk->p_next -= state.hunk_post_context;
- chunk->t_next -= state.hunk_post_context;
- }
- return state.ret;
-}
-
-static void free_patch(struct patch *p)
-{
- free(p->chunks);
- free(p);
-}
-
/*
* Link in a new blame entry to the scoreboard. Entries that cover the
* same line range have been removed from the scoreboard previously.
}
}
+struct blame_chunk_cb_data {
+ struct scoreboard *sb;
+ struct origin *target;
+ struct origin *parent;
+ long plno;
+ long tlno;
+};
+
+static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+{
+ struct blame_chunk_cb_data *d = data;
+ blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
/*
* We are looking at the origin 'target' and aiming to pass blame
* for the lines it is suspected to its parent. Run diff to find
struct origin *target,
struct origin *parent)
{
- int i, last_in_target, plno, tlno;
- struct patch *patch;
+ int last_in_target;
mmfile_t file_p, file_o;
+ struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
last_in_target = find_last_in_target(sb, target);
if (last_in_target < 0)
return 1; /* nothing remains for this target */
- /*
- * Run diff between two origins and grab the patch output, so that
- * we can pass blame for lines origin is currently suspected for
- * to its parent.
- */
fill_origin_blob(parent, &file_p);
fill_origin_blob(target, &file_o);
- patch = compare_buffer(&file_p, &file_o, 0);
num_get_patch++;
- plno = tlno = 0;
- for (i = 0; i < patch->num; i++) {
- struct chunk *chunk = &patch->chunks[i];
-
- blame_chunk(sb, tlno, plno, chunk->same, target, parent);
- plno = chunk->p_next;
- tlno = chunk->t_next;
- }
+ memset(&xpp, 0, sizeof(xpp));
+ xpp.flags = xdl_opts;
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = 0;
+ xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
/* The rest (i.e. anything after tlno) are the same as the parent */
- blame_chunk(sb, tlno, plno, last_in_target, target, parent);
+ blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
- free_patch(patch);
return 0;
}
}
}
+struct handle_split_cb_data {
+ struct scoreboard *sb;
+ struct blame_entry *ent;
+ struct origin *parent;
+ struct blame_entry *split;
+ long plno;
+ long tlno;
+};
+
+static void handle_split_cb(void *data, long same, long p_next, long t_next)
+{
+ struct handle_split_cb_data *d = data;
+ handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
/*
* 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
const char *cp;
int cnt;
mmfile_t file_o;
- struct patch *patch;
- int i, plno, tlno;
+ struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
/*
* Prepare mmfile that contains only the lines in ent.
}
file_o.size = cp - file_o.ptr;
- 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(&xpp, 0, sizeof(xpp));
+ xpp.flags = xdl_opts;
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = 1;
memset(split, 0, sizeof(struct blame_entry [3]));
- plno = tlno = 0;
- for (i = 0; i < patch->num; i++) {
- struct chunk *chunk = &patch->chunks[i];
-
- handle_split(sb, ent, tlno, plno, chunk->same, parent, split);
- plno = chunk->p_next;
- tlno = chunk->t_next;
- }
+ xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
/* remainder, if any, all match the preimage */
- handle_split(sb, ent, tlno, plno, ent->num_lines, parent, split);
- free_patch(patch);
+ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
}
/*
* Parse author/committer line in the commit object buffer
*/
static void get_ac_line(const char *inbuf, const char *what,
- int bufsz, char *person, const char **mail,
+ int person_len, char *person,
+ int mail_len, char *mail,
unsigned long *time, const char **tz)
{
int len, tzlen, maillen;
- char *tmp, *endp, *timepos;
+ char *tmp, *endp, *timepos, *mailpos;
tmp = strstr(inbuf, what);
if (!tmp)
len = strlen(tmp);
else
len = endp - tmp;
- if (bufsz <= len) {
+ if (person_len <= len) {
error_out:
/* Ugh */
- *mail = *tz = "(unknown)";
+ *tz = "(unknown)";
+ strcpy(mail, *tz);
*time = 0;
return;
}
*tmp = 0;
while (*tmp != ' ')
tmp--;
- *mail = tmp + 1;
+ mailpos = tmp + 1;
*tmp = 0;
maillen = timepos - tmp;
+ memcpy(mail, mailpos, maillen);
if (!mailmap.nr)
return;
* mailmap expansion may make the name longer.
* make room by pushing stuff down.
*/
- tmp = person + bufsz - (tzlen + 1);
+ tmp = person + person_len - (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
+ * Now, convert both name and e-mail using mailmap
*/
- map_email(&mailmap, tmp + 1, person, tmp-person-1);
+ if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+ /* Add a trailing '>' to email, since map_user returns plain emails
+ Note: It already has '<', since we replace from mail+1 */
+ mailpos = memchr(mail, '\0', mail_len);
+ if (mailpos && mailpos-mail < mail_len - 1) {
+ *mailpos = '>';
+ *(mailpos+1) = '\0';
+ }
+ }
}
static void get_commit_info(struct commit *commit,
int detailed)
{
int len;
- char *tmp, *endp;
- static char author_buf[1024];
- static char committer_buf[1024];
+ char *tmp, *endp, *reencoded, *message;
+ static char author_name[1024];
+ static char author_mail[1024];
+ static char committer_name[1024];
+ static char committer_mail[1024];
static char summary_buf[1024];
/*
die("Cannot read commit %s",
sha1_to_hex(commit->object.sha1));
}
- ret->author = author_buf;
- get_ac_line(commit->buffer, "\nauthor ",
- sizeof(author_buf), author_buf, &ret->author_mail,
+ reencoded = reencode_commit_message(commit, NULL);
+ message = reencoded ? reencoded : commit->buffer;
+ ret->author = author_name;
+ ret->author_mail = author_mail;
+ get_ac_line(message, "\nauthor ",
+ sizeof(author_name), author_name,
+ sizeof(author_mail), author_mail,
&ret->author_time, &ret->author_tz);
- if (!detailed)
+ if (!detailed) {
+ free(reencoded);
return;
+ }
- ret->committer = committer_buf;
- get_ac_line(commit->buffer, "\ncommitter ",
- sizeof(committer_buf), committer_buf, &ret->committer_mail,
+ ret->committer = committer_name;
+ ret->committer_mail = committer_mail;
+ get_ac_line(message, "\ncommitter ",
+ sizeof(committer_name), committer_name,
+ sizeof(committer_mail), committer_mail,
&ret->committer_time, &ret->committer_tz);
ret->summary = summary_buf;
- tmp = strstr(commit->buffer, "\n\n");
+ tmp = strstr(message, "\n\n");
if (!tmp) {
error_out:
sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+ free(reencoded);
return;
}
tmp += 2;
goto error_out;
memcpy(summary_buf, tmp, len);
summary_buf[len] = 0;
+ free(reencoded);
}
/*
@@ -1698,13 +1631,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
printf(" %*d", max_orig_digits,
ent->s_lno + 1 + cnt);
- if (!(opt & OUTPUT_NO_AUTHOR))
- printf(" (%-*.*s %10s",
- longest_author, longest_author,
- ci.author,
+ if (!(opt & OUTPUT_NO_AUTHOR)) {
+ int pad = longest_author - utf8_strwidth(ci.author);
+ printf(" (%s%*s %10s",
+ ci.author, pad, "",
format_time(ci.author_time,
ci.author_tz,
show_raw_time));
+ }
printf(" %*d) ",
max_digits, ent->lno + 1 + cnt);
}
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- num = strlen(ci.author);
+ num = utf8_strwidth(ci.author);
if (longest_author < num)
longest_author = num;
}
@@ -2076,7 +2010,6 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
const char *read_from;
- unsigned long fin_size;
if (contents_from) {
if (stat(contents_from, &st) < 0)
@@ -2088,7 +2021,6 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
die("Cannot lstat %s", path);
read_from = path;
}
- fin_size = xsize_t(st.st_size);
mode = canon_mode(st.st_mode);
switch (st.st_mode & S_IFMT) {
case S_IFREG:
@@ -2096,9 +2028,8 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
die("cannot open or read %s", read_from);
break;
case S_IFLNK:
- if (readlink(read_from, buf.buf, buf.alloc) != fin_size)
+ if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
die("cannot readlink %s", read_from);
- buf.len = fin_size;
break;
default:
die("unsupported file type %s", read_from);
die("reading graft file %s failed: %s",
revs_file, strerror(errno));
- read_mailmap(&mailmap, ".mailmap", NULL);
+ read_mailmap(&mailmap, NULL);
if (!incremental)
setup_pager();