#include "diff.h"
#include "diffcore.h"
#include "revision.h"
+#include "quote.h"
#include "xdiff-interface.h"
-
-static char blame_usage[] =
-"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
-" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
-" -b Show blank SHA-1 for boundary commits (Default: off)\n"
-" -l, --long Show long commit SHA1 (Default: off)\n"
-" --root Do not treat root commits as boundaries (Default: off)\n"
-" -t, --time Show raw timestamp (Default: off)\n"
-" -f, --show-name Show original filename (Default: auto)\n"
-" -n, --show-number Show original linenumber (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"
-" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n";
+#include "cache-tree.h"
+#include "string-list.h"
+#include "mailmap.h"
+#include "parse-options.h"
+
+static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
+
+static const char *blame_opt_usage[] = {
+ blame_usage,
+ "",
+ "[rev-opts] are documented in git-rev-list(1)",
+ NULL
+};
static int longest_file;
static int longest_author;
static int max_digits;
static int max_score_digits;
static int show_root;
+static int reverse;
static int blank_boundary;
+static int incremental;
+static int cmd_is_annotate;
+static int xdl_opts = XDF_NEED_MINIMAL;
+static struct string_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
char path[FLEX_ARRAY];
};
-static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+/*
+ * Given an origin, prepare mmfile_t structure to be used by the
+ * diff machinery
+ */
+static void fill_origin_blob(struct origin *o, mmfile_t *file)
{
if (!o->file.ptr) {
- char type[10];
+ enum object_type type;
num_read_blob++;
- file->ptr = read_sha1_file(o->blob_sha1, type,
+ file->ptr = read_sha1_file(o->blob_sha1, &type,
(unsigned long *)(&(file->size)));
+ if (!file->ptr)
+ die("Cannot read blob %s for path %s",
+ sha1_to_hex(o->blob_sha1),
+ o->path);
o->file = *file;
}
else
*file = o->file;
- return file->ptr;
}
+/*
+ * Origin is refcounted and usually we keep the blob contents to be
+ * reused.
+ */
static inline struct origin *origin_incref(struct origin *o)
{
if (o)
static void origin_decref(struct origin *o)
{
if (o && --o->refcnt <= 0) {
- if (o->file.ptr)
- free(o->file.ptr);
- memset(o, 0, sizeof(*o));
+ free(o->file.ptr);
free(o);
}
}
+static void drop_origin_blob(struct origin *o)
+{
+ if (o->file.ptr) {
+ free(o->file.ptr);
+ o->file.ptr = NULL;
+ }
+}
+
+/*
+ * Each group of lines is described by a blame_entry; it can be split
+ * as we pass blame to the parents. They form a linked list in the
+ * scoreboard structure, sorted by the target line number.
+ */
struct blame_entry {
struct blame_entry *prev;
struct blame_entry *next;
*/
char guilty;
+ /* true if the entry has been scanned for copies in the current parent
+ */
+ char scanned;
+
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
int s_lno;
/* how significant this entry is -- cached to avoid
- * scanning the lines over and over
+ * scanning the lines over and over.
*/
unsigned score;
};
+/*
+ * The current state of the blame assignment.
+ */
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
-
+ struct rev_info *revs;
const char *path;
- /* the contents in the final; pointed into by buf pointers of
- * blame_entries
+ /*
+ * The contents in the final image.
+ * Used by many functions to obtain contents of the nth line,
+ * indexed with scoreboard.lineno[blame_entry.lno].
*/
const char *final_buf;
unsigned long final_buf_size;
int *lineno;
};
-static int cmp_suspect(struct origin *a, struct origin *b)
+static inline int same_suspect(struct origin *a, struct origin *b)
{
- int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1);
- if (cmp)
- return cmp;
- return strcmp(a->path, b->path);
+ if (a == b)
+ return 1;
+ if (a->commit != b->commit)
+ return 0;
+ return !strcmp(a->path, b->path);
}
-#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) )
-
static void sanity_check_refcnt(struct scoreboard *);
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
static void coalesce(struct scoreboard *sb)
{
struct blame_entry *ent, *next;
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
- if (!cmp_suspect(ent->suspect, next->suspect) &&
+ if (same_suspect(ent->suspect, next->suspect) &&
ent->guilty == next->guilty &&
ent->s_lno + ent->num_lines == next->s_lno) {
ent->num_lines += next->num_lines;
sanity_check_refcnt(sb);
}
+/*
+ * Given a commit and a path in it, create a new origin structure.
+ * The callers that add blame to the scoreboard should use
+ * get_origin() to obtain shared, refcounted copy instead of calling
+ * this function directly.
+ */
static struct origin *make_origin(struct commit *commit, const char *path)
{
struct origin *o;
return o;
}
+/*
+ * Locate an existing origin or create a new one.
+ */
static struct origin *get_origin(struct scoreboard *sb,
struct commit *commit,
const char *path)
return make_origin(commit, path);
}
+/*
+ * Fill the blob_sha1 field of an origin if it hasn't, so that later
+ * call to fill_origin_blob() can use it to locate the data. blob_sha1
+ * for an origin is also used to pass the blame for the entire file to
+ * the parent to detect the case where a child's blob is identical to
+ * that of its parent's.
+ */
static int fill_blob_sha1(struct origin *origin)
{
unsigned mode;
- char type[10];
if (!is_null_sha1(origin->blob_sha1))
return 0;
origin->path,
origin->blob_sha1, &mode))
goto error_out;
- if (sha1_object_info(origin->blob_sha1, type, NULL) ||
- strcmp(type, blob_type))
+ if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
goto error_out;
return 0;
error_out:
return -1;
}
+/*
+ * We have an origin -- check if the same path exists in the
+ * parent and return an origin structure to represent it.
+ */
static struct origin *find_origin(struct scoreboard *sb,
struct commit *parent,
struct origin *origin)
const char *paths[2];
if (parent->util) {
- /* This is a freestanding copy of origin and not
- * refcounted.
+ /*
+ * Each commit object can cache one origin in that
+ * commit. This is a freestanding copy of origin and
+ * not refcounted.
*/
struct origin *cached = parent->util;
if (!strcmp(cached->path, origin->path)) {
+ /*
+ * The same path between origin and its parent
+ * without renaming -- the most common case.
+ */
porigin = get_origin(sb, parent, cached->path);
+
+ /*
+ * If the origin was newly created (i.e. get_origin
+ * would call make_origin if none is found in the
+ * scoreboard), it does not know the blob_sha1,
+ * so copy it. Otherwise porigin was in the
+ * scoreboard and already knows blob_sha1.
+ */
if (porigin->refcnt == 1)
hashcpy(porigin->blob_sha1, cached->blob_sha1);
return porigin;
* same and diff-tree is fairly efficient about this.
*/
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = 0;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
paths[0] = origin->path;
diff_tree_setup_paths(paths, &diff_opts);
if (diff_setup_done(&diff_opts) < 0)
die("diff-setup");
- diff_tree_sha1(parent->tree->object.sha1,
- origin->commit->tree->object.sha1,
- "", &diff_opts);
+
+ if (is_null_sha1(origin->commit->object.sha1))
+ do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ else
+ diff_tree_sha1(parent->tree->object.sha1,
+ origin->commit->tree->object.sha1,
+ "", &diff_opts);
diffcore_std(&diff_opts);
/* It is either one entry that says "modified", or "created",
}
}
diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
if (porigin) {
+ /*
+ * Create a freestanding copy that is not part of
+ * the refcounted origin found in the scoreboard, and
+ * cache it in the commit.
+ */
struct origin *cached;
+
cached = make_origin(porigin->commit, porigin->path);
hashcpy(cached->blob_sha1, porigin->blob_sha1);
parent->util = cached;
return porigin;
}
+/*
+ * We have an origin -- find the path that corresponds to it in its
+ * parent and return an origin structure to represent it.
+ */
static struct origin *find_rename(struct scoreboard *sb,
struct commit *parent,
struct origin *origin)
const char *paths[2];
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = origin->path;
diff_tree_setup_paths(paths, &diff_opts);
if (diff_setup_done(&diff_opts) < 0)
die("diff-setup");
- diff_tree_sha1(parent->tree->object.sha1,
- origin->commit->tree->object.sha1,
- "", &diff_opts);
+
+ if (is_null_sha1(origin->commit->object.sha1))
+ do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ else
+ diff_tree_sha1(parent->tree->object.sha1,
+ origin->commit->tree->object.sha1,
+ "", &diff_opts);
diffcore_std(&diff_opts);
for (i = 0; i < diff_queued_diff.nr; i++) {
}
}
diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
return porigin;
}
+/*
+ * Parsing of patch chunks...
+ */
struct chunk {
/* line number in postimage; up to but not including this
* line is the same as preimage
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));
state.ret->chunks = NULL;
state.ret->num = 0;
- xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb);
+ xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb);
if (state.ret->num) {
struct chunk *chunk;
return state.ret;
}
+/*
+ * 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.
+ */
static struct patch *get_patch(struct origin *parent, struct origin *origin)
{
mmfile_t file_p, file_o;
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.
+ */
static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
{
struct blame_entry *ent, *prev = NULL;
e->next->prev = e;
}
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * a malloced blame_entry that is already on the linked list of the
+ * scoreboard. The origin of dst loses a refcnt while the origin of src
+ * gains one.
+ */
static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
{
struct blame_entry *p, *n;
return sb->final_buf + sb->lineno[lno];
}
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range. it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ * <---- e ----->
+ * <------>
+ * <------------>
+ * <------------>
+ * <------------------>
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
static void split_overlap(struct blame_entry *split,
struct blame_entry *e,
int tlno, int plno, int same,
struct origin *parent)
{
- /* it is known that lines between tlno to same came from
- * parent, and e has an overlap with that range. it also is
- * known that parent's line plno corresponds to e's line tlno.
- *
- * <---- e ----->
- * <------>
- * <------------>
- * <------------>
- * <------------------>
- *
- * Potentially we need to split e into three parts; before
- * this chunk, the chunk to be blamed for parent, and after
- * that portion.
- */
int chunk_end_lno;
memset(split, 0, sizeof(struct blame_entry [3]));
chunk_end_lno = e->lno + e->num_lines;
split[1].num_lines = chunk_end_lno - split[1].lno;
+ /*
+ * if it turns out there is nothing to blame the parent for,
+ * forget about the splitting. !split[1].suspect signals this.
+ */
if (split[1].num_lines < 1)
return;
split[1].suspect = origin_incref(parent);
}
+/*
+ * split_overlap() divided an existing blame e into up to three parts
+ * in split. Adjust the linked list of blames in the scoreboard to
+ * reflect the split.
+ */
static void split_blame(struct scoreboard *sb,
struct blame_entry *split,
struct blame_entry *e)
struct blame_entry *new_entry;
if (split[0].suspect && split[2].suspect) {
- /* we need to split e into two and add another for parent */
+ /* The first part (reuse storage for the existing entry e) */
dup_entry(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(sb, new_entry);
+ /* ... and the middle part -- parent */
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
add_blame_entry(sb, new_entry);
}
else if (!split[0].suspect && !split[2].suspect)
- /* parent covers the entire area */
+ /*
+ * The parent covers the entire area; reuse storage for
+ * e and replace it with the parent.
+ */
dup_entry(e, &split[1]);
else if (split[0].suspect) {
+ /* me and then parent */
dup_entry(e, &split[0]);
new_entry = xmalloc(sizeof(*new_entry));
add_blame_entry(sb, new_entry);
}
else {
+ /* parent and then me */
dup_entry(e, &split[1]);
new_entry = xmalloc(sizeof(*new_entry));
}
}
+/*
+ * After splitting the blame, the origins used by the
+ * on-stack blame_entry should lose one refcnt each.
+ */
static void decref_split(struct blame_entry *split)
{
int i;
origin_decref(split[i].suspect);
}
+/*
+ * Helper for blame_chunk(). blame_entry e is known to overlap with
+ * the patch hunk; split it and pass blame to the parent.
+ */
static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
int tlno, int plno, int same,
struct origin *parent)
decref_split(split);
}
+/*
+ * Find the line number of the last line the target is suspected for.
+ */
static int find_last_in_target(struct scoreboard *sb, struct origin *target)
{
struct blame_entry *e;
int last_in_target = -1;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || cmp_suspect(e->suspect, target))
+ if (e->guilty || !same_suspect(e->suspect, target))
continue;
if (last_in_target < e->s_lno + e->num_lines)
last_in_target = e->s_lno + e->num_lines;
return last_in_target;
}
+/*
+ * Process one hunk from the patch between the current suspect for
+ * blame_entry e and its parent. Find and split the overlap, and
+ * pass blame to the overlapping part to the parent.
+ */
static void blame_chunk(struct scoreboard *sb,
int tlno, int plno, int same,
struct origin *target, struct origin *parent)
struct blame_entry *e;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || cmp_suspect(e->suspect, target))
+ if (e->guilty || !same_suspect(e->suspect, target))
continue;
if (same <= e->s_lno)
continue;
}
}
+/*
+ * 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
+ * which lines came from parent and pass blame for them.
+ */
static int pass_blame_to_parent(struct scoreboard *sb,
struct origin *target,
struct origin *parent)
plno = chunk->p_next;
tlno = chunk->t_next;
}
- /* rest (i.e. anything above tlno) are the same as parent */
+ /* The rest (i.e. anything after tlno) are the same as the parent */
blame_chunk(sb, tlno, plno, last_in_target, target, parent);
free_patch(patch);
return 0;
}
+/*
+ * The lines in blame_entry after splitting blames many times can become
+ * very small and trivial, and at some point it becomes pointless to
+ * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any
+ * ordinary C program, and it is not worth to say it was copied from
+ * totally unrelated file in the parent.
+ *
+ * Compute how trivial the lines in the blame_entry are.
+ */
static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
{
unsigned score;
return score;
}
+/*
+ * best_so_far[] and this[] are both a split of an existing blame_entry
+ * that passes blame to the parent. Maintain best_so_far the best split
+ * so far, by comparing this and best_so_far and copying this into
+ * bst_so_far as needed.
+ */
static void copy_split_if_better(struct scoreboard *sb,
struct blame_entry *best_so_far,
struct blame_entry *this)
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
+ * the parent.
+ */
static void find_copy_in_blob(struct scoreboard *sb,
struct blame_entry *ent,
struct origin *parent,
struct patch *patch;
int i, plno, tlno;
+ /*
+ * Prepare mmfile that contains only the lines in ent.
+ */
cp = nth_line(sb, ent->lno);
file_o.ptr = (char*) cp;
cnt = ent->num_lines;
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);
}
+/*
+ * See if lines currently target is suspected for can be attributed to
+ * parent.
+ */
static int find_move_in_parent(struct scoreboard *sb,
struct origin *target,
struct origin *parent)
while (made_progress) {
made_progress = 0;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || cmp_suspect(e->suspect, target))
+ if (e->guilty || !same_suspect(e->suspect, target) ||
+ ent_score(sb, e) < blame_move_score)
continue;
find_copy_in_blob(sb, e, parent, split, &file_p);
if (split[1].suspect &&
return 0;
}
-
struct blame_list {
struct blame_entry *ent;
struct blame_entry split[3];
};
+/*
+ * Count the number of entries the target is suspected for,
+ * and prepare a list of entry and the best split.
+ */
static struct blame_list *setup_blame_list(struct scoreboard *sb,
struct origin *target,
+ int min_score,
int *num_ents_p)
{
struct blame_entry *e;
int num_ents, i;
struct blame_list *blame_list = NULL;
- /* Count the number of entries the target is suspected for,
- * and prepare a list of entry and the best split.
- */
for (e = sb->ent, num_ents = 0; e; e = e->next)
- if (!e->guilty && !cmp_suspect(e->suspect, target))
+ if (!e->scanned && !e->guilty &&
+ same_suspect(e->suspect, target) &&
+ min_score < ent_score(sb, e))
num_ents++;
if (num_ents) {
blame_list = xcalloc(num_ents, sizeof(struct blame_list));
for (e = sb->ent, i = 0; e; e = e->next)
- if (!e->guilty && !cmp_suspect(e->suspect, target))
+ if (!e->scanned && !e->guilty &&
+ same_suspect(e->suspect, target) &&
+ min_score < ent_score(sb, e))
blame_list[i++].ent = e;
}
*num_ents_p = num_ents;
return blame_list;
}
+/*
+ * Reset the scanned status on all entries.
+ */
+static void reset_scanned_flag(struct scoreboard *sb)
+{
+ struct blame_entry *e;
+ for (e = sb->ent; e; e = e->next)
+ e->scanned = 0;
+}
+
+/*
+ * For lines target is suspected for, see if we can find code movement
+ * across file boundary from the parent commit. porigin is the path
+ * in the parent we already tried.
+ */
static int find_copy_in_parent(struct scoreboard *sb,
struct origin *target,
struct commit *parent,
struct blame_list *blame_list;
int num_ents;
- blame_list = setup_blame_list(sb, target, &num_ents);
+ blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
if (!blame_list)
return 1; /* nothing remains for this target */
diff_setup(&diff_opts);
- diff_opts.recursive = 1;
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
paths[0] = NULL;
* 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)))
- diff_opts.find_copies_harder = 1;
+ if ((opt & PICKAXE_BLAME_COPY_HARDEST)
+ || ((opt & PICKAXE_BLAME_COPY_HARDER)
+ && (!porigin || strcmp(target->path, porigin->path))))
+ DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
- diff_tree_sha1(parent->tree->object.sha1,
- target->commit->tree->object.sha1,
- "", &diff_opts);
+ if (is_null_sha1(target->commit->object.sha1))
+ do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ else
+ diff_tree_sha1(parent->tree->object.sha1,
+ target->commit->tree->object.sha1,
+ "", &diff_opts);
- if (!diff_opts.find_copies_harder)
+ if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
diffcore_std(&diff_opts);
retval = 0;
split_blame(sb, split, blame_list[j].ent);
made_progress = 1;
}
+ else
+ blame_list[j].ent->scanned = 1;
decref_split(split);
}
free(blame_list);
if (!made_progress)
break;
- blame_list = setup_blame_list(sb, target, &num_ents);
+ blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
if (!blame_list) {
retval = 1;
break;
}
}
+ reset_scanned_flag(sb);
diff_flush(&diff_opts);
-
+ diff_tree_release_paths(&diff_opts);
return retval;
}
-/* The blobs of origin and porigin exactly match, so everything
+/*
+ * The blobs of origin and porigin exactly match, so everything
* origin is suspected for can be blamed on the parent.
*/
static void pass_whole_blame(struct scoreboard *sb,
origin->file.ptr = NULL;
}
for (e = sb->ent; e; e = e->next) {
- if (cmp_suspect(e->suspect, origin))
+ if (!same_suspect(e->suspect, origin))
continue;
origin_incref(porigin);
origin_decref(e->suspect);
}
}
-#define MAXPARENT 16
+/*
+ * We pass blame from the current commit to its parents. We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+{
+ if (!reverse)
+ return commit->parents;
+ return lookup_decoration(&revs->children, &commit->object);
+}
+
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+{
+ int cnt;
+ struct commit_list *l = first_scapegoat(revs, commit);
+ for (cnt = 0; l; l = l->next)
+ cnt++;
+ return cnt;
+}
+
+#define MAXSG 16
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
{
- int i, pass;
+ struct rev_info *revs = sb->revs;
+ int i, pass, num_sg;
struct commit *commit = origin->commit;
- struct commit_list *parent;
- struct origin *parent_origin[MAXPARENT], *porigin;
-
- memset(parent_origin, 0, sizeof(parent_origin));
+ struct commit_list *sg;
+ struct origin *sg_buf[MAXSG];
+ struct origin *porigin, **sg_origin = sg_buf;
+
+ num_sg = num_scapegoats(revs, commit);
+ if (!num_sg)
+ goto finish;
+ else if (num_sg < ARRAY_SIZE(sg_buf))
+ memset(sg_buf, 0, sizeof(sg_buf));
+ else
+ sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
- /* The first pass looks for unrenamed path to optimize for
+ /*
+ * The first pass looks for unrenamed path to optimize for
* common cases, then we look for renames in the second pass.
*/
for (pass = 0; pass < 2; pass++) {
struct commit *, struct origin *);
find = pass ? find_rename : find_origin;
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct commit *p = parent->item;
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct commit *p = sg->item;
int j, same;
- if (parent_origin[i])
+ if (sg_origin[i])
continue;
if (parse_commit(p))
continue;
goto finish;
}
for (j = same = 0; j < i; j++)
- if (parent_origin[j] &&
- !hashcmp(parent_origin[j]->blob_sha1,
+ if (sg_origin[j] &&
+ !hashcmp(sg_origin[j]->blob_sha1,
porigin->blob_sha1)) {
same = 1;
break;
}
if (!same)
- parent_origin[i] = porigin;
+ sg_origin[i] = porigin;
else
origin_decref(porigin);
}
}
num_commits++;
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct origin *porigin = parent_origin[i];
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (pass_blame_to_parent(sb, origin, porigin))
}
/*
- * Optionally run "miff" to find moves in parents' files here.
+ * Optionally find moves in parents' files.
*/
if (opt & PICKAXE_BLAME_MOVE)
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct origin *porigin = parent_origin[i];
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (find_move_in_parent(sb, origin, porigin))
}
/*
- * Optionally run "ciff" to find copies from parents' files here.
+ * Optionally find copies from parents' files.
*/
if (opt & PICKAXE_BLAME_COPY)
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct origin *porigin = parent_origin[i];
- if (find_copy_in_parent(sb, origin, parent->item,
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (find_copy_in_parent(sb, origin, sg->item,
porigin, opt))
goto finish;
}
finish:
- for (i = 0; i < MAXPARENT; i++)
- origin_decref(parent_origin[i]);
-}
-
-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
-{
- while (1) {
- struct blame_entry *ent;
- struct commit *commit;
- struct origin *suspect = NULL;
-
- /* find one suspect to break down */
- for (ent = sb->ent; !suspect && ent; ent = ent->next)
- if (!ent->guilty)
- suspect = ent->suspect;
- if (!suspect)
- return; /* all done */
-
- origin_incref(suspect);
- commit = suspect->commit;
- if (!commit->object.parsed)
- parse_commit(commit);
- if (!(commit->object.flags & UNINTERESTING) &&
- !(revs->max_age != -1 && commit->date < revs->max_age))
- pass_blame(sb, suspect, opt);
- else {
- commit->object.flags |= UNINTERESTING;
- if (commit->object.parsed)
- mark_parents_uninteresting(commit);
+ for (i = 0; i < num_sg; i++) {
+ if (sg_origin[i]) {
+ drop_origin_blob(sg_origin[i]);
+ origin_decref(sg_origin[i]);
}
- /* treat root commit as boundary */
- if (!commit->parents && !show_root)
- commit->object.flags |= UNINTERESTING;
-
- /* Take responsibility for the remaining entries */
- for (ent = sb->ent; ent; ent = ent->next)
- if (!cmp_suspect(ent->suspect, suspect))
- ent->guilty = 1;
- origin_decref(suspect);
-
- if (DEBUG) /* sanity */
- sanity_check_refcnt(sb);
- }
-}
-
-static const char *format_time(unsigned long time, const char *tz_str,
- int show_raw_time)
-{
- static char time_buf[128];
- time_t t = time;
- int minutes, tz;
- struct tm *tm;
-
- if (show_raw_time) {
- sprintf(time_buf, "%lu %s", time, tz_str);
- return time_buf;
}
-
- tz = atoi(tz_str);
- minutes = tz < 0 ? -tz : tz;
- minutes = (minutes / 100)*60 + (minutes % 100);
- minutes = tz < 0 ? -minutes : minutes;
- t = time + minutes * 60;
- tm = gmtime(&t);
-
- strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
- strcat(time_buf, tz_str);
- return time_buf;
+ drop_origin_blob(origin);
+ if (sg_buf != sg_origin)
+ free(sg_origin);
}
+/*
+ * Information on commits, used for output.
+ */
struct commit_info
{
- char *author;
- char *author_mail;
+ const char *author;
+ const char *author_mail;
unsigned long author_time;
- char *author_tz;
+ const char *author_tz;
/* filled only when asked for details */
- char *committer;
- char *committer_mail;
+ const char *committer;
+ const char *committer_mail;
unsigned long committer_time;
- char *committer_tz;
+ const char *committer_tz;
- char *summary;
+ const char *summary;
};
+/*
+ * Parse author/committer line in the commit object buffer
+ */
static void get_ac_line(const char *inbuf, const char *what,
- int bufsz, char *person, char **mail,
- unsigned long *time, char **tz)
+ 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)
if (bufsz <= len) {
error_out:
/* Ugh */
- person = *mail = *tz = "(unknown)";
+ *mail = *tz = "(unknown)";
*time = 0;
return;
}
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,
static char committer_buf[1024];
static char summary_buf[1024];
- /* We've operated without save_commit_buffer, so
+ /*
+ * We've operated without save_commit_buffer, so
* we now need to populate them for output.
*/
if (!commit->buffer) {
- char type[20];
+ enum object_type type;
unsigned long size;
commit->buffer =
- read_sha1_file(commit->object.sha1, type, &size);
+ read_sha1_file(commit->object.sha1, &type, &size);
+ if (!commit->buffer)
+ die("Cannot read commit %s",
+ sha1_to_hex(commit->object.sha1));
}
ret->author = author_buf;
get_ac_line(commit->buffer, "\nauthor ",
tmp += 2;
endp = strchr(tmp, '\n');
if (!endp)
- goto error_out;
+ endp = tmp + strlen(tmp);
len = endp - tmp;
- if (len >= sizeof(summary_buf))
+ if (len >= sizeof(summary_buf) || len == 0)
goto error_out;
memcpy(summary_buf, tmp, len);
summary_buf[len] = 0;
}
+/*
+ * To allow LF and other nonportable characters in pathnames,
+ * they are c-style quoted as needed.
+ */
+static void write_filename_info(const char *path)
+{
+ printf("filename ");
+ write_name_quoted(path, stdout, '\n');
+}
+
+/*
+ * The blame_entry is found to be guilty for the range. Mark it
+ * as such, and show it in incremental output.
+ */
+static void found_guilty_entry(struct blame_entry *ent)
+{
+ if (ent->guilty)
+ return;
+ ent->guilty = 1;
+ if (incremental) {
+ struct origin *suspect = ent->suspect;
+
+ printf("%s %d %d %d\n",
+ sha1_to_hex(suspect->commit->object.sha1),
+ ent->s_lno + 1, ent->lno + 1, ent->num_lines);
+ if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+ struct commit_info ci;
+ suspect->commit->object.flags |= METAINFO_SHOWN;
+ get_commit_info(suspect->commit, &ci, 1);
+ printf("author %s\n", ci.author);
+ printf("author-mail %s\n", ci.author_mail);
+ printf("author-time %lu\n", ci.author_time);
+ printf("author-tz %s\n", ci.author_tz);
+ printf("committer %s\n", ci.committer);
+ printf("committer-mail %s\n", ci.committer_mail);
+ printf("committer-time %lu\n", ci.committer_time);
+ printf("committer-tz %s\n", ci.committer_tz);
+ printf("summary %s\n", ci.summary);
+ if (suspect->commit->object.flags & UNINTERESTING)
+ printf("boundary\n");
+ }
+ write_filename_info(suspect->path);
+ maybe_flush_or_die(stdout, "stdout");
+ }
+}
+
+/*
+ * The main loop -- while the scoreboard has lines whose true origin
+ * is still unknown, pick one blame_entry, and allow its current
+ * suspect to pass blames to its parents.
+ */
+static void assign_blame(struct scoreboard *sb, int opt)
+{
+ struct rev_info *revs = sb->revs;
+
+ while (1) {
+ struct blame_entry *ent;
+ struct commit *commit;
+ struct origin *suspect = NULL;
+
+ /* find one suspect to break down */
+ for (ent = sb->ent; !suspect && ent; ent = ent->next)
+ if (!ent->guilty)
+ suspect = ent->suspect;
+ if (!suspect)
+ return; /* all done */
+
+ /*
+ * We will use this suspect later in the loop,
+ * so hold onto it in the meantime.
+ */
+ origin_incref(suspect);
+ commit = suspect->commit;
+ if (!commit->object.parsed)
+ parse_commit(commit);
+ if (reverse ||
+ (!(commit->object.flags & UNINTERESTING) &&
+ !(revs->max_age != -1 && commit->date < revs->max_age)))
+ pass_blame(sb, suspect, opt);
+ else {
+ commit->object.flags |= UNINTERESTING;
+ if (commit->object.parsed)
+ mark_parents_uninteresting(commit);
+ }
+ /* treat root commit as boundary */
+ if (!commit->parents && !show_root)
+ commit->object.flags |= UNINTERESTING;
+
+ /* Take responsibility for the remaining entries */
+ for (ent = sb->ent; ent; ent = ent->next)
+ if (same_suspect(ent->suspect, suspect))
+ found_guilty_entry(ent);
+ origin_decref(suspect);
+
+ if (DEBUG) /* sanity */
+ sanity_check_refcnt(sb);
+ }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+ int show_raw_time)
+{
+ static char time_buf[128];
+ time_t t = time;
+ int minutes, tz;
+ struct tm *tm;
+
+ if (show_raw_time) {
+ sprintf(time_buf, "%lu %s", time, tz_str);
+ return time_buf;
+ }
+
+ tz = atoi(tz_str);
+ minutes = tz < 0 ? -tz : tz;
+ minutes = (minutes / 100)*60 + (minutes % 100);
+ minutes = tz < 0 ? -minutes : minutes;
+ t = time + minutes * 60;
+ tm = gmtime(&t);
+
+ strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+ strcat(time_buf, tz_str);
+ return time_buf;
+}
+
#define OUTPUT_ANNOTATE_COMPAT 001
#define OUTPUT_LONG_OBJECT_NAME 002
#define OUTPUT_RAW_TIMESTAMP 004
#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)
{
printf("committer-mail %s\n", ci.committer_mail);
printf("committer-time %lu\n", ci.committer_time);
printf("committer-tz %s\n", ci.committer_tz);
- printf("filename %s\n", suspect->path);
+ write_filename_info(suspect->path);
printf("summary %s\n", ci.summary);
if (suspect->commit->object.flags & UNINTERESTING)
printf("boundary\n");
}
else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
- printf("filename %s\n", suspect->path);
+ write_filename_info(suspect->path);
cp = nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
if (suspect->commit->object.flags & UNINTERESTING) {
- if (!blank_boundary) {
+ if (blank_boundary)
+ memset(hex, ' ', length);
+ else if (!cmd_is_annotate) {
length--;
putchar('^');
}
- else
- memset(hex, ' ', length);
}
printf("%.*s", length, hex);
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 {
}
}
+/*
+ * To allow quick access to the contents of nth line in the
+ * final image, prepare an index in the scoreboard.
+ */
static int prepare_lines(struct scoreboard *sb)
{
const char *buf = sb->final_buf;
return sb->num_lines;
}
+/*
+ * Add phony grafts for use with -S; this is primarily to
+ * support git-cvsserver that wants to give a linear history
+ * to its clients.
+ */
static int read_ancestry(const char *graft_file)
{
FILE *fp = fopen(graft_file, "r");
return 0;
}
+/*
+ * How many columns do we need to show line numbers in decimal?
+ */
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;
}
+/*
+ * How many columns do we need to show line numbers, authors,
+ * and filenames?
+ */
static void find_alignment(struct scoreboard *sb, int *option)
{
int longest_src_lines = 0;
max_score_digits = lineno_width(largest_score);
}
+/*
+ * For debugging -- origin is refcounted, and this asserts that
+ * we do not underflow.
+ */
static void sanity_check_refcnt(struct scoreboard *sb)
{
int baa = 0;
ent->suspect->refcnt = -ent->suspect->refcnt;
}
for (ent = sb->ent; ent; ent = ent->next) {
- /* then pick each and see if they have the the correct
- * refcnt.
+ /*
+ * ... then pick each and see if they have the the
+ * correct refcnt.
*/
int found;
struct blame_entry *e;
}
}
-static int has_path_in_work_tree(const char *path)
+/*
+ * Used for the command line parsing; check if the path exists
+ * in the working tree.
+ */
+static int has_string_in_work_tree(const char *path)
{
struct stat st;
return !lstat(path, &st);
static const char *add_prefix(const char *prefix, const char *path)
{
- if (!prefix || !prefix[0])
- return path;
- return prefix_path(prefix, strlen(prefix), path);
+ return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
+/*
+ * Parsing of (comma separated) one item in the -L option
+ */
static const char *parse_loc(const char *spec,
struct scoreboard *sb, long lno,
long begin, long *ret)
}
}
+/*
+ * Parsing of -L option
+ */
static void prepare_blame_range(struct scoreboard *sb,
const char *bottomtop,
long lno,
usage(blame_usage);
}
-static int git_blame_config(const char *var, const char *value)
+static int git_blame_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "blame.showroot")) {
show_root = git_config_bool(var, value);
blank_boundary = git_config_bool(var, value);
return 0;
}
- return git_default_config(var, value);
+ return git_default_config(var, value, cb);
+}
+
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
+static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
+{
+ struct commit *commit;
+ struct origin *origin;
+ unsigned char head_sha1[20];
+ struct strbuf buf;
+ const char *ident;
+ time_t now;
+ int size, len;
+ struct cache_entry *ce;
+ unsigned mode;
+
+ if (get_sha1("HEAD", head_sha1))
+ die("No such ref: HEAD");
+
+ time(&now);
+ commit = xcalloc(1, sizeof(*commit));
+ commit->parents = xcalloc(1, sizeof(*commit->parents));
+ commit->parents->item = lookup_commit_reference(head_sha1);
+ commit->object.parsed = 1;
+ commit->date = now;
+ commit->object.type = OBJ_COMMIT;
+
+ origin = make_origin(commit, path);
+
+ strbuf_init(&buf, 0);
+ 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)
+ die("Cannot stat %s", contents_from);
+ read_from = contents_from;
+ }
+ else {
+ if (lstat(path, &st) < 0)
+ 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:
+ if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
+ die("cannot open or read %s", read_from);
+ break;
+ case S_IFLNK:
+ if (readlink(read_from, buf.buf, buf.alloc) != fin_size)
+ die("cannot readlink %s", read_from);
+ buf.len = fin_size;
+ break;
+ default:
+ die("unsupported file type %s", read_from);
+ }
+ }
+ else {
+ /* Reading from stdin */
+ contents_from = "standard input";
+ mode = 0;
+ if (strbuf_read(&buf, 0, 0) < 0)
+ die("read error %s from stdin", strerror(errno));
+ }
+ 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);
+ commit->util = origin;
+
+ /*
+ * Read the current index, replace the path entry with
+ * origin->blob_sha1 without mucking with its mode or type
+ * bits; we are not going to write this index out -- we just
+ * want to run "diff-index --cached".
+ */
+ discard_cache();
+ read_cache();
+
+ len = strlen(path);
+ if (!mode) {
+ int pos = cache_name_pos(path, len);
+ if (0 <= pos)
+ mode = active_cache[pos]->ce_mode;
+ else
+ /* Let's not bother reading from HEAD tree */
+ mode = S_IFREG | 0644;
+ }
+ size = cache_entry_size(len);
+ ce = xcalloc(1, size);
+ hashcpy(ce->sha1, origin->blob_sha1);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = create_ce_flags(len, 0);
+ 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(active_cache_tree, path);
+
+ commit->buffer = xmalloc(400);
+ ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+ snprintf(commit->buffer, 400,
+ "tree 0000000000000000000000000000000000000000\n"
+ "parent %s\n"
+ "author %s\n"
+ "committer %s\n\n"
+ "Version of %s from %s\n",
+ sha1_to_hex(head_sha1),
+ ident, ident, path, contents_from ? contents_from : path);
+ return commit;
+}
+
+static const char *prepare_final(struct scoreboard *sb)
+{
+ int i;
+ const char *final_commit_name = NULL;
+ struct rev_info *revs = sb->revs;
+
+ /*
+ * There must be one and only one positive commit in the
+ * revs->pending array.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ 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 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;
+ }
+ return final_commit_name;
+}
+
+static const char *prepare_initial(struct scoreboard *sb)
+{
+ int i;
+ const char *final_commit_name = NULL;
+ struct rev_info *revs = sb->revs;
+
+ /*
+ * There must be one and only one negative commit, and it must be
+ * the boundary.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (!(obj->flags & UNINTERESTING))
+ continue;
+ while (obj->type == OBJ_TAG)
+ 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?",
+ 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?");
+ return final_commit_name;
+}
+
+static int blame_copy_callback(const struct option *option, const char *arg, int unset)
+{
+ int *opt = option->value;
+
+ /*
+ * -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;
+
+ if (arg)
+ blame_copy_score = parse_score(arg);
+ return 0;
+}
+
+static int blame_move_callback(const struct option *option, const char *arg, int unset)
+{
+ int *opt = option->value;
+
+ *opt |= PICKAXE_BLAME_MOVE;
+
+ if (arg)
+ blame_move_score = parse_score(arg);
+ return 0;
+}
+
+static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
+{
+ const char **bottomtop = option->value;
+ if (!arg)
+ return -1;
+ if (*bottomtop)
+ die("More than one '-L n,m' option given");
+ *bottomtop = arg;
+ return 0;
}
int cmd_blame(int argc, const char **argv, const char *prefix)
struct scoreboard sb;
struct origin *o;
struct blame_entry *ent;
- int i, seen_dashdash, unk, opt;
- long bottom, top, lno;
- int output_option = 0;
- const char *revs_file = NULL;
+ long dashdash_pos, bottom, top, lno;
const char *final_commit_name = NULL;
- char type[10];
- const char *bottomtop = NULL;
-
- git_config(git_blame_config);
+ enum object_type type;
+
+ static const char *bottomtop = NULL;
+ 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[] = {
+ OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
+ OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
+ OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
+ OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
+ OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
+ 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('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),
+ OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+ OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
+ OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
+ OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
+ { 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_END()
+ };
+
+ struct parse_opt_ctx_t ctx;
+
+ cmd_is_annotate = !strcmp(argv[0], "annotate");
+
+ git_config(git_blame_config, NULL);
+ init_revisions(&revs, NULL);
save_commit_buffer = 0;
-
- opt = 0;
- seen_dashdash = 0;
- for (unk = i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (*arg != '-')
- break;
- else if (!strcmp("-b", arg))
- blank_boundary = 1;
- else if (!strcmp("--root", arg))
- show_root = 1;
- else if (!strcmp("-c", arg))
- output_option |= OUTPUT_ANNOTATE_COMPAT;
- else if (!strcmp("-t", arg))
- output_option |= OUTPUT_RAW_TIMESTAMP;
- else if (!strcmp("-l", arg))
- output_option |= OUTPUT_LONG_OBJECT_NAME;
- else if (!strcmp("-S", arg) && ++i < argc)
- revs_file = argv[i];
- else if (!strncmp("-M", arg, 2)) {
- opt |= PICKAXE_BLAME_MOVE;
- blame_move_score = parse_score(arg+2);
+ dashdash_pos = 0;
+
+ parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
+ PARSE_OPT_KEEP_ARGV0);
+ for (;;) {
+ switch (parse_options_step(&ctx, options, blame_opt_usage)) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_DONE:
+ if (ctx.argv[0])
+ dashdash_pos = ctx.cpidx;
+ goto parse_done;
}
- else if (!strncmp("-C", arg, 2)) {
- if (opt & PICKAXE_BLAME_COPY)
- opt |= PICKAXE_BLAME_COPY_HARDER;
- opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
- blame_copy_score = parse_score(arg+2);
- }
- else if (!strncmp("-L", arg, 2)) {
- if (!arg[2]) {
- if (++i >= argc)
- usage(blame_usage);
- arg = argv[i];
- }
- else
- arg += 2;
- if (bottomtop)
- die("More than one '-L n,m' option given");
- bottomtop = arg;
- }
- else if (!strcmp("--score-debug", arg))
- output_option |= OUTPUT_SHOW_SCORE;
- else if (!strcmp("-f", arg) ||
- !strcmp("--show-name", arg))
- output_option |= OUTPUT_SHOW_NAME;
- else if (!strcmp("-n", arg) ||
- !strcmp("--show-number", arg))
- output_option |= OUTPUT_SHOW_NUMBER;
- else if (!strcmp("-p", arg) ||
- !strcmp("--porcelain", arg))
- output_option |= OUTPUT_PORCELAIN;
- else if (!strcmp("--", arg)) {
- seen_dashdash = 1;
- i++;
- break;
+
+ if (!strcmp(ctx.argv[0], "--reverse")) {
+ ctx.argv[0] = "--children";
+ reverse = 1;
}
- else
- argv[unk++] = arg;
+ parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
+parse_done:
+ argc = parse_options_end(&ctx);
if (!blame_move_score)
blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
if (!blame_copy_score)
blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
- /* We have collected options unknown to us in argv[1..unk]
+ /*
+ * We have collected options unknown to us in argv[1..unk]
* which are to be passed to revision machinery if we are
- * going to do the "bottom" procesing.
+ * going to do the "bottom" processing.
*
* The remaining are:
*
- * (1) if seen_dashdash, its either
- * "-options -- <path>" or
- * "-options -- <path> <rev>".
- * but the latter is allowed only if there is no
- * options that we passed to revision machinery.
+ * (1) if dashdash_pos != 0, its either
+ * "blame [revisions] -- <path>" or
+ * "blame -- <path> <rev>"
*
- * (2) otherwise, we may have "--" somewhere later and
- * might be looking at the first one of multiple 'rev'
- * parameters (e.g. " master ^next ^maint -- path").
- * See if there is a dashdash first, and give the
- * arguments before that to revision machinery.
- * After that there must be one 'path'.
+ * (2) otherwise, its one of the two:
+ * "blame [revisions] <path>"
+ * "blame <path> <rev>"
*
- * (3) otherwise, its one of the three:
- * "-options <path> <rev>"
- * "-options <rev> <path>"
- * "-options <path>"
- * but again the first one is allowed only if
- * there is no options that we passed to revision
- * machinery.
+ * Note that we must strip out <path> from the arguments: we do not
+ * want the path pruning but we may want "bottom" processing.
*/
-
- if (seen_dashdash) {
- /* (1) */
- if (argc <= i)
- usage(blame_usage);
- path = add_prefix(prefix, argv[i]);
- if (i + 1 == argc - 1) {
- if (unk != 1)
- usage(blame_usage);
- argv[unk++] = argv[i + 1];
+ if (dashdash_pos) {
+ switch (argc - dashdash_pos - 1) {
+ case 2: /* (1b) */
+ if (argc != 4)
+ usage_with_options(blame_opt_usage, options);
+ /* reorder for the new way: <rev> -- <path> */
+ argv[1] = argv[3];
+ argv[3] = argv[2];
+ argv[2] = "--";
+ /* FALLTHROUGH */
+ case 1: /* (1a) */
+ path = add_prefix(prefix, argv[--argc]);
+ argv[argc] = NULL;
+ break;
+ default:
+ usage_with_options(blame_opt_usage, options);
}
- else if (i + 1 != argc)
- /* garbage at end */
- usage(blame_usage);
- }
- else {
- int j;
- for (j = i; !seen_dashdash && j < argc; j++)
- if (!strcmp(argv[j], "--"))
- seen_dashdash = j;
- if (seen_dashdash) {
- if (seen_dashdash + 1 != argc - 1)
- usage(blame_usage);
- path = add_prefix(prefix, argv[seen_dashdash + 1]);
- for (j = i; j < seen_dashdash; j++)
- argv[unk++] = argv[j];
+ } else {
+ if (argc < 2)
+ usage_with_options(blame_opt_usage, options);
+ path = add_prefix(prefix, argv[argc - 1]);
+ if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+ path = add_prefix(prefix, argv[1]);
+ argv[1] = argv[2];
}
- else {
- /* (3) */
- path = add_prefix(prefix, argv[i]);
- if (i + 1 == argc - 1) {
- final_commit_name = argv[i + 1];
-
- /* if (unk == 1) we could be getting
- * old-style
- */
- if (unk == 1 && !has_path_in_work_tree(path)) {
- path = add_prefix(prefix, argv[i + 1]);
- final_commit_name = argv[i];
- }
- }
- else if (i != argc - 1)
- usage(blame_usage); /* garbage at end */
+ argv[argc - 1] = "--";
- if (!has_path_in_work_tree(path))
- die("cannot stat path %s: %s",
- path, strerror(errno));
- }
+ setup_work_tree();
+ if (!has_string_in_work_tree(path))
+ die("cannot stat path %s: %s", path, strerror(errno));
}
- if (final_commit_name)
- argv[unk++] = final_commit_name;
-
- /* Now we got rev and path. We do not want the path pruning
- * but we may want "bottom" processing.
- */
- argv[unk++] = "--"; /* terminate the rev name */
- argv[unk] = NULL;
-
- init_revisions(&revs, NULL);
- setup_revisions(unk, argv, &revs, "HEAD");
+ setup_revisions(argc, argv, &revs, NULL);
memset(&sb, 0, sizeof(sb));
- /* There must be one and only one positive commit in the
- * revs->pending array.
- */
- for (i = 0; i < revs.pending.nr; i++) {
- struct object *obj = revs.pending.objects[i].item;
- if (obj->flags & UNINTERESTING)
- continue;
- while (obj->type == OBJ_TAG)
- 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 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;
- }
+ sb.revs = &revs;
+ if (!reverse)
+ final_commit_name = prepare_final(&sb);
+ else if (contents_from)
+ die("--contents and --children do not blend well.");
+ else
+ final_commit_name = prepare_initial(&sb);
if (!sb.final) {
- /* "--not A B -- path" without anything positive */
- unsigned char head_sha1[20];
-
- final_commit_name = "HEAD";
- if (get_sha1(final_commit_name, head_sha1))
- die("No such ref: HEAD");
- sb.final = lookup_commit_reference(head_sha1);
- add_pending_object(&revs, &(sb.final->object), "HEAD");
+ /*
+ * "--not A B -- path" without anything positive;
+ * do not default to HEAD, but use the working tree
+ * or "--contents".
+ */
+ setup_work_tree();
+ sb.final = fake_working_tree_commit(path, contents_from);
+ add_pending_object(&revs, &(sb.final->object), ":");
}
+ else if (contents_from)
+ die("Cannot use --contents with final commit object name");
- /* If we have bottom, this will mark the ancestors of the
+ /*
+ * If we have bottom, this will mark the ancestors of the
* bottom commits we would reach while traversing as
* uninteresting.
*/
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
- o = get_origin(&sb, sb.final, path);
- if (fill_blob_sha1(o))
- die("no such path %s in %s", path, final_commit_name);
+ if (is_null_sha1(sb.final->object.sha1)) {
+ char *buf;
+ o = sb.final->util;
+ buf = xmalloc(o->file.size + 1);
+ memcpy(buf, o->file.ptr, o->file.size + 1);
+ sb.final_buf = buf;
+ sb.final_buf_size = o->file.size;
+ }
+ else {
+ o = get_origin(&sb, sb.final, path);
+ if (fill_blob_sha1(o))
+ die("no such path %s in %s", path, final_commit_name);
- sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size);
+ sb.final_buf = read_sha1_file(o->blob_sha1, &type,
+ &sb.final_buf_size);
+ if (!sb.final_buf)
+ die("Cannot read blob %s for path %s",
+ sha1_to_hex(o->blob_sha1),
+ path);
+ }
num_read_blob++;
lno = prepare_lines(&sb);
die("reading graft file %s failed: %s",
revs_file, strerror(errno));
- assign_blame(&sb, &revs, opt);
+ read_mailmap(&mailmap, ".mailmap", NULL);
+
+ if (!incremental)
+ setup_pager();
+
+ assign_blame(&sb, opt);
+
+ if (incremental)
+ return 0;
coalesce(&sb);
ent = e;
}
- if (DEBUG) {
+ if (show_stats) {
printf("num read blob: %d\n", num_read_blob);
printf("num get patch: %d\n", num_get_patch);
printf("num commits: %d\n", num_commits);