git/builtin/blame.c

2908 lines
79 KiB
C
Raw Normal View History

/*
* Blame
*
* Copyright (c) 2006, 2014 by its authors
* See COPYING for licensing conditions
*/
#include "cache.h"
#include "refs.h"
#include "builtin.h"
#include "commit.h"
#include "tag.h"
#include "tree-walk.h"
#include "diff.h"
#include "diffcore.h"
#include "revision.h"
#include "quote.h"
#include "xdiff-interface.h"
#include "cache-tree.h"
#include "string-list.h"
#include "mailmap.h"
#include "mergesort.h"
#include "parse-options.h"
#include "prio-queue.h"
#include "utf8.h"
#include "userdiff.h"
#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>");
static const char *blame_opt_usage[] = {
blame_usage,
"",
N_("<rev-opts> are documented in git-rev-list(1)"),
NULL
};
static int longest_file;
static int longest_author;
static int max_orig_digits;
static int max_digits;
static int max_score_digits;
static int show_root;
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
static int reverse;
static int blank_boundary;
static int incremental;
static int xdl_opts;
static int abbrev = -1;
static int no_whole_file_rename;
static int show_progress;
convert "enum date_mode" into a struct In preparation for adding date modes that may carry extra information beyond the mode itself, this patch converts the date_mode enum into a struct. Most of the conversion is fairly straightforward; we pass the struct as a pointer and dereference the type field where necessary. Locations that declare a date_mode can use a "{}" constructor. However, the tricky case is where we use the enum labels as constants, like: show_date(t, tz, DATE_NORMAL); Ideally we could say: show_date(t, tz, &{ DATE_NORMAL }); but of course C does not allow that. Likewise, we cannot cast the constant to a struct, because we need to pass an actual address. Our options are basically: 1. Manually add a "struct date_mode d = { DATE_NORMAL }" definition to each caller, and pass "&d". This makes the callers uglier, because they sometimes do not even have their own scope (e.g., they are inside a switch statement). 2. Provide a pre-made global "date_normal" struct that can be passed by address. We'd also need "date_rfc2822", "date_iso8601", and so forth. But at least the ugliness is defined in one place. 3. Provide a wrapper that generates the correct struct on the fly. The big downside is that we end up pointing to a single global, which makes our wrapper non-reentrant. But show_date is already not reentrant, so it does not matter. This patch implements 3, along with a minor macro to keep the size of the callers sane. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-26 00:55:02 +08:00
static struct date_mode blame_date_mode = { DATE_ISO8601 };
static size_t blame_date_width;
static struct string_list mailmap = STRING_LIST_INIT_NODUP;
#ifndef DEBUG
#define DEBUG 0
#endif
/* stats */
static int num_read_blob;
static int num_get_patch;
static int num_commits;
#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
* is not passed to the parent using move/copy logic.
*/
static unsigned blame_move_score;
static unsigned blame_copy_score;
#define BLAME_DEFAULT_MOVE_SCORE 20
#define BLAME_DEFAULT_COPY_SCORE 40
/* Remember to update object flag allocation in object.h */
#define METAINFO_SHOWN (1u<<12)
#define MORE_THAN_ONE_PATH (1u<<13)
/*
* One blob in a commit that is being suspected
*/
struct blame_origin {
int refcnt;
/* Record preceding blame record for this blob */
struct blame_origin *previous;
/* origins are put in a list linked via `next' hanging off the
* corresponding commit's util field in order to make finding
* them fast. The presence in this chain does not count
* towards the origin's reference count. It is tempting to
* let it count as long as the commit is pending examination,
* but even under circumstances where the commit will be
* present multiple times in the priority queue of unexamined
* commits, processing the first instance will not leave any
* work requiring the origin data for the second instance. An
* interspersed commit changing that would have to be
* preexisting with a different ancestry and with the same
* commit date in order to wedge itself between two instances
* of the same commit in the priority queue _and_ produce
* blame entries relevant for it. While we don't want to let
* us get tripped up by this case, it certainly does not seem
* worth optimizing for.
*/
struct blame_origin *next;
struct commit *commit;
/* `suspects' contains blame entries that may be attributed to
* this origin's commit or to parent commits. When a commit
* is being processed, all suspects will be moved, either by
* assigning them to an origin in a different commit, or by
* shipping them to the scoreboard's ent list because they
* cannot be attributed to a different commit.
*/
struct blame_entry *suspects;
mmfile_t file;
struct object_id blob_oid;
unsigned mode;
/* guilty gets set when shipping any suspects to the final
* blame list instead of other commits
*/
char guilty;
char path[FLEX_ARRAY];
};
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};
xdemitconf_t xecfg = {0};
xdemitcb_t ecb = {NULL};
xpp.flags = xdl_opts;
xecfg.hunk_func = hunk_func;
ecb.priv = cb_data;
return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
}
/*
* Given an origin, prepare mmfile_t structure to be used by the
* diff machinery
*/
static void fill_origin_blob(struct diff_options *opt,
struct blame_origin *o, mmfile_t *file)
{
if (!o->file.ptr) {
enum object_type type;
unsigned long file_size;
num_read_blob++;
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
;
else
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",
oid_to_hex(&o->blob_oid),
o->path);
o->file = *file;
}
else
*file = o->file;
}
/*
* Origin is refcounted and usually we keep the blob contents to be
* reused.
*/
static inline struct blame_origin *blame_origin_incref(struct blame_origin *o)
{
if (o)
o->refcnt++;
return o;
}
static void blame_origin_decref(struct blame_origin *o)
{
if (o && --o->refcnt <= 0) {
struct blame_origin *p, *l = NULL;
if (o->previous)
blame_origin_decref(o->previous);
Avoid unnecessary "if-before-free" tests. This change removes all obvious useless if-before-free tests. E.g., it replaces code like this: if (some_expression) free (some_expression); with the now-equivalent: free (some_expression); It is equivalent not just because POSIX has required free(NULL) to work for a long time, but simply because it has worked for so long that no reasonable porting target fails the test. Here's some evidence from nearly 1.5 years ago: http://www.winehq.org/pipermail/wine-patches/2006-October/031544.html FYI, the change below was prepared by running the following: git ls-files -z | xargs -0 \ perl -0x3b -pi -e \ 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)\s+(free\s*\(\s*\1\s*\))/$2/s' Note however, that it doesn't handle brace-enclosed blocks like "if (x) { free (x); }". But that's ok, since there were none like that in git sources. Beware: if you do use the above snippet, note that it can produce syntactically invalid C code. That happens when the affected "if"-statement has a matching "else". E.g., it would transform this if (x) free (x); else foo (); into this: free (x); else foo (); There were none of those here, either. If you're interested in automating detection of the useless tests, you might like the useless-if-before-free script in gnulib: [it *does* detect brace-enclosed free statements, and has a --name=S option to make it detect free-like functions with different names] http://git.sv.gnu.org/gitweb/?p=gnulib.git;a=blob;f=build-aux/useless-if-before-free Addendum: Remove one more (in imap-send.c), spotted by Jean-Luc Herren <jlh@gmx.ch>. Signed-off-by: Jim Meyering <meyering@redhat.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-02-01 01:26:32 +08:00
free(o->file.ptr);
/* Should be present exactly once in commit chain */
for (p = o->commit->util; p; l = p, p = p->next) {
if (p == o) {
if (l)
l->next = p->next;
else
o->commit->util = p->next;
free(o);
return;
}
}
die("internal error in blame_origin_decref");
}
}
static void drop_origin_blob(struct blame_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 are arranged in linked lists
* kept as `suspects' of some unprocessed origin, or entered (when the
* blame origin has been finalized) into the scoreboard structure.
* While the scoreboard structure is only sorted at the end of
* processing (according to final image line number), the lists
* attached to an origin are sorted by the target line number.
*/
struct blame_entry {
struct blame_entry *next;
/* the first line of this group in the final image;
* internally all line numbers are 0 based.
*/
int lno;
/* how many lines this group has */
int num_lines;
/* the commit that introduced this group into the final image */
struct blame_origin *suspect;
/* 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.
*/
unsigned score;
};
/*
* Any merge of blames happens on lists of blames that arrived via
* different parents in a single suspect. In this case, we want to
* sort according to the suspect line numbers as opposed to the final
* image line numbers. The function body is somewhat longish because
* it avoids unnecessary writes.
*/
static struct blame_entry *blame_merge(struct blame_entry *list1,
struct blame_entry *list2)
{
struct blame_entry *p1 = list1, *p2 = list2,
**tail = &list1;
if (!p1)
return p2;
if (!p2)
return p1;
if (p1->s_lno <= p2->s_lno) {
do {
tail = &p1->next;
if ((p1 = *tail) == NULL) {
*tail = p2;
return list1;
}
} while (p1->s_lno <= p2->s_lno);
}
for (;;) {
*tail = p2;
do {
tail = &p2->next;
if ((p2 = *tail) == NULL) {
*tail = p1;
return list1;
}
} while (p1->s_lno > p2->s_lno);
*tail = p1;
do {
tail = &p1->next;
if ((p1 = *tail) == NULL) {
*tail = p2;
return list1;
}
} while (p1->s_lno <= p2->s_lno);
}
}
static void *get_next_blame(const void *p)
{
return ((struct blame_entry *)p)->next;
}
static void set_next_blame(void *p1, void *p2)
{
((struct blame_entry *)p1)->next = p2;
}
/*
* Final image line numbers are all different, so we don't need a
* three-way comparison here.
*/
static int compare_blame_final(const void *p1, const void *p2)
{
return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno
? 1 : -1;
}
static int compare_blame_suspect(const void *p1, const void *p2)
{
const struct blame_entry *s1 = p1, *s2 = p2;
/*
* to allow for collating suspects, we sort according to the
* respective pointer value as the primary sorting criterion.
* The actual relation is pretty unimportant as long as it
* establishes a total order. Comparing as integers gives us
* that.
*/
if (s1->suspect != s2->suspect)
return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1;
if (s1->s_lno == s2->s_lno)
return 0;
return s1->s_lno > s2->s_lno ? 1 : -1;
}
static struct blame_entry *blame_sort(struct blame_entry *head,
int (*compare_fn)(const void *, const void *))
{
return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn);
}
static int compare_commits_by_reverse_commit_date(const void *a,
const void *b,
void *c)
{
return -compare_commits_by_commit_date(a, b, c);
}
/*
* The current state of the blame assignment.
*/
struct blame_scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
/* Priority queue for commits with unassigned blame records */
struct prio_queue commits;
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
struct rev_info *revs;
const char *path;
/*
* 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;
/* linked list of blames */
struct blame_entry *ent;
/* look-up a line in the final buffer */
int num_lines;
int *lineno;
};
static void sanity_check_refcnt(struct blame_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 blame_coalesce(struct blame_scoreboard *sb)
{
struct blame_entry *ent, *next;
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
if (ent->suspect == next->suspect &&
ent->s_lno + ent->num_lines == next->s_lno) {
ent->num_lines += next->num_lines;
ent->next = next->next;
blame_origin_decref(next->suspect);
free(next);
ent->score = 0;
next = ent; /* again */
}
}
if (DEBUG) /* sanity */
sanity_check_refcnt(sb);
}
/*
* Merge the given sorted list of blames into a preexisting origin.
* If there were no previous blames to that commit, it is entered into
* the commit priority queue of the score board.
*/
static void queue_blames(struct blame_scoreboard *sb, struct blame_origin *porigin,
struct blame_entry *sorted)
{
if (porigin->suspects)
porigin->suspects = blame_merge(porigin->suspects, sorted);
else {
struct blame_origin *o;
for (o = porigin->commit->util; o; o = o->next) {
if (o->suspects) {
porigin->suspects = sorted;
return;
}
}
porigin->suspects = sorted;
prio_queue_put(&sb->commits, porigin->commit);
}
}
/*
* 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 blame_origin *make_origin(struct commit *commit, const char *path)
{
struct blame_origin *o;
FLEX_ALLOC_STR(o, path, path);
o->commit = commit;
o->refcnt = 1;
o->next = commit->util;
commit->util = o;
return o;
}
/*
* Locate an existing origin or create a new one.
* This moves the origin to front position in the commit util list.
*/
static struct blame_origin *get_origin(struct commit *commit, const char *path)
{
struct blame_origin *o, *l;
for (o = commit->util, l = NULL; o; l = o, o = o->next) {
if (!strcmp(o->path, path)) {
/* bump to front */
if (l) {
l->next = o->next;
o->next = commit->util;
commit->util = o;
}
return blame_origin_incref(o);
}
}
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.
*
* This also fills origin->mode for corresponding tree path.
*/
static int fill_blob_sha1_and_mode(struct blame_origin *origin)
{
if (!is_null_oid(&origin->blob_oid))
return 0;
if (get_tree_entry(origin->commit->object.oid.hash,
origin->path,
origin->blob_oid.hash, &origin->mode))
goto error_out;
if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB)
goto error_out;
return 0;
error_out:
oidclr(&origin->blob_oid);
origin->mode = S_IFINVALID;
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 blame_origin *find_origin(struct commit *parent,
struct blame_origin *origin)
{
struct blame_origin *porigin;
struct diff_options diff_opts;
const char *paths[2];
/* First check any existing origins */
for (porigin = parent->util; porigin; porigin = porigin->next)
if (!strcmp(porigin->path, origin->path)) {
/*
* The same path between origin and its parent
* without renaming -- the most common case.
*/
return blame_origin_incref (porigin);
}
/* See if the origin->path is different between parent
* and origin first. Most of the time they are the
* same and diff-tree is fairly efficient about this.
*/
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = 0;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
paths[0] = origin->path;
paths[1] = NULL;
parse_pathspec(&diff_opts.pathspec,
PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
PATHSPEC_LITERAL_PATH, "", paths);
diff_setup_done(&diff_opts);
if (is_null_oid(&origin->commit->object.oid))
do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
else
diff_tree_sha1(parent->tree->object.oid.hash,
origin->commit->tree->object.oid.hash,
"", &diff_opts);
diffcore_std(&diff_opts);
if (!diff_queued_diff.nr) {
/* The path is the same as parent */
porigin = get_origin(parent, origin->path);
oidcpy(&porigin->blob_oid, &origin->blob_oid);
porigin->mode = origin->mode;
} else {
/*
* Since origin->path is a pathspec, if the parent
* commit had it as a directory, we will see a whole
* bunch of deletion of files in the directory that we
* do not care about.
*/
int i;
struct diff_filepair *p = NULL;
for (i = 0; i < diff_queued_diff.nr; i++) {
const char *name;
p = diff_queued_diff.queue[i];
name = p->one->path ? p->one->path : p->two->path;
if (!strcmp(name, origin->path))
break;
}
if (!p)
die("internal error in blame::find_origin");
switch (p->status) {
default:
die("internal error in blame::find_origin (%c)",
p->status);
case 'M':
porigin = get_origin(parent, origin->path);
oidcpy(&porigin->blob_oid, &p->one->oid);
porigin->mode = p->one->mode;
break;
case 'A':
case 'T':
/* Did not exist in parent, or type changed */
break;
}
}
diff_flush(&diff_opts);
clear_pathspec(&diff_opts.pathspec);
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 blame_origin *find_rename(struct commit *parent,
struct blame_origin *origin)
{
struct blame_origin *porigin = NULL;
struct diff_options diff_opts;
int i;
diff_setup(&diff_opts);
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_setup_done(&diff_opts);
if (is_null_oid(&origin->commit->object.oid))
do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
else
diff_tree_sha1(parent->tree->object.oid.hash,
origin->commit->tree->object.oid.hash,
"", &diff_opts);
diffcore_std(&diff_opts);
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
if ((p->status == 'R' || p->status == 'C') &&
!strcmp(p->two->path, origin->path)) {
porigin = get_origin(parent, p->one->path);
oidcpy(&porigin->blob_oid, &p->one->oid);
porigin->mode = p->one->mode;
break;
}
}
diff_flush(&diff_opts);
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,
const struct blame_entry *src)
{
struct blame_entry *e = xmalloc(sizeof(*e));
memcpy(e, src, sizeof(*e));
blame_origin_incref(e->suspect);
e->next = **queue;
**queue = e;
*queue = &e->next;
}
/*
* src typically is on-stack; we want to copy the information in it to
* a malloced blame_entry that gets added to the given queue. The
* origin of dst loses a refcnt.
*/
static void dup_entry(struct blame_entry ***queue,
struct blame_entry *dst, struct blame_entry *src)
{
blame_origin_incref(src->suspect);
blame_origin_decref(dst->suspect);
memcpy(dst, src, sizeof(*src));
dst->next = **queue;
**queue = dst;
*queue = &dst->next;
}
static const char *blame_nth_line(struct blame_scoreboard *sb, long lno)
{
return sb->final_buf + sb->lineno[lno];
}
static const char *nth_line_cb(void *data, long lno)
{
return blame_nth_line((struct blame_scoreboard *)data, 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 blame_origin *parent)
{
int chunk_end_lno;
memset(split, 0, sizeof(struct blame_entry [3]));
if (e->s_lno < tlno) {
/* there is a pre-chunk part not blamed on parent */
split[0].suspect = blame_origin_incref(e->suspect);
split[0].lno = e->lno;
split[0].s_lno = e->s_lno;
split[0].num_lines = tlno - e->s_lno;
split[1].lno = e->lno + tlno - e->s_lno;
split[1].s_lno = plno;
}
else {
split[1].lno = e->lno;
split[1].s_lno = plno + (e->s_lno - tlno);
}
if (same < e->s_lno + e->num_lines) {
/* there is a post-chunk part not blamed on parent */
split[2].suspect = blame_origin_incref(e->suspect);
split[2].lno = e->lno + (same - e->s_lno);
split[2].s_lno = e->s_lno + (same - e->s_lno);
split[2].num_lines = e->s_lno + e->num_lines - same;
chunk_end_lno = split[2].lno;
}
else
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 = blame_origin_incref(parent);
}
/*
* split_overlap() divided an existing blame e into up to three parts
* in split. Any assigned blame is moved to queue to
* reflect the split.
*/
static void split_blame(struct blame_entry ***blamed,
struct blame_entry ***unblamed,
struct blame_entry *split,
struct blame_entry *e)
{
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 */
add_blame_entry(unblamed, &split[2]);
/* ... and the middle part -- parent */
add_blame_entry(blamed, &split[1]);
}
else if (!split[0].suspect && !split[2].suspect)
/*
* The parent covers the entire area; reuse storage for
* e and replace it with the parent.
*/
dup_entry(blamed, e, &split[1]);
else if (split[0].suspect) {
/* me and then parent */
dup_entry(unblamed, e, &split[0]);
add_blame_entry(blamed, &split[1]);
}
else {
/* parent and then me */
dup_entry(blamed, e, &split[1]);
add_blame_entry(unblamed, &split[2]);
}
}
/*
* 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;
for (i = 0; i < 3; i++)
blame_origin_decref(split[i].suspect);
}
/*
* reverse_blame reverses the list given in head, appending tail.
* That allows us to build lists in reverse order, then reverse them
* afterwards. This can be faster than building the list in proper
* order right away. The reason is that building in proper order
* requires writing a link in the _previous_ element, while building
* in reverse order just requires placing the list head into the
* _current_ element.
*/
static struct blame_entry *reverse_blame(struct blame_entry *head,
struct blame_entry *tail)
{
while (head) {
struct blame_entry *next = head->next;
head->next = tail;
tail = head;
head = next;
}
return tail;
}
/*
* Process one hunk from the patch between the current suspect for
* blame_entry e and its parent. This first blames any unfinished
* entries before the chunk (which is where target and parent start
* differing) on the parent, and then splits blame entries at the
* start and at the end of the difference region. Since use of -M and
* -C options may lead to overlapping/duplicate source line number
* ranges, all we can rely on from sorting/merging is the order of the
* first suspect line number.
*/
static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
int tlno, int offset, int same,
struct blame_origin *parent)
{
struct blame_entry *e = **srcq;
struct blame_entry *samep = NULL, *diffp = NULL;
while (e && e->s_lno < tlno) {
struct blame_entry *next = e->next;
/*
* current record starts before differing portion. If
* it reaches into it, we need to split it up and
* examine the second part separately.
*/
if (e->s_lno + e->num_lines > tlno) {
/* Move second half to a new record */
int len = tlno - e->s_lno;
struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
n->suspect = e->suspect;
n->lno = e->lno + len;
n->s_lno = e->s_lno + len;
n->num_lines = e->num_lines - len;
e->num_lines = len;
e->score = 0;
/* Push new record to diffp */
n->next = diffp;
diffp = n;
} else
blame_origin_decref(e->suspect);
/* Pass blame for everything before the differing
* chunk to the parent */
e->suspect = blame_origin_incref(parent);
e->s_lno += offset;
e->next = samep;
samep = e;
e = next;
}
/*
* As we don't know how much of a common stretch after this
* diff will occur, the currently blamed parts are all that we
* can assign to the parent for now.
*/
if (samep) {
**dstq = reverse_blame(samep, **dstq);
*dstq = &samep->next;
}
/*
* Prepend the split off portions: everything after e starts
* after the blameable portion.
*/
e = reverse_blame(diffp, e);
/*
* Now retain records on the target while parts are different
* from the parent.
*/
samep = NULL;
diffp = NULL;
while (e && e->s_lno < same) {
struct blame_entry *next = e->next;
/*
* If current record extends into sameness, need to split.
*/
if (e->s_lno + e->num_lines > same) {
/*
* Move second half to a new record to be
* processed by later chunks
*/
int len = same - e->s_lno;
struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
n->suspect = blame_origin_incref(e->suspect);
n->lno = e->lno + len;
n->s_lno = e->s_lno + len;
n->num_lines = e->num_lines - len;
e->num_lines = len;
e->score = 0;
/* Push new record to samep */
n->next = samep;
samep = n;
}
e->next = diffp;
diffp = e;
e = next;
}
**srcq = reverse_blame(diffp, reverse_blame(samep, e));
/* Move across elements that are in the unblamable portion */
if (diffp)
*srcq = &diffp->next;
}
struct blame_chunk_cb_data {
struct blame_origin *parent;
long offset;
struct blame_entry **dstq;
struct blame_entry **srcq;
};
/* diff chunks are from parent to target */
static int blame_chunk_cb(long start_a, long count_a,
long start_b, long count_b, void *data)
{
struct blame_chunk_cb_data *d = data;
if (start_a - start_b != d->offset)
die("internal error in blame::blame_chunk_cb");
blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b,
start_b + count_b, d->parent);
d->offset = start_a + count_a - (start_b + count_b);
return 0;
}
/*
* 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 void pass_blame_to_parent(struct blame_scoreboard *sb,
struct blame_origin *target,
struct blame_origin *parent)
{
mmfile_t file_p, file_o;
struct blame_chunk_cb_data d;
struct blame_entry *newdest = NULL;
if (!target->suspects)
return; /* nothing remains for this target */
d.parent = parent;
d.offset = 0;
d.dstq = &newdest; d.srcq = &target->suspects;
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
num_get_patch++;
if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d))
react to errors in xdi_diff When we call into xdiff to perform a diff, we generally lose the return code completely. Typically by ignoring the return of our xdi_diff wrapper, but sometimes we even propagate that return value up and then ignore it later. This can lead to us silently producing incorrect diffs (e.g., "git log" might produce no output at all, not even a diff header, for a content-level diff). In practice this does not happen very often, because the typical reason for xdiff to report failure is that it malloc() failed (it uses straight malloc, and not our xmalloc wrapper). But it could also happen when xdiff triggers one our callbacks, which returns an error (e.g., outf() in builtin/rerere.c tries to report a write failure in this way). And the next patch also plans to add more failure modes. Let's notice an error return from xdiff and react appropriately. In most of the diff.c code, we can simply die(), which matches the surrounding code (e.g., that is what we do if we fail to load a file for diffing in the first place). This is not that elegant, but we are probably better off dying to let the user know there was a problem, rather than simply generating bogus output. We could also just die() directly in xdi_diff, but the callers typically have a bit more context, and can provide a better message (and if we do later decide to pass errors up, we're one step closer to doing so). There is one interesting case, which is in diff_grep(). Here if we cannot generate the diff, there is nothing to match, and we silently return "no hits". This is actually what the existing code does already, but we make it a little more explicit. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-25 07:12:23 +08:00
die("unable to generate diff (%s -> %s)",
oid_to_hex(&parent->commit->object.oid),
oid_to_hex(&target->commit->object.oid));
/* The rest are the same as the parent */
blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
*d.dstq = NULL;
queue_blames(sb, parent, newdest);
return;
}
/*
* 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 blame_entry_score(struct blame_scoreboard *sb, struct blame_entry *e)
{
unsigned score;
const char *cp, *ep;
if (e->score)
return e->score;
score = 1;
cp = blame_nth_line(sb, e->lno);
ep = blame_nth_line(sb, e->lno + e->num_lines);
while (cp < ep) {
unsigned ch = *((unsigned char *)cp);
if (isalnum(ch))
score++;
cp++;
}
e->score = 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 blame_scoreboard *sb,
struct blame_entry *best_so_far,
struct blame_entry *this)
{
int i;
if (!this[1].suspect)
return;
if (best_so_far[1].suspect) {
if (blame_entry_score(sb, &this[1]) < blame_entry_score(sb, &best_so_far[1]))
return;
}
for (i = 0; i < 3; i++)
blame_origin_incref(this[i].suspect);
decref_split(best_so_far);
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 blame_scoreboard *sb,
struct blame_entry *ent,
int tlno, int plno, int same,
struct blame_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);
}
}
struct handle_split_cb_data {
struct blame_scoreboard *sb;
struct blame_entry *ent;
struct blame_origin *parent;
struct blame_entry *split;
long plno;
long tlno;
};
static int handle_split_cb(long start_a, long count_a,
long start_b, long count_b, void *data)
{
struct handle_split_cb_data *d = data;
handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
d->split);
d->plno = start_a + count_a;
d->tlno = start_b + count_b;
return 0;
}
/*
* 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 blame_scoreboard *sb,
struct blame_entry *ent,
struct blame_origin *parent,
struct blame_entry *split,
mmfile_t *file_p)
{
const char *cp;
mmfile_t file_o;
struct handle_split_cb_data d;
memset(&d, 0, sizeof(d));
d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
/*
* Prepare mmfile that contains only the lines in ent.
*/
cp = blame_nth_line(sb, ent->lno);
file_o.ptr = (char *) cp;
file_o.size = blame_nth_line(sb, ent->lno + ent->num_lines) - cp;
/*
* 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]));
if (diff_hunks(file_p, &file_o, handle_split_cb, &d))
react to errors in xdi_diff When we call into xdiff to perform a diff, we generally lose the return code completely. Typically by ignoring the return of our xdi_diff wrapper, but sometimes we even propagate that return value up and then ignore it later. This can lead to us silently producing incorrect diffs (e.g., "git log" might produce no output at all, not even a diff header, for a content-level diff). In practice this does not happen very often, because the typical reason for xdiff to report failure is that it malloc() failed (it uses straight malloc, and not our xmalloc wrapper). But it could also happen when xdiff triggers one our callbacks, which returns an error (e.g., outf() in builtin/rerere.c tries to report a write failure in this way). And the next patch also plans to add more failure modes. Let's notice an error return from xdiff and react appropriately. In most of the diff.c code, we can simply die(), which matches the surrounding code (e.g., that is what we do if we fail to load a file for diffing in the first place). This is not that elegant, but we are probably better off dying to let the user know there was a problem, rather than simply generating bogus output. We could also just die() directly in xdi_diff, but the callers typically have a bit more context, and can provide a better message (and if we do later decide to pass errors up, we're one step closer to doing so). There is one interesting case, which is in diff_grep(). Here if we cannot generate the diff, there is nothing to match, and we silently return "no hits". This is actually what the existing code does already, but we make it a little more explicit. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-25 07:12:23 +08:00
die("unable to generate diff (%s)",
oid_to_hex(&parent->commit->object.oid));
/* remainder, if any, all match the preimage */
handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
}
/* Move all blame entries from list *source that have a score smaller
* than score_min to the front of list *small.
* Returns a pointer to the link pointing to the old head of the small list.
*/
static struct blame_entry **filter_small(struct blame_scoreboard *sb,
struct blame_entry **small,
struct blame_entry **source,
unsigned score_min)
{
struct blame_entry *p = *source;
struct blame_entry *oldsmall = *small;
while (p) {
if (blame_entry_score(sb, p) <= score_min) {
*small = p;
small = &p->next;
p = *small;
} else {
*source = p;
source = &p->next;
p = *source;
}
}
*small = oldsmall;
*source = NULL;
return small;
}
/*
* See if lines currently target is suspected for can be attributed to
* parent.
*/
static void find_move_in_parent(struct blame_scoreboard *sb,
struct blame_entry ***blamed,
struct blame_entry **toosmall,
struct blame_origin *target,
struct blame_origin *parent)
{
struct blame_entry *e, split[3];
struct blame_entry *unblamed = target->suspects;
struct blame_entry *leftover = NULL;
mmfile_t file_p;
if (!unblamed)
return; /* nothing remains for this target */
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
if (!file_p.ptr)
return;
/* At each iteration, unblamed has a NULL-terminated list of
* entries that have not yet been tested for blame. leftover
* contains the reversed list of entries that have been tested
* without being assignable to the parent.
*/
do {
struct blame_entry **unblamedtail = &unblamed;
struct blame_entry *next;
for (e = unblamed; e; e = next) {
next = e->next;
find_copy_in_blob(sb, e, parent, split, &file_p);
if (split[1].suspect &&
blame_move_score < blame_entry_score(sb, &split[1])) {
split_blame(blamed, &unblamedtail, split, e);
} else {
e->next = leftover;
leftover = e;
}
decref_split(split);
}
*unblamedtail = NULL;
toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score);
} while (unblamed);
target->suspects = reverse_blame(leftover, NULL);
}
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 blame_entry *unblamed,
int *num_ents_p)
{
struct blame_entry *e;
int num_ents, i;
struct blame_list *blame_list = NULL;
for (e = unblamed, num_ents = 0; e; e = e->next)
num_ents++;
if (num_ents) {
blame_list = xcalloc(num_ents, sizeof(struct blame_list));
for (e = unblamed, i = 0; e; e = e->next)
blame_list[i++].ent = e;
}
*num_ents_p = num_ents;
return blame_list;
}
/*
* 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 void find_copy_in_parent(struct blame_scoreboard *sb,
struct blame_entry ***blamed,
struct blame_entry **toosmall,
struct blame_origin *target,
struct commit *parent,
struct blame_origin *porigin,
int opt)
{
struct diff_options diff_opts;
int i, j;
struct blame_list *blame_list;
int num_ents;
struct blame_entry *unblamed = target->suspects;
struct blame_entry *leftover = NULL;
if (!unblamed)
return; /* nothing remains for this target */
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
* we do not want to use diffcore_rename() actually to
* match things up; find_copies_harder is set only to
* force diff_tree_sha1() to feed all filepairs to diff_queue,
* and this code needs to be after diff_setup_done(), which
* usually makes find-copies-harder imply copy detection.
*/
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);
if (is_null_oid(&target->commit->object.oid))
do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
else
diff_tree_sha1(parent->tree->object.oid.hash,
target->commit->tree->object.oid.hash,
"", &diff_opts);
if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
diffcore_std(&diff_opts);
do {
struct blame_entry **unblamedtail = &unblamed;
blame_list = setup_blame_list(unblamed, &num_ents);
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
struct blame_origin *norigin;
mmfile_t file_p;
struct blame_entry this[3];
if (!DIFF_FILE_VALID(p->one))
continue; /* does not exist in parent */
if (S_ISGITLINK(p->one->mode))
continue; /* ignore git links */
if (porigin && !strcmp(p->one->path, porigin->path))
/* find_move already dealt with this path */
continue;
norigin = get_origin(parent, p->one->path);
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)
continue;
for (j = 0; j < num_ents; j++) {
find_copy_in_blob(sb, blame_list[j].ent,
norigin, this, &file_p);
copy_split_if_better(sb, blame_list[j].split,
this);
decref_split(this);
}
blame_origin_decref(norigin);
}
for (j = 0; j < num_ents; j++) {
struct blame_entry *split = blame_list[j].split;
if (split[1].suspect &&
blame_copy_score < blame_entry_score(sb, &split[1])) {
split_blame(blamed, &unblamedtail, split,
blame_list[j].ent);
} else {
blame_list[j].ent->next = leftover;
leftover = blame_list[j].ent;
}
decref_split(split);
}
free(blame_list);
*unblamedtail = NULL;
toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score);
} while (unblamed);
target->suspects = reverse_blame(leftover, NULL);
diff_flush(&diff_opts);
clear_pathspec(&diff_opts.pathspec);
}
/*
* 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 blame_scoreboard *sb,
struct blame_origin *origin, struct blame_origin *porigin)
{
struct blame_entry *e, *suspects;
if (!porigin->file.ptr && origin->file.ptr) {
/* Steal its file */
porigin->file = origin->file;
origin->file.ptr = NULL;
}
suspects = origin->suspects;
origin->suspects = NULL;
for (e = suspects; e; e = e->next) {
blame_origin_incref(porigin);
blame_origin_decref(e->suspect);
e->suspect = porigin;
}
queue_blames(sb, porigin, suspects);
}
/*
* 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.
*/
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
{
if (!reverse) {
if (revs->first_parent_only &&
commit->parents &&
commit->parents->next) {
free_commit_list(commit->parents->next);
commit->parents->next = NULL;
}
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
return commit->parents;
}
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
return lookup_decoration(&revs->children, &commit->object);
}
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
static int num_scapegoats(struct rev_info *revs, struct commit *commit)
{
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
struct commit_list *l = first_scapegoat(revs, commit);
return commit_list_count(l);
}
/* Distribute collected unsorted blames to the respected sorted lists
* in the various origins.
*/
static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed)
{
blamed = blame_sort(blamed, compare_blame_suspect);
while (blamed)
{
struct blame_origin *porigin = blamed->suspect;
struct blame_entry *suspects = NULL;
do {
struct blame_entry *next = blamed->next;
blamed->next = suspects;
suspects = blamed;
blamed = next;
} while (blamed && blamed->suspect == porigin);
suspects = reverse_blame(suspects, NULL);
queue_blames(sb, porigin, suspects);
}
}
#define MAXSG 16
static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin, int opt)
{
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
struct rev_info *revs = sb->revs;
int i, pass, num_sg;
struct commit *commit = origin->commit;
struct commit_list *sg;
struct blame_origin *sg_buf[MAXSG];
struct blame_origin *porigin, **sg_origin = sg_buf;
struct blame_entry *toosmall = NULL;
struct blame_entry *blames, **blametail = &blames;
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
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
* common cases, then we look for renames in the second pass.
*/
for (pass = 0; pass < 2 - no_whole_file_rename; pass++) {
struct blame_origin *(*find)(struct commit *, struct blame_origin *);
find = pass ? find_rename : find_origin;
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
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 (sg_origin[i])
continue;
if (parse_commit(p))
continue;
porigin = find(p, origin);
if (!porigin)
continue;
if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) {
pass_whole_blame(sb, origin, porigin);
blame_origin_decref(porigin);
goto finish;
}
for (j = same = 0; j < i; j++)
if (sg_origin[j] &&
!oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) {
same = 1;
break;
}
if (!same)
sg_origin[i] = porigin;
else
blame_origin_decref(porigin);
}
}
num_commits++;
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct blame_origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (!origin->previous) {
blame_origin_incref(porigin);
origin->previous = porigin;
}
pass_blame_to_parent(sb, origin, porigin);
if (!origin->suspects)
goto finish;
}
/*
* Optionally find moves in parents' files.
*/
if (opt & PICKAXE_BLAME_MOVE) {
filter_small(sb, &toosmall, &origin->suspects, blame_move_score);
if (origin->suspects) {
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct blame_origin *porigin = sg_origin[i];
if (!porigin)
continue;
find_move_in_parent(sb, &blametail, &toosmall, origin, porigin);
if (!origin->suspects)
break;
}
}
}
/*
* Optionally find copies from parents' files.
*/
if (opt & PICKAXE_BLAME_COPY) {
if (blame_copy_score > blame_move_score)
filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
else if (blame_copy_score < blame_move_score) {
origin->suspects = blame_merge(origin->suspects, toosmall);
toosmall = NULL;
filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
}
if (!origin->suspects)
goto finish;
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct blame_origin *porigin = sg_origin[i];
find_copy_in_parent(sb, &blametail, &toosmall,
origin, sg->item, porigin, opt);
if (!origin->suspects)
goto finish;
}
}
finish:
*blametail = NULL;
distribute_blame(sb, blames);
/*
* prepend toosmall to origin->suspects
*
* There is no point in sorting: this ends up on a big
* unsorted list in the caller anyway.
*/
if (toosmall) {
struct blame_entry **tail = &toosmall;
while (*tail)
tail = &(*tail)->next;
*tail = origin->suspects;
origin->suspects = toosmall;
}
for (i = 0; i < num_sg; i++) {
if (sg_origin[i]) {
drop_origin_blob(sg_origin[i]);
blame_origin_decref(sg_origin[i]);
}
}
drop_origin_blob(origin);
if (sg_buf != sg_origin)
free(sg_origin);
}
/*
* Information on commits, used for output.
*/
struct commit_info {
struct strbuf author;
struct strbuf author_mail;
timestamp_t author_time;
struct strbuf author_tz;
/* filled only when asked for details */
struct strbuf committer;
struct strbuf committer_mail;
timestamp_t committer_time;
struct strbuf committer_tz;
struct strbuf summary;
};
/*
* Parse author/committer line in the commit object buffer
*/
static void get_ac_line(const char *inbuf, const char *what,
struct strbuf *name, struct strbuf *mail,
timestamp_t *time, struct strbuf *tz)
{
struct ident_split ident;
size_t len, maillen, namelen;
char *tmp, *endp;
const char *namebuf, *mailbuf;
tmp = strstr(inbuf, what);
if (!tmp)
goto error_out;
tmp += strlen(what);
endp = strchr(tmp, '\n');
if (!endp)
len = strlen(tmp);
else
len = endp - tmp;
if (split_ident_line(&ident, tmp, len)) {
error_out:
/* Ugh */
tmp = "(unknown)";
strbuf_addstr(name, tmp);
strbuf_addstr(mail, tmp);
strbuf_addstr(tz, tmp);
*time = 0;
return;
}
namelen = ident.name_end - ident.name_begin;
namebuf = ident.name_begin;
maillen = ident.mail_end - ident.mail_begin;
mailbuf = ident.mail_begin;
if (ident.date_begin && ident.date_end)
*time = strtoul(ident.date_begin, NULL, 10);
else
*time = 0;
if (ident.tz_begin && ident.tz_end)
strbuf_add(tz, ident.tz_begin, ident.tz_end - ident.tz_begin);
else
strbuf_addstr(tz, "(unknown)");
/*
* Now, convert both name and e-mail using mailmap
*/
map_user(&mailmap, &mailbuf, &maillen,
&namebuf, &namelen);
strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf);
strbuf_add(name, namebuf, namelen);
}
static void commit_info_init(struct commit_info *ci)
{
strbuf_init(&ci->author, 0);
strbuf_init(&ci->author_mail, 0);
strbuf_init(&ci->author_tz, 0);
strbuf_init(&ci->committer, 0);
strbuf_init(&ci->committer_mail, 0);
strbuf_init(&ci->committer_tz, 0);
strbuf_init(&ci->summary, 0);
}
static void commit_info_destroy(struct commit_info *ci)
{
strbuf_release(&ci->author);
strbuf_release(&ci->author_mail);
strbuf_release(&ci->author_tz);
strbuf_release(&ci->committer);
strbuf_release(&ci->committer_mail);
strbuf_release(&ci->committer_tz);
strbuf_release(&ci->summary);
}
static void get_commit_info(struct commit *commit,
struct commit_info *ret,
int detailed)
{
int len;
const char *subject, *encoding;
const char *message;
commit_info_init(ret);
encoding = get_log_output_encoding();
message = logmsg_reencode(commit, NULL, encoding);
get_ac_line(message, "\nauthor ",
&ret->author, &ret->author_mail,
&ret->author_time, &ret->author_tz);
if (!detailed) {
unuse_commit_buffer(commit, message);
return;
}
get_ac_line(message, "\ncommitter ",
&ret->committer, &ret->committer_mail,
&ret->committer_time, &ret->committer_tz);
len = find_commit_subject(message, &subject);
if (len)
strbuf_add(&ret->summary, subject, len);
else
strbuf_addf(&ret->summary, "(%s)", oid_to_hex(&commit->object.oid));
unuse_commit_buffer(commit, message);
}
/*
blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:20:51 +08:00
* 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(struct blame_origin *suspect)
{
blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:20:51 +08:00
if (suspect->previous) {
struct blame_origin *prev = suspect->previous;
blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:20:51 +08:00
printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
write_name_quoted(prev->path, stdout, '\n');
}
printf("filename ");
blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:20:51 +08:00
write_name_quoted(suspect->path, stdout, '\n');
}
/*
* Porcelain/Incremental format wants to show a lot of details per
* commit. Instead of repeating this every line, emit it only once,
* the first time each commit appears in the output (unless the
* user has specifically asked for us to repeat).
*/
static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat)
{
struct commit_info ci;
if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
return 0;
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
printf("author %s\n", ci.author.buf);
printf("author-mail %s\n", ci.author_mail.buf);
printf("author-time %"PRItime"\n", ci.author_time);
printf("author-tz %s\n", ci.author_tz.buf);
printf("committer %s\n", ci.committer.buf);
printf("committer-mail %s\n", ci.committer_mail.buf);
printf("committer-time %"PRItime"\n", ci.committer_time);
printf("committer-tz %s\n", ci.committer_tz.buf);
printf("summary %s\n", ci.summary.buf);
if (suspect->commit->object.flags & UNINTERESTING)
printf("boundary\n");
commit_info_destroy(&ci);
return 1;
}
/*
* 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,
struct progress_info *pi)
{
if (incremental) {
struct blame_origin *suspect = ent->suspect;
printf("%s %d %d %d\n",
oid_to_hex(&suspect->commit->object.oid),
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
emit_one_suspect_detail(suspect, 0);
blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:20:51 +08:00
write_filename_info(suspect);
maybe_flush_or_die(stdout, "stdout");
}
pi->blamed_lines += ent->num_lines;
display_progress(pi->progress, pi->blamed_lines);
}
/*
* The main loop -- while we have blobs with lines whose true origin
* is still unknown, pick one blob, and allow its lines to pass blames
* to its parents. */
static void assign_blame(struct blame_scoreboard *sb, int opt)
{
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
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);
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
while (commit) {
struct blame_entry *ent;
struct blame_origin *suspect = commit->util;
/* find one suspect to break down */
while (suspect && !suspect->suspects)
suspect = suspect->next;
if (!suspect) {
commit = prio_queue_get(&sb->commits);
continue;
}
assert(commit == suspect->commit);
/*
* We will use this suspect later in the loop,
* so hold onto it in the meantime.
*/
blame_origin_incref(suspect);
parse_commit(commit);
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
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 */
ent = suspect->suspects;
if (ent) {
suspect->guilty = 1;
for (;;) {
struct blame_entry *next = ent->next;
found_guilty_entry(ent, &pi);
if (next) {
ent = next;
continue;
}
ent->next = sb->ent;
sb->ent = suspect->suspects;
suspect->suspects = NULL;
break;
}
}
blame_origin_decref(suspect);
if (DEBUG) /* sanity */
sanity_check_refcnt(sb);
}
stop_progress(&pi.progress);
}
static const char *format_time(timestamp_t time, const char *tz_str,
int show_raw_time)
{
static struct strbuf time_buf = STRBUF_INIT;
strbuf_reset(&time_buf);
if (show_raw_time) {
strbuf_addf(&time_buf, "%"PRItime" %s", time, tz_str);
}
else {
const char *time_str;
size_t time_width;
int tz;
tz = atoi(tz_str);
convert "enum date_mode" into a struct In preparation for adding date modes that may carry extra information beyond the mode itself, this patch converts the date_mode enum into a struct. Most of the conversion is fairly straightforward; we pass the struct as a pointer and dereference the type field where necessary. Locations that declare a date_mode can use a "{}" constructor. However, the tricky case is where we use the enum labels as constants, like: show_date(t, tz, DATE_NORMAL); Ideally we could say: show_date(t, tz, &{ DATE_NORMAL }); but of course C does not allow that. Likewise, we cannot cast the constant to a struct, because we need to pass an actual address. Our options are basically: 1. Manually add a "struct date_mode d = { DATE_NORMAL }" definition to each caller, and pass "&d". This makes the callers uglier, because they sometimes do not even have their own scope (e.g., they are inside a switch statement). 2. Provide a pre-made global "date_normal" struct that can be passed by address. We'd also need "date_rfc2822", "date_iso8601", and so forth. But at least the ugliness is defined in one place. 3. Provide a wrapper that generates the correct struct on the fly. The big downside is that we end up pointing to a single global, which makes our wrapper non-reentrant. But show_date is already not reentrant, so it does not matter. This patch implements 3, along with a minor macro to keep the size of the callers sane. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-26 00:55:02 +08:00
time_str = show_date(time, tz, &blame_date_mode);
strbuf_addstr(&time_buf, time_str);
/*
* Add space paddings to time_buf to display a fixed width
* string, and use time_width for display width calibration.
*/
for (time_width = utf8_strwidth(time_str);
time_width < blame_date_width;
time_width++)
strbuf_addch(&time_buf, ' ');
}
return time_buf.buf;
}
#define OUTPUT_ANNOTATE_COMPAT 001
#define OUTPUT_LONG_OBJECT_NAME 002
#define OUTPUT_RAW_TIMESTAMP 004
#define OUTPUT_PORCELAIN 010
#define OUTPUT_SHOW_NAME 020
#define OUTPUT_SHOW_NUMBER 040
#define OUTPUT_SHOW_SCORE 0100
#define OUTPUT_NO_AUTHOR 0200
#define OUTPUT_SHOW_EMAIL 0400
#define OUTPUT_LINE_PORCELAIN 01000
static void emit_porcelain_details(struct blame_origin *suspect, int repeat)
{
if (emit_one_suspect_detail(suspect, repeat) ||
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
blame: output porcelain "previous" header for each file It's possible for content currently found in one file to have originated in two separate files, each of which may have been modified in some single older commit. The --porcelain output generates an incorrect "previous" header in this case, whereas --line-porcelain gets it right. The problem is that the porcelain output tries to omit repeated details of commits, and treats "previous" as a property of the commit, when it is really a property of the blamed block of lines. Let's look at an example. In a case like this, you might see this output from --line-porcelain: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 author ... committer ... previous SOME_SHA1^ file_two filename file_two ...some different content.... The "filename" fields tell us that the two lines are from two different files. But notice that the filename also appears in the "previous" field, which tells us where to start a re-blame. The second content line never appeared in file_one at all, so we would obviously need to re-blame from file_two (or possibly even some other file, if had just been renamed to file_two in SOME_SHA1). So far so good. Now here's what --porcelain looks like: SOME_SHA1 1 1 1 author ... committer ... previous SOME_SHA1^ file_one filename file_one ...some line content... SOME_SHA1 2 1 1 filename file_two ...some different content.... We've dropped the author and committer fields from the second line, as they would just be repeats. But we can't omit "filename", because it depends on the actual block of blamed lines, not just the commit. This is handled by emit_porcelain_details(), which will show the filename either if it is the first mention of the commit _or_ if the commit has multiple paths in it. But we don't give "previous" the same handling. It's written inside emit_one_suspect_detail(), which bails early if we've already seen that commit. And so the output above is wrong; a reader would assume that the correct place to re-blame line two is from file_one, but that's obviously nonsense. Let's treat "previous" the same as "filename", and show it fresh whenever we know we are in a confusing case like this. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:20:51 +08:00
write_filename_info(suspect);
}
static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
int opt)
{
int repeat = opt & OUTPUT_LINE_PORCELAIN;
int cnt;
const char *cp;
struct blame_origin *suspect = ent->suspect;
char hex[GIT_MAX_HEXSZ + 1];
oid_to_hex_r(hex, &suspect->commit->object.oid);
printf("%s %d %d %d\n",
hex,
ent->s_lno + 1,
ent->lno + 1,
ent->num_lines);
emit_porcelain_details(suspect, repeat);
cp = blame_nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
char ch;
if (cnt) {
printf("%s %d %d\n", hex,
ent->s_lno + 1 + cnt,
ent->lno + 1 + cnt);
if (repeat)
emit_porcelain_details(suspect, 1);
}
putchar('\t');
do {
ch = *cp++;
putchar(ch);
} while (ch != '\n' &&
cp < sb->final_buf + sb->final_buf_size);
}
if (sb->final_buf_size && cp[-1] != '\n')
putchar('\n');
}
static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)
{
int cnt;
const char *cp;
struct blame_origin *suspect = ent->suspect;
struct commit_info ci;
char hex[GIT_MAX_HEXSZ + 1];
int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
get_commit_info(suspect->commit, &ci, 1);
oid_to_hex_r(hex, &suspect->commit->object.oid);
cp = blame_nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
char ch;
int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
if (suspect->commit->object.flags & UNINTERESTING) {
if (blank_boundary)
memset(hex, ' ', length);
else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
length--;
putchar('^');
}
}
printf("%.*s", length, hex);
if (opt & OUTPUT_ANNOTATE_COMPAT) {
const char *name;
if (opt & OUTPUT_SHOW_EMAIL)
name = ci.author_mail.buf;
else
name = ci.author.buf;
printf("\t(%10s\t%10s\t%d)", name,
format_time(ci.author_time, ci.author_tz.buf,
show_raw_time),
ent->lno + 1 + cnt);
} else {
if (opt & OUTPUT_SHOW_SCORE)
printf(" %*d %02d",
max_score_digits, ent->score,
ent->suspect->refcnt);
if (opt & OUTPUT_SHOW_NAME)
printf(" %-*.*s", longest_file, longest_file,
suspect->path);
if (opt & OUTPUT_SHOW_NUMBER)
printf(" %*d", max_orig_digits,
ent->s_lno + 1 + cnt);
if (!(opt & OUTPUT_NO_AUTHOR)) {
const char *name;
int pad;
if (opt & OUTPUT_SHOW_EMAIL)
name = ci.author_mail.buf;
else
name = ci.author.buf;
pad = longest_author - utf8_strwidth(name);
printf(" (%s%*s %10s",
name, pad, "",
format_time(ci.author_time,
ci.author_tz.buf,
show_raw_time));
}
printf(" %*d) ",
max_digits, ent->lno + 1 + cnt);
}
do {
ch = *cp++;
putchar(ch);
} while (ch != '\n' &&
cp < sb->final_buf + sb->final_buf_size);
}
if (sb->final_buf_size && cp[-1] != '\n')
putchar('\n');
commit_info_destroy(&ci);
}
static void output(struct blame_scoreboard *sb, int option)
{
struct blame_entry *ent;
if (option & OUTPUT_PORCELAIN) {
for (ent = sb->ent; ent; ent = ent->next) {
int count = 0;
struct blame_origin *suspect;
struct commit *commit = ent->suspect->commit;
if (commit->object.flags & MORE_THAN_ONE_PATH)
continue;
for (suspect = commit->util; suspect; suspect = suspect->next) {
if (suspect->guilty && count++) {
commit->object.flags |= MORE_THAN_ONE_PATH;
break;
}
}
}
}
for (ent = sb->ent; ent; ent = ent->next) {
if (option & OUTPUT_PORCELAIN)
emit_porcelain(sb, ent, option);
else {
emit_other(sb, ent, option);
}
}
}
static const char *get_next_line(const char *start, const char *end)
{
const char *nl = memchr(start, '\n', end - start);
return nl ? nl + 1 : end;
}
/*
* 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 blame_scoreboard *sb)
{
const char *buf = sb->final_buf;
unsigned long len = sb->final_buf_size;
const char *end = buf + len;
const char *p;
int *lineno;
int num = 0;
for (p = buf; p < end; p = get_next_line(p, end))
num++;
ALLOC_ARRAY(sb->lineno, num + 1);
lineno = sb->lineno;
for (p = buf; p < end; p = get_next_line(p, end))
*lineno++ = p - buf;
*lineno = len;
sb->num_lines = num;
return sb->num_lines;
}
/*
* Add phony grafts for use with -S; this is primarily to
* support git's 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");
struct strbuf buf = STRBUF_INIT;
if (!fp)
return -1;
while (!strbuf_getwholeline(&buf, fp, '\n')) {
/* The format is just "Commit Parent1 Parent2 ...\n" */
struct commit_graft *graft = read_graft_line(buf.buf, buf.len);
if (graft)
register_commit_graft(graft, 0);
}
fclose(fp);
strbuf_release(&buf);
return 0;
}
static int update_auto_abbrev(int auto_abbrev, struct blame_origin *suspect)
{
const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash,
auto_abbrev);
int len = strlen(uniq);
if (auto_abbrev < len)
return len;
return auto_abbrev;
}
/*
* How many columns do we need to show line numbers, authors,
* and filenames?
*/
static void find_alignment(struct blame_scoreboard *sb, int *option)
{
int longest_src_lines = 0;
int longest_dst_lines = 0;
unsigned largest_score = 0;
struct blame_entry *e;
int compute_auto_abbrev = (abbrev < 0);
int auto_abbrev = DEFAULT_ABBREV;
for (e = sb->ent; e; e = e->next) {
struct blame_origin *suspect = e->suspect;
int num;
if (compute_auto_abbrev)
auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
if (strcmp(suspect->path, sb->path))
*option |= OUTPUT_SHOW_NAME;
num = strlen(suspect->path);
if (longest_file < num)
longest_file = num;
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
struct commit_info ci;
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
if (*option & OUTPUT_SHOW_EMAIL)
num = utf8_strwidth(ci.author_mail.buf);
else
num = utf8_strwidth(ci.author.buf);
if (longest_author < num)
longest_author = num;
commit_info_destroy(&ci);
}
num = e->s_lno + e->num_lines;
if (longest_src_lines < num)
longest_src_lines = num;
num = e->lno + e->num_lines;
if (longest_dst_lines < num)
longest_dst_lines = num;
if (largest_score < blame_entry_score(sb, e))
largest_score = blame_entry_score(sb, e);
}
max_orig_digits = decimal_width(longest_src_lines);
max_digits = decimal_width(longest_dst_lines);
max_score_digits = decimal_width(largest_score);
if (compute_auto_abbrev)
/* one more abbrev length is needed for the boundary commit */
abbrev = auto_abbrev + 1;
}
/*
* For debugging -- origin is refcounted, and this asserts that
* we do not underflow.
*/
static void sanity_check_refcnt(struct blame_scoreboard *sb)
{
int baa = 0;
struct blame_entry *ent;
for (ent = sb->ent; ent; ent = ent->next) {
/* Nobody should have zero or negative refcnt */
if (ent->suspect->refcnt <= 0) {
fprintf(stderr, "%s in %s has negative refcnt %d\n",
ent->suspect->path,
oid_to_hex(&ent->suspect->commit->object.oid),
ent->suspect->refcnt);
baa = 1;
}
}
if (baa) {
int opt = 0160;
find_alignment(sb, &opt);
output(sb, opt);
die("Baa %d!", baa);
}
}
static unsigned parse_score(const char *arg)
{
char *end;
unsigned long score = strtoul(arg, &end, 10);
if (*end)
return 0;
return score;
}
static const char *add_prefix(const char *prefix, const char *path)
{
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
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);
return 0;
}
if (!strcmp(var, "blame.blankboundary")) {
blank_boundary = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "blame.showemail")) {
int *output_option = cb;
if (git_config_bool(var, value))
*output_option |= OUTPUT_SHOW_EMAIL;
else
*output_option &= ~OUTPUT_SHOW_EMAIL;
return 0;
}
if (!strcmp(var, "blame.date")) {
if (!value)
return config_error_nonbool(var);
convert "enum date_mode" into a struct In preparation for adding date modes that may carry extra information beyond the mode itself, this patch converts the date_mode enum into a struct. Most of the conversion is fairly straightforward; we pass the struct as a pointer and dereference the type field where necessary. Locations that declare a date_mode can use a "{}" constructor. However, the tricky case is where we use the enum labels as constants, like: show_date(t, tz, DATE_NORMAL); Ideally we could say: show_date(t, tz, &{ DATE_NORMAL }); but of course C does not allow that. Likewise, we cannot cast the constant to a struct, because we need to pass an actual address. Our options are basically: 1. Manually add a "struct date_mode d = { DATE_NORMAL }" definition to each caller, and pass "&d". This makes the callers uglier, because they sometimes do not even have their own scope (e.g., they are inside a switch statement). 2. Provide a pre-made global "date_normal" struct that can be passed by address. We'd also need "date_rfc2822", "date_iso8601", and so forth. But at least the ugliness is defined in one place. 3. Provide a wrapper that generates the correct struct on the fly. The big downside is that we end up pointing to a single global, which makes our wrapper non-reentrant. But show_date is already not reentrant, so it does not matter. This patch implements 3, along with a minor macro to keep the size of the callers sane. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-26 00:55:02 +08:00
parse_date_format(value, &blame_date_mode);
return 0;
}
if (git_diff_heuristic_config(var, value, cb) < 0)
return -1;
drop odd return value semantics from userdiff_config When the userdiff_config function was introduced in be58e70 (diff: unify external diff and funcname parsing code, 2008-10-05), it used a return value convention unlike any other config callback. Like other callbacks, it used "-1" to signal error. But it returned "1" to indicate that it found something, and "0" otherwise; other callbacks simply returned "0" to indicate that no error occurred. This distinction was necessary at the time, because the userdiff namespace overlapped slightly with the color configuration namespace. So "diff.color.foo" could mean "the 'foo' slot of diff coloring" or "the 'foo' component of the "color" userdiff driver". Because the color-parsing code would die on an unknown color slot, we needed the userdiff code to indicate that it had matched the variable, letting us bypass the color-parsing code entirely. Later, in 8b8e862 (ignore unknown color configuration, 2009-12-12), the color-parsing code learned to silently ignore unknown slots. This means we no longer need to protect userdiff-matched variables from reaching the color-parsing code. We can therefore change the userdiff_config calling convention to a more normal one. This drops some code from each caller, which is nice. But more importantly, it reduces the cognitive load for readers who may wonder why userdiff_config is unlike every other config callback. There's no need to add a new test confirming that this works; t4020 already contains a test that sets diff.color.external. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-08 02:23:02 +08:00
if (userdiff_config(var, value) < 0)
return -1;
return git_default_config(var, value, 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 struct object_id *commit_oid = &parents->item->object.oid;
struct object_id blob_oid;
unsigned mode;
if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) &&
sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB)
return;
}
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 struct object_id *oid)
{
struct commit *parent;
parent = lookup_commit_reference(oid->hash);
if (!parent)
die("no such commit %s", oid_to_hex(oid));
return &commit_list_insert(parent, tail)->next;
}
static void append_merge_parents(struct commit_list **tail)
{
int merge_head;
struct strbuf line = STRBUF_INIT;
memoize common git-path "constant" files One of the most common uses of git_path() is to pass a constant, like git_path("MERGE_MSG"). This has two drawbacks: 1. The return value is a static buffer, and the lifetime is dependent on other calls to git_path, etc. 2. There's no compile-time checking of the pathname. This is OK for a one-off (after all, we have to spell it correctly at least once), but many of these constant strings appear throughout the code. This patch introduces a series of functions to "memoize" these strings, which are essentially globals for the lifetime of the program. We compute the value once, take ownership of the buffer, and return the cached value for subsequent calls. cache.h provides a helper macro for defining these functions as one-liners, and defines a few common ones for global use. Using a macro is a little bit gross, but it does nicely document the purpose of the functions. If we need to touch them all later (e.g., because we learned how to change the git_dir variable at runtime, and need to invalidate all of the stored values), it will be much easier to have the complete list. Note that the shared-global functions have separate, manual declarations. We could do something clever with the macros (e.g., expand it to a declaration in some places, and a declaration _and_ a definition in path.c). But there aren't that many, and it's probably better to stay away from too-magical macros. Likewise, if we abandon the C preprocessor in favor of generating these with a script, we could get much fancier. E.g., normalizing "FOO/BAR-BAZ" into "git_path_foo_bar_baz". But the small amount of saved typing is probably not worth the resulting confusion to readers who want to grep for the function's definition. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-08-10 17:38:57 +08:00
merge_head = open(git_path_merge_head(), O_RDONLY);
if (merge_head < 0) {
if (errno == ENOENT)
return;
memoize common git-path "constant" files One of the most common uses of git_path() is to pass a constant, like git_path("MERGE_MSG"). This has two drawbacks: 1. The return value is a static buffer, and the lifetime is dependent on other calls to git_path, etc. 2. There's no compile-time checking of the pathname. This is OK for a one-off (after all, we have to spell it correctly at least once), but many of these constant strings appear throughout the code. This patch introduces a series of functions to "memoize" these strings, which are essentially globals for the lifetime of the program. We compute the value once, take ownership of the buffer, and return the cached value for subsequent calls. cache.h provides a helper macro for defining these functions as one-liners, and defines a few common ones for global use. Using a macro is a little bit gross, but it does nicely document the purpose of the functions. If we need to touch them all later (e.g., because we learned how to change the git_dir variable at runtime, and need to invalidate all of the stored values), it will be much easier to have the complete list. Note that the shared-global functions have separate, manual declarations. We could do something clever with the macros (e.g., expand it to a declaration in some places, and a declaration _and_ a definition in path.c). But there aren't that many, and it's probably better to stay away from too-magical macros. Likewise, if we abandon the C preprocessor in favor of generating these with a script, we could get much fancier. E.g., normalizing "FOO/BAR-BAZ" into "git_path_foo_bar_baz". But the small amount of saved typing is probably not worth the resulting confusion to readers who want to grep for the function's definition. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-08-10 17:38:57 +08:00
die("cannot open '%s' for reading", git_path_merge_head());
}
while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
struct object_id oid;
if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid))
memoize common git-path "constant" files One of the most common uses of git_path() is to pass a constant, like git_path("MERGE_MSG"). This has two drawbacks: 1. The return value is a static buffer, and the lifetime is dependent on other calls to git_path, etc. 2. There's no compile-time checking of the pathname. This is OK for a one-off (after all, we have to spell it correctly at least once), but many of these constant strings appear throughout the code. This patch introduces a series of functions to "memoize" these strings, which are essentially globals for the lifetime of the program. We compute the value once, take ownership of the buffer, and return the cached value for subsequent calls. cache.h provides a helper macro for defining these functions as one-liners, and defines a few common ones for global use. Using a macro is a little bit gross, but it does nicely document the purpose of the functions. If we need to touch them all later (e.g., because we learned how to change the git_dir variable at runtime, and need to invalidate all of the stored values), it will be much easier to have the complete list. Note that the shared-global functions have separate, manual declarations. We could do something clever with the macros (e.g., expand it to a declaration in some places, and a declaration _and_ a definition in path.c). But there aren't that many, and it's probably better to stay away from too-magical macros. Likewise, if we abandon the C preprocessor in favor of generating these with a script, we could get much fancier. E.g., normalizing "FOO/BAR-BAZ" into "git_path_foo_bar_baz". But the small amount of saved typing is probably not worth the resulting confusion to readers who want to grep for the function's definition. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-08-10 17:38:57 +08:00
die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
tail = append_parent(tail, &oid);
}
close(merge_head);
strbuf_release(&line);
}
/*
* This isn't as simple as passing sb->buf and sb->len, because we
* want to transfer ownership of the buffer to the commit (so we
* must use detach).
*/
static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb)
{
size_t len;
void *buf = strbuf_detach(sb, &len);
set_commit_buffer(c, buf, len);
}
/*
* 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(struct diff_options *opt,
const char *path,
const char *contents_from)
{
struct commit *commit;
struct blame_origin *origin;
struct commit_list **parent_tail, *parent;
struct object_id head_oid;
struct strbuf buf = STRBUF_INIT;
const char *ident;
time_t now;
int size, len;
struct cache_entry *ce;
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_oid.hash, NULL))
die("no such ref: HEAD");
parent_tail = append_parent(parent_tail, &head_oid);
append_merge_parents(parent_tail);
verify_working_tree_path(commit, path);
origin = make_origin(commit, path);
ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
for (parent = commit->parents; parent; parent = parent->next)
strbuf_addf(&msg, "parent %s\n",
oid_to_hex(&parent->item->object.oid));
strbuf_addf(&msg,
"author %s\n"
"committer %s\n\n"
"Version of %s from %s\n",
ident, ident, path,
(!contents_from ? path :
(!strcmp(contents_from, "-") ? "standard input" : contents_from)));
set_commit_buffer_from_strbuf(commit, &msg);
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
const char *read_from;
char *buf_ptr;
unsigned long buf_len;
if (contents_from) {
if (stat(contents_from, &st) < 0)
die_errno("Cannot stat '%s'", contents_from);
read_from = contents_from;
}
else {
if (lstat(path, &st) < 0)
die_errno("Cannot lstat '%s'", path);
read_from = path;
}
mode = canon_mode(st.st_mode);
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
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);
break;
case S_IFLNK:
if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
die_errno("cannot readlink '%s'", read_from);
break;
default:
die("unsupported file type %s", read_from);
}
}
else {
/* Reading from stdin */
mode = 0;
if (strbuf_read(&buf, 0, 0) < 0)
die_errno("failed to read from stdin");
}
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_oid.hash);
/*
* 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);
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);
cache_tree_invalidate_path(&the_index, path);
return commit;
}
static struct commit *find_single_final(struct rev_info *revs,
const char **name_p)
{
int i;
struct commit *found = NULL;
const char *name = NULL;
for (i = 0; i < revs->pending.nr; i++) {
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (found)
die("More than one commit to dig from %s and %s?",
revs->pending.objects[i].name, name);
found = (struct commit *)obj;
name = revs->pending.objects[i].name;
}
if (name_p)
*name_p = name;
return found;
}
static char *prepare_final(struct blame_scoreboard *sb)
{
const char *name;
sb->final = find_single_final(sb->revs, &name);
return xstrdup_or_null(name);
}
static const char *dwim_reverse_initial(struct blame_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 blame_scoreboard *sb)
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
{
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;
obj = deref_tag(obj, NULL, 0);
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (sb->final)
die("More than one commit to dig up from, %s and %s?",
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
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)
final_commit_name = dwim_reverse_initial(sb);
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
if (!final_commit_name)
die("No commit to dig up from?");
return xstrdup(final_commit_name);
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
}
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;
}
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
const char *path;
struct blame_scoreboard sb;
struct blame_origin *o;
struct blame_entry *ent = NULL;
long dashdash_pos, lno;
char *final_commit_name = NULL;
enum object_type type;
struct commit *final_commit = NULL;
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),
OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
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")),
{ OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
{ OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
OPT__ABBREV(&abbrev),
OPT_END()
};
struct parse_opt_ctx_t ctx;
int cmd_is_annotate = !strcmp(argv[0], "annotate");
struct range_set ranges;
unsigned int range_i;
long anchor;
git_config(git_blame_config, &output_option);
init_revisions(&revs, NULL);
revs.date_mode = blame_date_mode;
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES);
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);
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;
}
if (!strcmp(ctx.argv[0], "--reverse")) {
ctx.argv[0] = "--children";
reverse = 1;
}
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
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 (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);
blame: fix alignment with --abbrev=40 The blame command internally adds 1 to any requested sha1 abbreviation length, and then subtracts it when outputting a boundary commit. This lets regular and boundary sha1s line up visually, but it misses one corner case. When the requested length is 40, we bump the value to 41. But since we only have 40 characters, that's all we can show (fortunately the truncation is done by a printf precision field, so it never tries to read past the end of the buffer). So a normal sha1 shows 40 hex characters, and a boundary sha1 shows "^" plus 40 hex characters. The result is misaligned. The "-l" option to show long sha1s gets around this by skipping the "abbrev" variable entirely and just always using GIT_SHA1_HEXSZ. This avoids the "+1" issue, but it does mean that boundary commits only have 39 characters printed. This is somewhat odd, but it does look good visually: the results are aligned and left-justified. The alternative would be to allocate an extra column that would contain either an extra space or the "^" boundary marker. As this is by definition the human-readable view, it's probably not that big a deal either way (and of course --porcelain, etc, correctly produce correct 40-hex sha1s). But for consistency, this patch teaches --abbrev=40 to produce the same output as "-l" (always left-aligned, with 40-hex for normal sha1s, and "^" plus 39-hex for boundaries). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 12:17:40 +08:00
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);
if (cmd_is_annotate) {
output_option |= OUTPUT_ANNOTATE_COMPAT;
convert "enum date_mode" into a struct In preparation for adding date modes that may carry extra information beyond the mode itself, this patch converts the date_mode enum into a struct. Most of the conversion is fairly straightforward; we pass the struct as a pointer and dereference the type field where necessary. Locations that declare a date_mode can use a "{}" constructor. However, the tricky case is where we use the enum labels as constants, like: show_date(t, tz, DATE_NORMAL); Ideally we could say: show_date(t, tz, &{ DATE_NORMAL }); but of course C does not allow that. Likewise, we cannot cast the constant to a struct, because we need to pass an actual address. Our options are basically: 1. Manually add a "struct date_mode d = { DATE_NORMAL }" definition to each caller, and pass "&d". This makes the callers uglier, because they sometimes do not even have their own scope (e.g., they are inside a switch statement). 2. Provide a pre-made global "date_normal" struct that can be passed by address. We'd also need "date_rfc2822", "date_iso8601", and so forth. But at least the ugliness is defined in one place. 3. Provide a wrapper that generates the correct struct on the fly. The big downside is that we end up pointing to a single global, which makes our wrapper non-reentrant. But show_date is already not reentrant, so it does not matter. This patch implements 3, along with a minor macro to keep the size of the callers sane. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-26 00:55:02 +08:00
blame_date_mode.type = DATE_ISO8601;
} else {
blame_date_mode = revs.date_mode;
}
/* The maximum width used to show the dates */
convert "enum date_mode" into a struct In preparation for adding date modes that may carry extra information beyond the mode itself, this patch converts the date_mode enum into a struct. Most of the conversion is fairly straightforward; we pass the struct as a pointer and dereference the type field where necessary. Locations that declare a date_mode can use a "{}" constructor. However, the tricky case is where we use the enum labels as constants, like: show_date(t, tz, DATE_NORMAL); Ideally we could say: show_date(t, tz, &{ DATE_NORMAL }); but of course C does not allow that. Likewise, we cannot cast the constant to a struct, because we need to pass an actual address. Our options are basically: 1. Manually add a "struct date_mode d = { DATE_NORMAL }" definition to each caller, and pass "&d". This makes the callers uglier, because they sometimes do not even have their own scope (e.g., they are inside a switch statement). 2. Provide a pre-made global "date_normal" struct that can be passed by address. We'd also need "date_rfc2822", "date_iso8601", and so forth. But at least the ugliness is defined in one place. 3. Provide a wrapper that generates the correct struct on the fly. The big downside is that we end up pointing to a single global, which makes our wrapper non-reentrant. But show_date is already not reentrant, so it does not matter. This patch implements 3, along with a minor macro to keep the size of the callers sane. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-26 00:55:02 +08:00
switch (blame_date_mode.type) {
case DATE_RFC2822:
blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
break;
case DATE_ISO8601_STRICT:
blame_date_width = sizeof("2006-10-19T16:00:04-07:00");
break;
case DATE_ISO8601:
blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
break;
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;
case DATE_RELATIVE:
/* TRANSLATORS: This string is used to tell us the maximum
display width for a relative timestamp in "git blame"
output. For C locale, "4 years, 11 months ago", which
takes 22 places, is the longest among various forms of
relative timestamps, but your language may need more or
fewer display columns. */
blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
break;
case DATE_NORMAL:
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
break;
case DATE_STRFTIME:
blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */
break;
}
blame_date_width -= 1; /* strip the null */
if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
PICKAXE_BLAME_COPY_HARDER);
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]
* which are to be passed to revision machinery if we are
* going to do the "bottom" processing.
*
* The remaining are:
*
* (1) if dashdash_pos != 0, it is either
* "blame [revisions] -- <path>" or
* "blame -- <path> <rev>"
*
* (2) otherwise, it is one of the two:
* "blame [revisions] <path>"
* "blame <path> <rev>"
*
* Note that we must strip out <path> from the arguments: we do not
* want the path pruning but we may want "bottom" processing.
*/
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 (argc < 2)
usage_with_options(blame_opt_usage, options);
path = add_prefix(prefix, argv[argc - 1]);
if (argc == 3 && !file_exists(path)) { /* (2b) */
path = add_prefix(prefix, argv[1]);
argv[1] = argv[2];
}
argv[argc - 1] = "--";
setup_work_tree();
if (!file_exists(path))
die_errno("cannot stat path '%s'", path);
}
revs.disable_stdin = 1;
setup_revisions(argc, argv, &revs, NULL);
memset(&sb, 0, sizeof(sb));
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
sb.revs = &revs;
if (!reverse) {
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
final_commit_name = prepare_final(&sb);
sb.commits.compare = compare_commits_by_commit_date;
}
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
else if (contents_from)
die(_("--contents and --reverse do not blend well."));
else {
git-blame --reverse This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-04-03 13:17:53 +08:00
final_commit_name = prepare_initial(&sb);
sb.commits.compare = compare_commits_by_reverse_commit_date;
if (revs.first_parent_only)
revs.children.name = NULL;
}
if (!sb.final) {
/*
* "--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(&sb.revs->diffopt,
path, contents_from);
add_pending_object(&revs, &(sb.final->object), ":");
}
else if (contents_from)
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"));
}
/*
* If we have bottom, this will mark the ancestors of the
* bottom commits we would reach while traversing as
* uninteresting.
*/
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
if (reverse && revs.first_parent_only) {
struct commit *c = final_commit;
sb.revs->children.name = "children";
while (c->parents &&
oidcmp(&c->object.oid, &sb.final->object.oid)) {
struct commit_list *l = xcalloc(1, sizeof(*l));
l->item = c;
if (add_decoration(&sb.revs->children,
&c->parents->item->object, l))
die("BUG: not unique item in first-parent chain");
c = c->parents->item;
}
if (oidcmp(&c->object.oid, &sb.final->object.oid))
die(_("--reverse --first-parent together require range along first-parent chain"));
}
if (is_null_oid(&sb.final->object.oid)) {
o = sb.final->util;
sb.final_buf = xmemdupz(o->file.ptr, o->file.size);
sb.final_buf_size = o->file.size;
}
else {
o = get_origin(sb.final, path);
if (fill_blob_sha1_and_mode(o))
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_oid, 1, (char **) &sb.final_buf,
&sb.final_buf_size))
;
else
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"),
oid_to_hex(&o->blob_oid),
path);
}
num_read_blob++;
lno = prepare_lines(&sb);
if (lno && !range_list.nr)
string_list_append(&range_list, "1");
anchor = 1;
range_set_init(&ranges, range_list.nr);
for (range_i = 0; range_i < range_list.nr; ++range_i) {
long bottom, top;
if (parse_range_arg(range_list.items[range_i].string,
nth_line_cb, &sb, lno, anchor,
&bottom, &top, sb.path))
usage(blame_usage);
if (lno < top || ((lno || bottom) && lno < bottom))
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)
top = lno;
bottom--;
range_set_append_unsafe(&ranges, bottom, top);
anchor = top + 1;
}
sort_and_merge_range_set(&ranges);
for (range_i = ranges.nr; range_i > 0; --range_i) {
const struct range *r = &ranges.ranges[range_i - 1];
long bottom = r->start;
long top = r->end;
struct blame_entry *next = ent;
ent = xcalloc(1, sizeof(*ent));
ent->lno = bottom;
ent->num_lines = top - bottom;
ent->suspect = o;
ent->s_lno = bottom;
ent->next = next;
blame_origin_incref(o);
}
o->suspects = ent;
prio_queue_put(&sb.commits, o->commit);
blame_origin_decref(o);
range_set_release(&ranges);
string_list_clear(&range_list, 0);
sb.ent = NULL;
sb.path = path;
read_mailmap(&mailmap, NULL);
assign_blame(&sb, opt);
if (!incremental)
setup_pager();
free(final_commit_name);
if (incremental)
return 0;
sb.ent = blame_sort(sb.ent, compare_blame_final);
blame_coalesce(&sb);
if (!(output_option & OUTPUT_PORCELAIN))
find_alignment(&sb, &output_option);
output(&sb, output_option);
free((void *)sb.final_buf);
for (ent = sb.ent; ent; ) {
struct blame_entry *e = ent->next;
free(ent);
ent = e;
}
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);
}
return 0;
}