diff options
Diffstat (limited to 'builtin/blame.c')
-rw-r--r-- | builtin/blame.c | 299 |
1 files changed, 189 insertions, 110 deletions
diff --git a/builtin/blame.c b/builtin/blame.c index 1df13cf7f4..f7aa95f4ba 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -28,6 +28,7 @@ #include "line-range.h" #include "line-log.h" #include "dir.h" +#include "progress.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -50,11 +51,12 @@ static int incremental; static int xdl_opts; static int abbrev = -1; static int no_whole_file_rename; +static int show_progress; static struct date_mode blame_date_mode = { DATE_ISO8601 }; static size_t blame_date_width; -static struct string_list mailmap; +static struct string_list mailmap = STRING_LIST_INIT_NODUP; #ifndef DEBUG #define DEBUG 0 @@ -118,7 +120,7 @@ struct origin { */ struct blame_entry *suspects; mmfile_t file; - unsigned char blob_sha1[20]; + struct object_id blob_oid; unsigned mode; /* guilty gets set when shipping any suspects to the final * blame list instead of other commits @@ -127,7 +129,12 @@ struct origin { char path[FLEX_ARRAY]; }; -static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, +struct progress_info { + struct progress *progress; + int blamed_lines; +}; + +static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) { xpparam_t xpp = {0}; @@ -135,7 +142,6 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, xdemitcb_t ecb = {NULL}; xpp.flags = xdl_opts; - xecfg.ctxlen = ctxlen; xecfg.hunk_func = hunk_func; ecb.priv = cb_data; return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); @@ -148,8 +154,8 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, */ int textconv_object(const char *path, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, char **buf, unsigned long *buf_size) { @@ -157,7 +163,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, sha1_valid, mode); + fill_filespec(df, oid->hash, oid_valid, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -182,15 +188,16 @@ static void fill_origin_blob(struct diff_options *opt, num_read_blob++; if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) ; else - file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); + file->ptr = read_sha1_file(o->blob_oid.hash, &type, + &file_size); file->size = file_size; if (!file->ptr) die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), + oid_to_hex(&o->blob_oid), o->path); o->file = *file; } @@ -459,13 +466,11 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin, static struct origin *make_origin(struct commit *commit, const char *path) { struct origin *o; - size_t pathlen = strlen(path) + 1; - o = xcalloc(1, sizeof(*o) + pathlen); + FLEX_ALLOC_STR(o, path, path); o->commit = commit; o->refcnt = 1; o->next = commit->util; commit->util = o; - memcpy(o->path, path, pathlen); /* includes NUL */ return o; } @@ -504,17 +509,17 @@ static struct origin *get_origin(struct scoreboard *sb, */ static int fill_blob_sha1_and_mode(struct origin *origin) { - if (!is_null_sha1(origin->blob_sha1)) + if (!is_null_oid(&origin->blob_oid)) return 0; if (get_tree_entry(origin->commit->object.oid.hash, origin->path, - origin->blob_sha1, &origin->mode)) + origin->blob_oid.hash, &origin->mode)) goto error_out; - if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) + if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: - hashclr(origin->blob_sha1); + oidclr(&origin->blob_oid); origin->mode = S_IFINVALID; return -1; } @@ -568,7 +573,7 @@ static struct origin *find_origin(struct scoreboard *sb, if (!diff_queued_diff.nr) { /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, origin->blob_sha1); + oidcpy(&porigin->blob_oid, &origin->blob_oid); porigin->mode = origin->mode; } else { /* @@ -594,7 +599,7 @@ static struct origin *find_origin(struct scoreboard *sb, p->status); case 'M': porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->sha1); + oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; case 'A': @@ -604,7 +609,7 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); - free_pathspec(&diff_opts.pathspec); + clear_pathspec(&diff_opts.pathspec); return porigin; } @@ -640,21 +645,24 @@ static struct origin *find_rename(struct scoreboard *sb, if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->sha1); + oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; } } diff_flush(&diff_opts); - free_pathspec(&diff_opts.pathspec); + clear_pathspec(&diff_opts.pathspec); return porigin; } /* * Append a new blame entry to a given output queue. */ -static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e) +static void add_blame_entry(struct blame_entry ***queue, + const struct blame_entry *src) { + struct blame_entry *e = xmalloc(sizeof(*e)); + memcpy(e, src, sizeof(*e)); origin_incref(e->suspect); e->next = **queue; @@ -755,21 +763,15 @@ static void split_blame(struct blame_entry ***blamed, struct blame_entry *split, struct blame_entry *e) { - struct blame_entry *new_entry; - if (split[0].suspect && split[2].suspect) { /* The first part (reuse storage for the existing entry e) */ dup_entry(unblamed, e, &split[0]); /* The last part -- me */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(unblamed, new_entry); + add_blame_entry(unblamed, &split[2]); /* ... and the middle part -- parent */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(blamed, new_entry); + add_blame_entry(blamed, &split[1]); } else if (!split[0].suspect && !split[2].suspect) /* @@ -780,18 +782,12 @@ static void split_blame(struct blame_entry ***blamed, else if (split[0].suspect) { /* me and then parent */ dup_entry(unblamed, e, &split[0]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(blamed, new_entry); + add_blame_entry(blamed, &split[1]); } else { /* parent and then me */ dup_entry(blamed, e, &split[1]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(unblamed, new_entry); + add_blame_entry(unblamed, &split[2]); } } @@ -975,7 +971,7 @@ static void pass_blame_to_parent(struct scoreboard *sb, fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; - if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d)) + if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d)) die("unable to generate diff (%s -> %s)", oid_to_hex(&parent->commit->object.oid), oid_to_hex(&target->commit->object.oid)); @@ -1124,7 +1120,7 @@ static void find_copy_in_blob(struct scoreboard *sb, * file_p partially may match that image. */ memset(split, 0, sizeof(struct blame_entry [3])); - if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d)) + if (diff_hunks(file_p, &file_o, handle_split_cb, &d)) die("unable to generate diff (%s)", oid_to_hex(&parent->commit->object.oid)); /* remainder, if any, all match the preimage */ @@ -1304,7 +1300,7 @@ static void find_copy_in_parent(struct scoreboard *sb, continue; norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); + oidcpy(&norigin->blob_oid, &p->one->oid); norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) @@ -1338,7 +1334,7 @@ static void find_copy_in_parent(struct scoreboard *sb, } while (unblamed); target->suspects = reverse_blame(leftover, NULL); diff_flush(&diff_opts); - free_pathspec(&diff_opts.pathspec); + clear_pathspec(&diff_opts.pathspec); } /* @@ -1454,15 +1450,14 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) porigin = find(sb, p, origin); if (!porigin) continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { + if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) { pass_whole_blame(sb, origin, porigin); origin_decref(porigin); goto finish; } for (j = same = 0; j < i; j++) if (sg_origin[j] && - !hashcmp(sg_origin[j]->blob_sha1, - porigin->blob_sha1)) { + !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) { same = 1; break; } @@ -1696,13 +1691,23 @@ static void get_commit_info(struct commit *commit, } /* + * Write out any suspect information which depends on the path. This must be + * handled separately from emit_one_suspect_detail(), because a given commit + * may have changes in multiple paths. So this needs to appear each time + * we mention a new group. + * * To allow LF and other nonportable characters in pathnames, * they are c-style quoted as needed. */ -static void write_filename_info(const char *path) +static void write_filename_info(struct origin *suspect) { + if (suspect->previous) { + struct origin *prev = suspect->previous; + printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); + write_name_quoted(prev->path, stdout, '\n'); + } printf("filename "); - write_name_quoted(path, stdout, '\n'); + write_name_quoted(suspect->path, stdout, '\n'); } /* @@ -1731,11 +1736,6 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); - if (suspect->previous) { - struct origin *prev = suspect->previous; - printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); - write_name_quoted(prev->path, stdout, '\n'); - } commit_info_destroy(&ci); @@ -1746,7 +1746,8 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) * The blame_entry is found to be guilty for the range. * Show it in incremental output. */ -static void found_guilty_entry(struct blame_entry *ent) +static void found_guilty_entry(struct blame_entry *ent, + struct progress_info *pi) { if (incremental) { struct origin *suspect = ent->suspect; @@ -1755,9 +1756,11 @@ static void found_guilty_entry(struct blame_entry *ent) oid_to_hex(&suspect->commit->object.oid), ent->s_lno + 1, ent->lno + 1, ent->num_lines); emit_one_suspect_detail(suspect, 0); - write_filename_info(suspect->path); + write_filename_info(suspect); maybe_flush_or_die(stdout, "stdout"); } + pi->blamed_lines += ent->num_lines; + display_progress(pi->progress, pi->blamed_lines); } /* @@ -1768,6 +1771,11 @@ static void assign_blame(struct scoreboard *sb, int opt) { struct rev_info *revs = sb->revs; struct commit *commit = prio_queue_get(&sb->commits); + struct progress_info pi = { NULL, 0 }; + + if (show_progress) + pi.progress = start_progress_delay(_("Blaming lines"), + sb->num_lines, 50, 1); while (commit) { struct blame_entry *ent; @@ -1809,7 +1817,7 @@ static void assign_blame(struct scoreboard *sb, int opt) suspect->guilty = 1; for (;;) { struct blame_entry *next = ent->next; - found_guilty_entry(ent); + found_guilty_entry(ent, &pi); if (next) { ent = next; continue; @@ -1825,6 +1833,8 @@ static void assign_blame(struct scoreboard *sb, int opt) if (DEBUG) /* sanity */ sanity_check_refcnt(sb); } + + stop_progress(&pi.progress); } static const char *format_time(unsigned long time, const char *tz_str, @@ -1870,7 +1880,7 @@ 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); + write_filename_info(suspect); } static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, @@ -1882,7 +1892,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, struct origin *suspect = ent->suspect; char hex[GIT_SHA1_HEXSZ + 1]; - sha1_to_hex_r(hex, suspect->commit->object.oid.hash); + oid_to_hex_r(hex, &suspect->commit->object.oid); printf("%s %d %d %d\n", hex, ent->s_lno + 1, @@ -1922,12 +1932,12 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); get_commit_info(suspect->commit, &ci, 1); - sha1_to_hex_r(hex, suspect->commit->object.oid.hash); + oid_to_hex_r(hex, &suspect->commit->object.oid); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -2042,7 +2052,8 @@ static int prepare_lines(struct scoreboard *sb) for (p = buf; p < end; p = get_next_line(p, end)) num++; - sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1)); + ALLOC_ARRAY(sb->lineno, num + 1); + lineno = sb->lineno; for (p = buf; p < end; p = get_next_line(p, end)) *lineno++ = p - buf; @@ -2096,7 +2107,7 @@ static void find_alignment(struct scoreboard *sb, int *option) unsigned largest_score = 0; struct blame_entry *e; int compute_auto_abbrev = (abbrev < 0); - int auto_abbrev = default_abbrev; + int auto_abbrev = DEFAULT_ABBREV; for (e = sb->ent; e; e = e->next) { struct origin *suspect = e->suspect; @@ -2205,6 +2216,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) return 0; } + if (git_diff_heuristic_config(var, value, cb) < 0) + return -1; if (userdiff_config(var, value) < 0) return -1; @@ -2214,26 +2227,35 @@ static int git_blame_config(const char *var, const char *value, void *cb) static void verify_working_tree_path(struct commit *work_tree, const char *path) { struct commit_list *parents; + int pos; for (parents = work_tree->parents; parents; parents = parents->next) { - const unsigned char *commit_sha1 = parents->item->object.oid.hash; - unsigned char blob_sha1[20]; + const struct object_id *commit_oid = &parents->item->object.oid; + struct object_id blob_oid; unsigned mode; - if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) && - sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) + if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) && + sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) return; } - die("no such path '%s' in HEAD", path); + + pos = cache_name_pos(path, strlen(path)); + if (pos >= 0) + ; /* path is in the index */ + else if (-1 - pos < active_nr && + !strcmp(active_cache[-1 - pos]->name, path)) + ; /* path is in the index, unmerged */ + else + die("no such path '%s' in HEAD", path); } -static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1) +static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid) { struct commit *parent; - parent = lookup_commit_reference(sha1); + parent = lookup_commit_reference(oid->hash); if (!parent) - die("no such commit %s", sha1_to_hex(sha1)); + die("no such commit %s", oid_to_hex(oid)); return &commit_list_insert(parent, tail)->next; } @@ -2250,10 +2272,10 @@ static void append_merge_parents(struct commit_list **tail) } while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { - unsigned char sha1[20]; - if (line.len < 40 || get_sha1_hex(line.buf, sha1)) + struct object_id oid; + if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid)) die("unknown line in '%s': %s", git_path_merge_head(), line.buf); - tail = append_parent(tail, sha1); + tail = append_parent(tail, &oid); } close(merge_head); strbuf_release(&line); @@ -2282,7 +2304,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, struct commit *commit; struct origin *origin; struct commit_list **parent_tail, *parent; - unsigned char head_sha1[20]; + struct object_id head_oid; struct strbuf buf = STRBUF_INIT; const char *ident; time_t now; @@ -2291,16 +2313,17 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, unsigned mode; struct strbuf msg = STRBUF_INIT; + read_cache(); time(&now); commit = alloc_commit_node(); commit->object.parsed = 1; commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) die("no such ref: HEAD"); - parent_tail = append_parent(parent_tail, head_sha1); + parent_tail = append_parent(parent_tail, &head_oid); append_merge_parents(parent_tail); verify_working_tree_path(commit, path); @@ -2341,7 +2364,7 @@ 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, 0, &buf_ptr, &buf_len)) + textconv_object(read_from, mode, &null_oid, 0, &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); @@ -2363,7 +2386,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, convert_to_git(path, buf.buf, buf.len, &buf, 0); origin->file.ptr = buf.buf; origin->file.size = buf.len; - pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); + pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash); /* * Read the current index, replace the path entry with @@ -2385,18 +2408,13 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, } size = cache_entry_size(len); ce = xcalloc(1, size); - hashcpy(ce->sha1, origin->blob_sha1); + oidcpy(&ce->oid, &origin->blob_oid); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - /* - * We are not going to write this out, so this does not matter - * right now, but someday we might optimize diff-index --cached - * with cache-tree information. - */ cache_tree_invalidate_path(&the_index, path); return commit; @@ -2413,8 +2431,7 @@ static struct commit *find_single_final(struct rev_info *revs, struct object *obj = revs->pending.objects[i].item; if (obj->flags & UNINTERESTING) continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); + obj = deref_tag(obj, NULL, 0); if (obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); if (found) @@ -2435,6 +2452,41 @@ static char *prepare_final(struct scoreboard *sb) return xstrdup_or_null(name); } +static const char *dwim_reverse_initial(struct scoreboard *sb) +{ + /* + * DWIM "git blame --reverse ONE -- PATH" as + * "git blame --reverse ONE..HEAD -- PATH" but only do so + * when it makes sense. + */ + struct object *obj; + struct commit *head_commit; + unsigned char head_sha1[20]; + + if (sb->revs->pending.nr != 1) + return NULL; + + /* Is that sole rev a committish? */ + obj = sb->revs->pending.objects[0].item; + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + return NULL; + + /* Do we have HEAD? */ + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) + return NULL; + head_commit = lookup_commit_reference_gently(head_sha1, 1); + if (!head_commit) + return NULL; + + /* Turn "ONE" into "ONE..HEAD" then */ + obj->flags |= UNINTERESTING; + add_pending_object(sb->revs, &head_commit->object, "HEAD"); + + sb->final = (struct commit *)obj; + return sb->revs->pending.objects[0].name; +} + static char *prepare_initial(struct scoreboard *sb) { int i; @@ -2449,19 +2501,21 @@ static char *prepare_initial(struct scoreboard *sb) struct object *obj = revs->pending.objects[i].item; if (!(obj->flags & UNINTERESTING)) continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); + obj = deref_tag(obj, NULL, 0); if (obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); if (sb->final) - die("More than one commit to dig down to %s and %s?", + die("More than one commit to dig up from, %s and %s?", revs->pending.objects[i].name, final_commit_name); sb->final = (struct commit *) obj; final_commit_name = revs->pending.objects[i].name; } + if (!final_commit_name) - die("No commit to dig down to?"); + final_commit_name = dwim_reverse_initial(sb); + if (!final_commit_name) + die("No commit to dig up from?"); return xstrdup(final_commit_name); } @@ -2510,16 +2564,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix) enum object_type type; struct commit *final_commit = NULL; - static struct string_list range_list; - static int output_option = 0, opt = 0; - static int show_stats = 0; - static const char *revs_file = NULL; - static const char *contents_from = NULL; - static const struct option options[] = { + struct string_list range_list = STRING_LIST_INIT_NODUP; + int output_option = 0, opt = 0; + int show_stats = 0; + const char *revs_file = NULL; + const char *contents_from = NULL; + const struct option options[] = { OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), + OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")), OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), @@ -2531,6 +2586,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + + /* + * The following two options are parsed by parse_revision_opt() + * and are only included here to get included in the "-h" + * output: + */ + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), @@ -2555,6 +2618,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; dashdash_pos = 0; + show_progress = -1; parse_options_start(&ctx, argc, argv, prefix, options, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); @@ -2576,12 +2640,22 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } parse_done: no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); - if (0 < abbrev) + if (incremental || (output_option & OUTPUT_PORCELAIN)) { + if (show_progress > 0) + die(_("--progress can't be used with --incremental or porcelain formats")); + show_progress = 0; + } else if (show_progress < 0) + show_progress = isatty(2); + + if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) /* one more abbrev length is needed for the boundary commit */ abbrev++; + else if (!abbrev) + abbrev = GIT_SHA1_HEXSZ; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); @@ -2607,6 +2681,9 @@ parse_done: case DATE_RAW: blame_date_width = sizeof("1161298804 -0700"); break; + case DATE_UNIX: + blame_date_width = sizeof("1161298804"); + break; case DATE_SHORT: blame_date_width = sizeof("2006-10-19"); break; @@ -2697,7 +2774,7 @@ parse_done: sb.commits.compare = compare_commits_by_commit_date; } else if (contents_from) - die("--contents and --reverse do not blend well."); + die(_("--contents and --reverse do not blend well.")); else { final_commit_name = prepare_initial(&sb); sb.commits.compare = compare_commits_by_reverse_commit_date; @@ -2717,12 +2794,12 @@ parse_done: add_pending_object(&revs, &(sb.final->object), ":"); } else if (contents_from) - die("Cannot use --contents with final commit object name"); + die(_("cannot use --contents with final commit object name")); if (reverse && revs.first_parent_only) { final_commit = find_single_final(sb.revs, NULL); if (!final_commit) - die("--reverse and --first-parent together require specified latest commit"); + die(_("--reverse and --first-parent together require specified latest commit")); } /* @@ -2749,7 +2826,7 @@ parse_done: } if (oidcmp(&c->object.oid, &sb.final->object.oid)) - die("--reverse --first-parent together require range along first-parent chain"); + die(_("--reverse --first-parent together require range along first-parent chain")); } if (is_null_oid(&sb.final->object.oid)) { @@ -2760,26 +2837,26 @@ parse_done: else { o = get_origin(&sb, sb.final, path); if (fill_blob_sha1_and_mode(o)) - die("no such path %s in %s", path, final_commit_name); + die(_("no such path %s in %s"), path, final_commit_name); if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf, + textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb.final_buf, &sb.final_buf_size)) ; else - sb.final_buf = read_sha1_file(o->blob_sha1, &type, + sb.final_buf = read_sha1_file(o->blob_oid.hash, &type, &sb.final_buf_size); if (!sb.final_buf) - die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), + die(_("cannot read blob %s for path %s"), + oid_to_hex(&o->blob_oid), path); } num_read_blob++; lno = prepare_lines(&sb); if (lno && !range_list.nr) - string_list_append(&range_list, xstrdup("1")); + string_list_append(&range_list, "1"); anchor = 1; range_set_init(&ranges, range_list.nr); @@ -2790,7 +2867,9 @@ parse_done: &bottom, &top, sb.path)) usage(blame_usage); if (lno < top || ((lno || bottom) && lno < bottom)) - die("file %s has only %lu lines", path, lno); + die(Q_("file %s has only %lu line", + "file %s has only %lu lines", + lno), path, lno); if (bottom < 1) bottom = 1; if (top < 1) @@ -2828,11 +2907,11 @@ parse_done: read_mailmap(&mailmap, NULL); + assign_blame(&sb, opt); + if (!incremental) setup_pager(); - assign_blame(&sb, opt); - free(final_commit_name); if (incremental) |