2006-10-19 23:00:04 +00:00
|
|
|
/*
|
2009-02-20 22:51:11 +00:00
|
|
|
* Blame
|
2006-10-19 23:00:04 +00:00
|
|
|
*
|
|
|
|
* Copyright (c) 2006, Junio C Hamano
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "cache.h"
|
|
|
|
#include "builtin.h"
|
|
|
|
#include "blob.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "tag.h"
|
|
|
|
#include "tree-walk.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "diffcore.h"
|
|
|
|
#include "revision.h"
|
2007-01-28 09:34:06 +00:00
|
|
|
#include "quote.h"
|
2006-10-19 23:00:04 +00:00
|
|
|
#include "xdiff-interface.h"
|
2007-01-30 09:11:08 +00:00
|
|
|
#include "cache-tree.h"
|
2008-07-21 18:03:49 +00:00
|
|
|
#include "string-list.h"
|
2007-04-27 07:42:15 +00:00
|
|
|
#include "mailmap.h"
|
2008-07-08 13:19:34 +00:00
|
|
|
#include "parse-options.h"
|
2009-01-30 09:41:29 +00:00
|
|
|
#include "utf8.h"
|
2010-06-15 13:58:48 +00:00
|
|
|
#include "userdiff.h"
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2008-07-17 00:22:50 +00:00
|
|
|
static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
|
2008-07-08 13:19:34 +00:00
|
|
|
|
|
|
|
static const char *blame_opt_usage[] = {
|
|
|
|
blame_usage,
|
|
|
|
"",
|
2008-07-09 20:47:38 +00:00
|
|
|
"[rev-opts] are documented in git-rev-list(1)",
|
2008-07-08 13:19:34 +00:00
|
|
|
NULL
|
|
|
|
};
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
static int longest_file;
|
|
|
|
static int longest_author;
|
|
|
|
static int max_orig_digits;
|
|
|
|
static int max_digits;
|
2006-10-20 21:51:12 +00:00
|
|
|
static int max_score_digits;
|
2006-12-18 22:04:38 +00:00
|
|
|
static int show_root;
|
2008-04-03 05:17:53 +00:00
|
|
|
static int reverse;
|
2006-12-18 22:04:38 +00:00
|
|
|
static int blank_boundary;
|
2007-01-28 09:34:06 +00:00
|
|
|
static int incremental;
|
2010-05-02 13:04:41 +00:00
|
|
|
static int xdl_opts;
|
2011-04-06 02:20:50 +00:00
|
|
|
static int abbrev = -1;
|
2009-02-20 22:51:11 +00:00
|
|
|
|
|
|
|
static enum date_mode blame_date_mode = DATE_ISO8601;
|
|
|
|
static size_t blame_date_width;
|
|
|
|
|
2008-07-21 18:03:49 +00:00
|
|
|
static struct string_list mailmap;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2006-10-29 11:07:40 +00:00
|
|
|
#ifndef DEBUG
|
|
|
|
#define DEBUG 0
|
|
|
|
#endif
|
|
|
|
|
2006-11-05 19:51:41 +00:00
|
|
|
/* stats */
|
|
|
|
static int num_read_blob;
|
|
|
|
static int num_get_patch;
|
|
|
|
static int num_commits;
|
|
|
|
|
2006-10-20 01:49:30 +00:00
|
|
|
#define PICKAXE_BLAME_MOVE 01
|
2006-10-20 01:50:17 +00:00
|
|
|
#define PICKAXE_BLAME_COPY 02
|
|
|
|
#define PICKAXE_BLAME_COPY_HARDER 04
|
2007-05-06 04:18:57 +00:00
|
|
|
#define PICKAXE_BLAME_COPY_HARDEST 010
|
2006-10-20 01:49:30 +00:00
|
|
|
|
2006-10-20 22:37:12 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
|
2006-10-19 23:00:04 +00:00
|
|
|
/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
|
|
|
|
#define METAINFO_SHOWN (1u<<12)
|
|
|
|
#define MORE_THAN_ONE_PATH (1u<<13)
|
|
|
|
|
|
|
|
/*
|
2006-10-29 11:07:40 +00:00
|
|
|
* One blob in a commit that is being suspected
|
2006-10-19 23:00:04 +00:00
|
|
|
*/
|
|
|
|
struct origin {
|
2006-10-29 11:07:40 +00:00
|
|
|
int refcnt;
|
2008-06-05 05:58:40 +00:00
|
|
|
struct origin *previous;
|
2006-10-19 23:00:04 +00:00
|
|
|
struct commit *commit;
|
2006-11-05 19:51:41 +00:00
|
|
|
mmfile_t file;
|
2006-10-19 23:00:04 +00:00
|
|
|
unsigned char blob_sha1[20];
|
2010-09-29 11:35:24 +00:00
|
|
|
unsigned mode;
|
2006-10-19 23:00:04 +00:00
|
|
|
char path[FLEX_ARRAY];
|
|
|
|
};
|
|
|
|
|
2012-05-09 20:23:39 +00:00
|
|
|
static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
|
|
|
|
xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
|
|
|
|
{
|
|
|
|
xpparam_t xpp = {0};
|
|
|
|
xdemitconf_t xecfg = {0};
|
2012-05-13 21:41:16 +00:00
|
|
|
xdemitcb_t ecb = {NULL};
|
2012-05-09 20:23:39 +00:00
|
|
|
|
|
|
|
xpp.flags = xdl_opts;
|
|
|
|
xecfg.ctxlen = ctxlen;
|
|
|
|
xecfg.hunk_func = hunk_func;
|
|
|
|
ecb.priv = cb_data;
|
|
|
|
return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
|
|
|
|
}
|
|
|
|
|
2010-06-15 13:58:48 +00:00
|
|
|
/*
|
|
|
|
* Prepare diff_filespec and convert it using diff textconv API
|
|
|
|
* if the textconv driver exists.
|
|
|
|
* Return 1 if the conversion succeeds, 0 otherwise.
|
|
|
|
*/
|
2010-06-15 15:50:28 +00:00
|
|
|
int textconv_object(const char *path,
|
2010-09-29 11:35:24 +00:00
|
|
|
unsigned mode,
|
2010-06-15 15:50:28 +00:00
|
|
|
const unsigned char *sha1,
|
|
|
|
char **buf,
|
|
|
|
unsigned long *buf_size)
|
2010-06-15 13:58:48 +00:00
|
|
|
{
|
|
|
|
struct diff_filespec *df;
|
|
|
|
struct userdiff_driver *textconv;
|
|
|
|
|
|
|
|
df = alloc_filespec(path);
|
2010-09-29 11:35:24 +00:00
|
|
|
fill_filespec(df, sha1, mode);
|
2010-06-15 13:58:48 +00:00
|
|
|
textconv = get_textconv(df);
|
|
|
|
if (!textconv) {
|
|
|
|
free_filespec(df);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
*buf_size = fill_textconv(textconv, df, buf);
|
|
|
|
free_filespec(df);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Given an origin, prepare mmfile_t structure to be used by the
|
|
|
|
* diff machinery
|
|
|
|
*/
|
2010-06-15 13:58:48 +00:00
|
|
|
static void fill_origin_blob(struct diff_options *opt,
|
|
|
|
struct origin *o, mmfile_t *file)
|
2006-11-05 19:51:41 +00:00
|
|
|
{
|
|
|
|
if (!o->file.ptr) {
|
2007-02-26 19:55:59 +00:00
|
|
|
enum object_type type;
|
2010-06-15 13:58:48 +00:00
|
|
|
unsigned long file_size;
|
|
|
|
|
2006-11-05 19:51:41 +00:00
|
|
|
num_read_blob++;
|
2010-06-15 13:58:48 +00:00
|
|
|
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
|
2010-09-29 11:35:24 +00:00
|
|
|
textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size))
|
2010-06-15 13:58:48 +00:00
|
|
|
;
|
|
|
|
else
|
|
|
|
file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
|
|
|
|
file->size = file_size;
|
|
|
|
|
2007-08-25 08:26:20 +00:00
|
|
|
if (!file->ptr)
|
|
|
|
die("Cannot read blob %s for path %s",
|
|
|
|
sha1_to_hex(o->blob_sha1),
|
|
|
|
o->path);
|
2006-11-05 19:51:41 +00:00
|
|
|
o->file = *file;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*file = o->file;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Origin is refcounted and usually we keep the blob contents to be
|
|
|
|
* reused.
|
|
|
|
*/
|
2006-10-29 11:07:40 +00:00
|
|
|
static inline struct origin *origin_incref(struct origin *o)
|
|
|
|
{
|
|
|
|
if (o)
|
|
|
|
o->refcnt++;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void origin_decref(struct origin *o)
|
|
|
|
{
|
|
|
|
if (o && --o->refcnt <= 0) {
|
2008-06-05 05:58:40 +00:00
|
|
|
if (o->previous)
|
|
|
|
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-01-31 17:26:32 +00:00
|
|
|
free(o->file.ptr);
|
2006-10-29 11:07:40 +00:00
|
|
|
free(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-12-12 00:05:50 +00:00
|
|
|
static void drop_origin_blob(struct origin *o)
|
|
|
|
{
|
|
|
|
if (o->file.ptr) {
|
|
|
|
free(o->file.ptr);
|
|
|
|
o->file.ptr = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Each group of lines is described by a blame_entry; it can be split
|
|
|
|
* as we pass blame to the parents. They form a linked list in the
|
|
|
|
* scoreboard structure, sorted by the target line number.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
struct blame_entry {
|
|
|
|
struct blame_entry *prev;
|
|
|
|
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 origin *suspect;
|
|
|
|
|
|
|
|
/* true if the suspect is truly guilty; false while we have not
|
|
|
|
* checked if the group came from one of its parents.
|
|
|
|
*/
|
|
|
|
char guilty;
|
|
|
|
|
2008-07-15 22:05:20 +00:00
|
|
|
/* true if the entry has been scanned for copies in the current parent
|
|
|
|
*/
|
|
|
|
char scanned;
|
|
|
|
|
2006-10-19 23:00:04 +00:00
|
|
|
/* 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;
|
2006-10-20 21:51:12 +00:00
|
|
|
|
|
|
|
/* how significant this entry is -- cached to avoid
|
2007-01-30 01:36:22 +00:00
|
|
|
* scanning the lines over and over.
|
2006-10-20 21:51:12 +00:00
|
|
|
*/
|
|
|
|
unsigned score;
|
2006-10-19 23:00:04 +00:00
|
|
|
};
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The current state of the blame assignment.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
struct scoreboard {
|
|
|
|
/* the final commit (i.e. where we started digging from) */
|
|
|
|
struct commit *final;
|
2008-04-03 05:17:53 +00:00
|
|
|
struct rev_info *revs;
|
2006-10-19 23:00:04 +00:00
|
|
|
const char *path;
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The contents in the final image.
|
|
|
|
* Used by many functions to obtain contents of the nth line,
|
|
|
|
* indexed with scoreboard.lineno[blame_entry.lno].
|
2006-10-19 23:00:04 +00:00
|
|
|
*/
|
|
|
|
const char *final_buf;
|
|
|
|
unsigned long final_buf_size;
|
|
|
|
|
|
|
|
/* linked list of blames */
|
|
|
|
struct blame_entry *ent;
|
|
|
|
|
2006-10-21 06:49:31 +00:00
|
|
|
/* look-up a line in the final buffer */
|
2006-10-19 23:00:04 +00:00
|
|
|
int num_lines;
|
|
|
|
int *lineno;
|
|
|
|
};
|
|
|
|
|
2007-03-21 06:37:51 +00:00
|
|
|
static inline int same_suspect(struct origin *a, struct origin *b)
|
2006-10-21 07:41:38 +00:00
|
|
|
{
|
2007-03-21 06:37:51 +00:00
|
|
|
if (a == b)
|
2007-03-20 05:17:10 +00:00
|
|
|
return 1;
|
2007-03-21 06:37:51 +00:00
|
|
|
if (a->commit != b->commit)
|
|
|
|
return 0;
|
|
|
|
return !strcmp(a->path, b->path);
|
2006-10-21 07:41:38 +00:00
|
|
|
}
|
|
|
|
|
2006-10-29 11:07:40 +00:00
|
|
|
static void sanity_check_refcnt(struct scoreboard *);
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void coalesce(struct scoreboard *sb)
|
|
|
|
{
|
|
|
|
struct blame_entry *ent, *next;
|
|
|
|
|
|
|
|
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
|
2007-03-21 06:37:51 +00:00
|
|
|
if (same_suspect(ent->suspect, next->suspect) &&
|
2006-10-19 23:00:04 +00:00
|
|
|
ent->guilty == next->guilty &&
|
|
|
|
ent->s_lno + ent->num_lines == next->s_lno) {
|
|
|
|
ent->num_lines += next->num_lines;
|
|
|
|
ent->next = next->next;
|
|
|
|
if (ent->next)
|
|
|
|
ent->next->prev = ent;
|
2006-10-29 11:07:40 +00:00
|
|
|
origin_decref(next->suspect);
|
2006-10-19 23:00:04 +00:00
|
|
|
free(next);
|
2006-10-21 07:41:38 +00:00
|
|
|
ent->score = 0;
|
2006-10-19 23:00:04 +00:00
|
|
|
next = ent; /* again */
|
|
|
|
}
|
|
|
|
}
|
2006-10-29 11:07:40 +00:00
|
|
|
|
|
|
|
if (DEBUG) /* sanity */
|
|
|
|
sanity_check_refcnt(sb);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-11-05 03:18:50 +00:00
|
|
|
static struct origin *make_origin(struct commit *commit, const char *path)
|
|
|
|
{
|
|
|
|
struct origin *o;
|
|
|
|
o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
|
|
|
|
o->commit = commit;
|
|
|
|
o->refcnt = 1;
|
|
|
|
strcpy(o->path, path);
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Locate an existing origin or create a new one.
|
|
|
|
*/
|
2006-10-21 09:56:33 +00:00
|
|
|
static struct origin *get_origin(struct scoreboard *sb,
|
|
|
|
struct commit *commit,
|
|
|
|
const char *path)
|
2006-10-19 23:00:04 +00:00
|
|
|
{
|
2006-10-21 09:56:33 +00:00
|
|
|
struct blame_entry *e;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2006-10-21 09:56:33 +00:00
|
|
|
for (e = sb->ent; e; e = e->next) {
|
|
|
|
if (e->suspect->commit == commit &&
|
|
|
|
!strcmp(e->suspect->path, path))
|
2006-10-29 11:07:40 +00:00
|
|
|
return origin_incref(e->suspect);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
2006-11-05 03:18:50 +00:00
|
|
|
return make_origin(commit, path);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
2010-09-29 11:35:24 +00:00
|
|
|
*
|
|
|
|
* This also fills origin->mode for corresponding tree path.
|
2007-01-30 01:36:22 +00:00
|
|
|
*/
|
2010-09-29 11:35:24 +00:00
|
|
|
static int fill_blob_sha1_and_mode(struct origin *origin)
|
2006-10-21 09:56:33 +00:00
|
|
|
{
|
|
|
|
if (!is_null_sha1(origin->blob_sha1))
|
|
|
|
return 0;
|
|
|
|
if (get_tree_entry(origin->commit->object.sha1,
|
|
|
|
origin->path,
|
2010-09-29 11:35:24 +00:00
|
|
|
origin->blob_sha1, &origin->mode))
|
2006-10-21 09:56:33 +00:00
|
|
|
goto error_out;
|
2007-02-26 19:55:59 +00:00
|
|
|
if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
|
2006-10-21 09:56:33 +00:00
|
|
|
goto error_out;
|
|
|
|
return 0;
|
|
|
|
error_out:
|
|
|
|
hashclr(origin->blob_sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
origin->mode = S_IFINVALID;
|
2006-10-21 09:56:33 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* We have an origin -- check if the same path exists in the
|
|
|
|
* parent and return an origin structure to represent it.
|
|
|
|
*/
|
2006-10-21 09:56:33 +00:00
|
|
|
static struct origin *find_origin(struct scoreboard *sb,
|
2006-10-19 23:00:04 +00:00
|
|
|
struct commit *parent,
|
|
|
|
struct origin *origin)
|
|
|
|
{
|
|
|
|
struct origin *porigin = NULL;
|
|
|
|
struct diff_options diff_opts;
|
2006-10-21 09:56:33 +00:00
|
|
|
const char *paths[2];
|
|
|
|
|
2006-10-31 09:00:01 +00:00
|
|
|
if (parent->util) {
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Each commit object can cache one origin in that
|
|
|
|
* commit. This is a freestanding copy of origin and
|
|
|
|
* not refcounted.
|
2006-11-05 03:18:50 +00:00
|
|
|
*/
|
2006-10-31 09:00:01 +00:00
|
|
|
struct origin *cached = parent->util;
|
2006-11-05 03:18:50 +00:00
|
|
|
if (!strcmp(cached->path, origin->path)) {
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The same path between origin and its parent
|
|
|
|
* without renaming -- the most common case.
|
|
|
|
*/
|
2006-11-05 03:18:50 +00:00
|
|
|
porigin = get_origin(sb, parent, cached->path);
|
2007-01-30 01:36:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If the origin was newly created (i.e. get_origin
|
|
|
|
* would call make_origin if none is found in the
|
2010-09-29 11:35:24 +00:00
|
|
|
* scoreboard), it does not know the blob_sha1/mode,
|
2007-01-30 01:36:22 +00:00
|
|
|
* so copy it. Otherwise porigin was in the
|
2010-09-29 11:35:24 +00:00
|
|
|
* scoreboard and already knows blob_sha1/mode.
|
2007-01-30 01:36:22 +00:00
|
|
|
*/
|
2010-09-29 11:35:24 +00:00
|
|
|
if (porigin->refcnt == 1) {
|
2006-11-05 03:18:50 +00:00
|
|
|
hashcpy(porigin->blob_sha1, cached->blob_sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
porigin->mode = cached->mode;
|
|
|
|
}
|
2006-11-05 03:18:50 +00:00
|
|
|
return porigin;
|
|
|
|
}
|
|
|
|
/* otherwise it was not very useful; free it */
|
|
|
|
free(parent->util);
|
|
|
|
parent->util = NULL;
|
2006-10-31 09:00:01 +00:00
|
|
|
}
|
|
|
|
|
2006-10-21 09:56:33 +00:00
|
|
|
/* 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);
|
2007-11-10 19:05:14 +00:00
|
|
|
DIFF_OPT_SET(&diff_opts, RECURSIVE);
|
2006-10-21 09:56:33 +00:00
|
|
|
diff_opts.detect_rename = 0;
|
|
|
|
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
|
|
|
paths[0] = origin->path;
|
|
|
|
paths[1] = NULL;
|
|
|
|
|
|
|
|
diff_tree_setup_paths(paths, &diff_opts);
|
|
|
|
if (diff_setup_done(&diff_opts) < 0)
|
|
|
|
die("diff-setup");
|
2007-01-30 09:11:08 +00:00
|
|
|
|
|
|
|
if (is_null_sha1(origin->commit->object.sha1))
|
|
|
|
do_diff_cache(parent->tree->object.sha1, &diff_opts);
|
|
|
|
else
|
|
|
|
diff_tree_sha1(parent->tree->object.sha1,
|
|
|
|
origin->commit->tree->object.sha1,
|
|
|
|
"", &diff_opts);
|
2006-10-21 09:56:33 +00:00
|
|
|
diffcore_std(&diff_opts);
|
|
|
|
|
|
|
|
if (!diff_queued_diff.nr) {
|
|
|
|
/* The path is the same as parent */
|
|
|
|
porigin = get_origin(sb, parent, origin->path);
|
|
|
|
hashcpy(porigin->blob_sha1, origin->blob_sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
porigin->mode = origin->mode;
|
2009-06-03 07:43:22 +00:00
|
|
|
} 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");
|
2006-10-21 09:56:33 +00:00
|
|
|
switch (p->status) {
|
|
|
|
default:
|
2006-11-09 02:47:54 +00:00
|
|
|
die("internal error in blame::find_origin (%c)",
|
2006-10-21 09:56:33 +00:00
|
|
|
p->status);
|
|
|
|
case 'M':
|
|
|
|
porigin = get_origin(sb, parent, origin->path);
|
|
|
|
hashcpy(porigin->blob_sha1, p->one->sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
porigin->mode = p->one->mode;
|
2006-10-21 09:56:33 +00:00
|
|
|
break;
|
|
|
|
case 'A':
|
|
|
|
case 'T':
|
|
|
|
/* Did not exist in parent, or type changed */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
diff_flush(&diff_opts);
|
2007-12-11 21:59:55 +00:00
|
|
|
diff_tree_release_paths(&diff_opts);
|
2006-10-31 09:00:01 +00:00
|
|
|
if (porigin) {
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Create a freestanding copy that is not part of
|
|
|
|
* the refcounted origin found in the scoreboard, and
|
|
|
|
* cache it in the commit.
|
|
|
|
*/
|
2006-11-05 03:18:50 +00:00
|
|
|
struct origin *cached;
|
2007-01-30 01:36:22 +00:00
|
|
|
|
2006-11-05 03:18:50 +00:00
|
|
|
cached = make_origin(porigin->commit, porigin->path);
|
|
|
|
hashcpy(cached->blob_sha1, porigin->blob_sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
cached->mode = porigin->mode;
|
2006-11-05 03:18:50 +00:00
|
|
|
parent->util = cached;
|
2006-10-31 09:00:01 +00:00
|
|
|
}
|
2006-10-31 01:17:41 +00:00
|
|
|
return porigin;
|
|
|
|
}
|
2006-10-21 09:56:33 +00:00
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* We have an origin -- find the path that corresponds to it in its
|
|
|
|
* parent and return an origin structure to represent it.
|
|
|
|
*/
|
2006-10-31 01:17:41 +00:00
|
|
|
static struct origin *find_rename(struct scoreboard *sb,
|
|
|
|
struct commit *parent,
|
|
|
|
struct origin *origin)
|
|
|
|
{
|
|
|
|
struct origin *porigin = NULL;
|
|
|
|
struct diff_options diff_opts;
|
|
|
|
int i;
|
|
|
|
const char *paths[2];
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
diff_setup(&diff_opts);
|
2007-11-10 19:05:14 +00:00
|
|
|
DIFF_OPT_SET(&diff_opts, RECURSIVE);
|
2006-10-19 23:00:04 +00:00
|
|
|
diff_opts.detect_rename = DIFF_DETECT_RENAME;
|
|
|
|
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
2006-11-02 08:02:11 +00:00
|
|
|
diff_opts.single_follow = origin->path;
|
2006-10-19 23:00:04 +00:00
|
|
|
paths[0] = NULL;
|
|
|
|
diff_tree_setup_paths(paths, &diff_opts);
|
|
|
|
if (diff_setup_done(&diff_opts) < 0)
|
|
|
|
die("diff-setup");
|
2007-01-30 09:11:08 +00:00
|
|
|
|
|
|
|
if (is_null_sha1(origin->commit->object.sha1))
|
|
|
|
do_diff_cache(parent->tree->object.sha1, &diff_opts);
|
|
|
|
else
|
|
|
|
diff_tree_sha1(parent->tree->object.sha1,
|
|
|
|
origin->commit->tree->object.sha1,
|
|
|
|
"", &diff_opts);
|
2006-10-19 23:00:04 +00:00
|
|
|
diffcore_std(&diff_opts);
|
|
|
|
|
|
|
|
for (i = 0; i < diff_queued_diff.nr; i++) {
|
|
|
|
struct diff_filepair *p = diff_queued_diff.queue[i];
|
2006-10-21 06:49:31 +00:00
|
|
|
if ((p->status == 'R' || p->status == 'C') &&
|
2006-10-21 09:56:33 +00:00
|
|
|
!strcmp(p->two->path, origin->path)) {
|
|
|
|
porigin = get_origin(sb, parent, p->one->path);
|
|
|
|
hashcpy(porigin->blob_sha1, p->one->sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
porigin->mode = p->one->mode;
|
2006-10-19 23:00:04 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
diff_flush(&diff_opts);
|
2007-12-11 21:59:55 +00:00
|
|
|
diff_tree_release_paths(&diff_opts);
|
2006-10-19 23:00:04 +00:00
|
|
|
return porigin;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
2007-02-04 04:49:16 +00:00
|
|
|
* Link in a new blame entry to the scoreboard. Entries that cover the
|
2007-01-30 01:36:22 +00:00
|
|
|
* same line range have been removed from the scoreboard previously.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
|
|
|
|
{
|
|
|
|
struct blame_entry *ent, *prev = NULL;
|
|
|
|
|
2006-10-29 11:07:40 +00:00
|
|
|
origin_incref(e->suspect);
|
|
|
|
|
2006-10-19 23:00:04 +00:00
|
|
|
for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
|
|
|
|
prev = ent;
|
|
|
|
|
|
|
|
/* prev, if not NULL, is the last one that is below e */
|
|
|
|
e->prev = prev;
|
|
|
|
if (prev) {
|
|
|
|
e->next = prev->next;
|
|
|
|
prev->next = e;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
e->next = sb->ent;
|
|
|
|
sb->ent = e;
|
|
|
|
}
|
|
|
|
if (e->next)
|
|
|
|
e->next->prev = e;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* src typically is on-stack; we want to copy the information in it to
|
2008-01-03 14:18:07 +00:00
|
|
|
* a malloced blame_entry that is already on the linked list of the
|
2007-01-30 01:36:22 +00:00
|
|
|
* scoreboard. The origin of dst loses a refcnt while the origin of src
|
|
|
|
* gains one.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
|
|
|
|
{
|
|
|
|
struct blame_entry *p, *n;
|
2006-10-29 11:07:40 +00:00
|
|
|
|
2006-10-19 23:00:04 +00:00
|
|
|
p = dst->prev;
|
|
|
|
n = dst->next;
|
2006-10-29 11:07:40 +00:00
|
|
|
origin_incref(src->suspect);
|
|
|
|
origin_decref(dst->suspect);
|
2006-10-19 23:00:04 +00:00
|
|
|
memcpy(dst, src, sizeof(*src));
|
|
|
|
dst->prev = p;
|
|
|
|
dst->next = n;
|
2006-10-20 21:51:12 +00:00
|
|
|
dst->score = 0;
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *nth_line(struct scoreboard *sb, int lno)
|
|
|
|
{
|
|
|
|
return sb->final_buf + sb->lineno[lno];
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-29 11:07:40 +00:00
|
|
|
static void split_overlap(struct blame_entry *split,
|
2006-10-19 23:00:04 +00:00
|
|
|
struct blame_entry *e,
|
|
|
|
int tlno, int plno, int same,
|
|
|
|
struct 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 */
|
2006-10-29 11:07:40 +00:00
|
|
|
split[0].suspect = origin_incref(e->suspect);
|
2006-10-19 23:00:04 +00:00
|
|
|
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 */
|
2006-10-29 11:07:40 +00:00
|
|
|
split[2].suspect = origin_incref(e->suspect);
|
2006-10-19 23:00:04 +00:00
|
|
|
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;
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* if it turns out there is nothing to blame the parent for,
|
|
|
|
* forget about the splitting. !split[1].suspect signals this.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
if (split[1].num_lines < 1)
|
|
|
|
return;
|
2006-10-29 11:07:40 +00:00
|
|
|
split[1].suspect = origin_incref(parent);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* split_overlap() divided an existing blame e into up to three parts
|
|
|
|
* in split. Adjust the linked list of blames in the scoreboard to
|
|
|
|
* reflect the split.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void split_blame(struct scoreboard *sb,
|
2006-10-29 11:07:40 +00:00
|
|
|
struct blame_entry *split,
|
2006-10-19 23:00:04 +00:00
|
|
|
struct blame_entry *e)
|
|
|
|
{
|
|
|
|
struct blame_entry *new_entry;
|
|
|
|
|
|
|
|
if (split[0].suspect && split[2].suspect) {
|
2007-01-30 01:36:22 +00:00
|
|
|
/* The first part (reuse storage for the existing entry e) */
|
2006-10-19 23:00:04 +00:00
|
|
|
dup_entry(e, &split[0]);
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/* The last part -- me */
|
2006-10-19 23:00:04 +00:00
|
|
|
new_entry = xmalloc(sizeof(*new_entry));
|
|
|
|
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
|
|
|
|
add_blame_entry(sb, new_entry);
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/* ... and the middle part -- parent */
|
2006-10-19 23:00:04 +00:00
|
|
|
new_entry = xmalloc(sizeof(*new_entry));
|
|
|
|
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
|
|
|
|
add_blame_entry(sb, new_entry);
|
|
|
|
}
|
|
|
|
else if (!split[0].suspect && !split[2].suspect)
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The parent covers the entire area; reuse storage for
|
|
|
|
* e and replace it with the parent.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
dup_entry(e, &split[1]);
|
|
|
|
else if (split[0].suspect) {
|
2007-01-30 01:36:22 +00:00
|
|
|
/* me and then parent */
|
2006-10-19 23:00:04 +00:00
|
|
|
dup_entry(e, &split[0]);
|
|
|
|
|
|
|
|
new_entry = xmalloc(sizeof(*new_entry));
|
|
|
|
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
|
|
|
|
add_blame_entry(sb, new_entry);
|
|
|
|
}
|
|
|
|
else {
|
2007-01-30 01:36:22 +00:00
|
|
|
/* parent and then me */
|
2006-10-19 23:00:04 +00:00
|
|
|
dup_entry(e, &split[1]);
|
|
|
|
|
|
|
|
new_entry = xmalloc(sizeof(*new_entry));
|
|
|
|
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
|
|
|
|
add_blame_entry(sb, new_entry);
|
|
|
|
}
|
|
|
|
|
2006-10-29 11:07:40 +00:00
|
|
|
if (DEBUG) { /* sanity */
|
2006-10-19 23:00:04 +00:00
|
|
|
struct blame_entry *ent;
|
2006-10-21 06:49:31 +00:00
|
|
|
int lno = sb->ent->lno, corrupt = 0;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
|
|
|
if (lno != ent->lno)
|
|
|
|
corrupt = 1;
|
|
|
|
if (ent->s_lno < 0)
|
|
|
|
corrupt = 1;
|
|
|
|
lno += ent->num_lines;
|
|
|
|
}
|
|
|
|
if (corrupt) {
|
2006-10-21 06:49:31 +00:00
|
|
|
lno = sb->ent->lno;
|
2006-10-19 23:00:04 +00:00
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
|
|
|
printf("L %8d l %8d n %8d\n",
|
|
|
|
lno, ent->lno, ent->num_lines);
|
|
|
|
lno = ent->lno + ent->num_lines;
|
|
|
|
}
|
|
|
|
die("oops");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* After splitting the blame, the origins used by the
|
|
|
|
* on-stack blame_entry should lose one refcnt each.
|
|
|
|
*/
|
2006-10-29 11:07:40 +00:00
|
|
|
static void decref_split(struct blame_entry *split)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
origin_decref(split[i].suspect);
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Helper for blame_chunk(). blame_entry e is known to overlap with
|
|
|
|
* the patch hunk; split it and pass blame to the parent.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
|
|
|
|
int tlno, int plno, int same,
|
|
|
|
struct origin *parent)
|
|
|
|
{
|
|
|
|
struct blame_entry split[3];
|
|
|
|
|
|
|
|
split_overlap(split, e, tlno, plno, same, parent);
|
2006-10-29 11:07:40 +00:00
|
|
|
if (split[1].suspect)
|
|
|
|
split_blame(sb, split, e);
|
|
|
|
decref_split(split);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Find the line number of the last line the target is suspected for.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static int find_last_in_target(struct scoreboard *sb, struct origin *target)
|
|
|
|
{
|
|
|
|
struct blame_entry *e;
|
|
|
|
int last_in_target = -1;
|
|
|
|
|
|
|
|
for (e = sb->ent; e; e = e->next) {
|
2007-03-21 06:37:51 +00:00
|
|
|
if (e->guilty || !same_suspect(e->suspect, target))
|
2006-10-19 23:00:04 +00:00
|
|
|
continue;
|
|
|
|
if (last_in_target < e->s_lno + e->num_lines)
|
|
|
|
last_in_target = e->s_lno + e->num_lines;
|
|
|
|
}
|
|
|
|
return last_in_target;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Process one hunk from the patch between the current suspect for
|
|
|
|
* blame_entry e and its parent. Find and split the overlap, and
|
|
|
|
* pass blame to the overlapping part to the parent.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void blame_chunk(struct scoreboard *sb,
|
|
|
|
int tlno, int plno, int same,
|
|
|
|
struct origin *target, struct origin *parent)
|
|
|
|
{
|
2006-10-21 06:49:31 +00:00
|
|
|
struct blame_entry *e;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2006-10-21 06:49:31 +00:00
|
|
|
for (e = sb->ent; e; e = e->next) {
|
2007-03-21 06:37:51 +00:00
|
|
|
if (e->guilty || !same_suspect(e->suspect, target))
|
2006-10-19 23:00:04 +00:00
|
|
|
continue;
|
|
|
|
if (same <= e->s_lno)
|
|
|
|
continue;
|
|
|
|
if (tlno < e->s_lno + e->num_lines)
|
|
|
|
blame_overlap(sb, e, tlno, plno, same, parent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-25 13:31:36 +00:00
|
|
|
struct blame_chunk_cb_data {
|
|
|
|
struct scoreboard *sb;
|
|
|
|
struct origin *target;
|
|
|
|
struct origin *parent;
|
|
|
|
long plno;
|
|
|
|
long tlno;
|
|
|
|
};
|
|
|
|
|
2012-05-09 20:21:56 +00:00
|
|
|
static int blame_chunk_cb(long start_a, long count_a,
|
|
|
|
long start_b, long count_b, void *data)
|
2008-10-25 13:31:36 +00:00
|
|
|
{
|
|
|
|
struct blame_chunk_cb_data *d = data;
|
2012-05-09 20:21:56 +00:00
|
|
|
blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent);
|
|
|
|
d->plno = start_a + count_a;
|
|
|
|
d->tlno = start_b + count_b;
|
|
|
|
return 0;
|
2008-10-25 13:31:36 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static int pass_blame_to_parent(struct scoreboard *sb,
|
|
|
|
struct origin *target,
|
|
|
|
struct origin *parent)
|
|
|
|
{
|
2008-10-25 13:31:36 +00:00
|
|
|
int last_in_target;
|
2008-10-25 13:30:22 +00:00
|
|
|
mmfile_t file_p, file_o;
|
2010-05-14 09:31:33 +00:00
|
|
|
struct blame_chunk_cb_data d;
|
2012-05-09 20:21:56 +00:00
|
|
|
|
2010-05-14 09:31:33 +00:00
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
d.sb = sb; d.target = target; d.parent = parent;
|
2006-10-19 23:00:04 +00:00
|
|
|
last_in_target = find_last_in_target(sb, target);
|
|
|
|
if (last_in_target < 0)
|
|
|
|
return 1; /* nothing remains for this target */
|
|
|
|
|
2010-06-15 13:58:48 +00:00
|
|
|
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
|
|
|
|
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
|
2008-10-25 13:30:22 +00:00
|
|
|
num_get_patch++;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2012-05-09 20:23:39 +00:00
|
|
|
diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
|
2007-01-30 01:36:22 +00:00
|
|
|
/* The rest (i.e. anything after tlno) are the same as the parent */
|
2008-10-25 13:31:36 +00:00
|
|
|
blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-20 21:51:12 +00:00
|
|
|
static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
|
|
|
|
{
|
|
|
|
unsigned score;
|
|
|
|
const char *cp, *ep;
|
|
|
|
|
|
|
|
if (e->score)
|
|
|
|
return e->score;
|
|
|
|
|
2006-10-21 06:49:31 +00:00
|
|
|
score = 1;
|
2006-10-20 21:51:12 +00:00
|
|
|
cp = nth_line(sb, e->lno);
|
|
|
|
ep = 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;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-20 21:51:12 +00:00
|
|
|
static void copy_split_if_better(struct scoreboard *sb,
|
2006-10-29 11:07:40 +00:00
|
|
|
struct blame_entry *best_so_far,
|
|
|
|
struct blame_entry *this)
|
2006-10-20 01:49:30 +00:00
|
|
|
{
|
2006-10-29 11:07:40 +00:00
|
|
|
int i;
|
|
|
|
|
2006-10-20 01:49:30 +00:00
|
|
|
if (!this[1].suspect)
|
|
|
|
return;
|
2006-10-20 21:51:12 +00:00
|
|
|
if (best_so_far[1].suspect) {
|
|
|
|
if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
|
|
|
|
return;
|
|
|
|
}
|
2006-10-29 11:07:40 +00:00
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
origin_incref(this[i].suspect);
|
|
|
|
decref_split(best_so_far);
|
2006-10-20 01:49:30 +00:00
|
|
|
memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
|
|
|
|
}
|
|
|
|
|
2007-05-05 16:13:26 +00:00
|
|
|
/*
|
|
|
|
* We are looking at a part of the final image represented by
|
|
|
|
* ent (tlno and same are offset by ent->s_lno).
|
|
|
|
* tlno is where we are looking at in the final image.
|
|
|
|
* up to (but not including) same match preimage.
|
|
|
|
* plno is where we are looking at in the preimage.
|
|
|
|
*
|
|
|
|
* <-------------- final image ---------------------->
|
|
|
|
* <------ent------>
|
|
|
|
* ^tlno ^same
|
|
|
|
* <---------preimage----->
|
|
|
|
* ^plno
|
|
|
|
*
|
|
|
|
* All line numbers are 0-based.
|
|
|
|
*/
|
|
|
|
static void handle_split(struct scoreboard *sb,
|
|
|
|
struct blame_entry *ent,
|
|
|
|
int tlno, int plno, int same,
|
|
|
|
struct origin *parent,
|
|
|
|
struct blame_entry *split)
|
|
|
|
{
|
|
|
|
if (ent->num_lines <= tlno)
|
|
|
|
return;
|
|
|
|
if (tlno < same) {
|
|
|
|
struct blame_entry this[3];
|
|
|
|
tlno += ent->s_lno;
|
|
|
|
same += ent->s_lno;
|
|
|
|
split_overlap(this, ent, tlno, plno, same, parent);
|
|
|
|
copy_split_if_better(sb, split, this);
|
|
|
|
decref_split(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-25 13:31:36 +00:00
|
|
|
struct handle_split_cb_data {
|
|
|
|
struct scoreboard *sb;
|
|
|
|
struct blame_entry *ent;
|
|
|
|
struct origin *parent;
|
|
|
|
struct blame_entry *split;
|
|
|
|
long plno;
|
|
|
|
long tlno;
|
|
|
|
};
|
|
|
|
|
2012-05-09 20:22:47 +00:00
|
|
|
static int handle_split_cb(long start_a, long count_a,
|
|
|
|
long start_b, long count_b, void *data)
|
2008-10-25 13:31:36 +00:00
|
|
|
{
|
|
|
|
struct handle_split_cb_data *d = data;
|
2012-05-09 20:22:47 +00:00
|
|
|
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;
|
2008-10-25 13:31:36 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-20 01:49:30 +00:00
|
|
|
static void find_copy_in_blob(struct scoreboard *sb,
|
|
|
|
struct blame_entry *ent,
|
|
|
|
struct origin *parent,
|
2006-10-29 11:07:40 +00:00
|
|
|
struct blame_entry *split,
|
2006-10-20 01:49:30 +00:00
|
|
|
mmfile_t *file_p)
|
|
|
|
{
|
|
|
|
const char *cp;
|
|
|
|
int cnt;
|
|
|
|
mmfile_t file_o;
|
2010-05-14 09:31:33 +00:00
|
|
|
struct handle_split_cb_data d;
|
2012-05-09 20:22:47 +00:00
|
|
|
|
2010-05-14 09:31:33 +00:00
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Prepare mmfile that contains only the lines in ent.
|
|
|
|
*/
|
2006-10-20 01:49:30 +00:00
|
|
|
cp = nth_line(sb, ent->lno);
|
2009-05-01 09:06:36 +00:00
|
|
|
file_o.ptr = (char *) cp;
|
2006-10-20 01:49:30 +00:00
|
|
|
cnt = ent->num_lines;
|
|
|
|
|
|
|
|
while (cnt && cp < sb->final_buf + sb->final_buf_size) {
|
|
|
|
if (*cp++ == '\n')
|
|
|
|
cnt--;
|
|
|
|
}
|
|
|
|
file_o.size = cp - file_o.ptr;
|
|
|
|
|
2007-05-05 16:13:26 +00:00
|
|
|
/*
|
|
|
|
* file_o is a part of final image we are annotating.
|
|
|
|
* file_p partially may match that image.
|
|
|
|
*/
|
2006-10-20 01:49:30 +00:00
|
|
|
memset(split, 0, sizeof(struct blame_entry [3]));
|
2012-05-09 20:23:39 +00:00
|
|
|
diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
|
2007-05-05 16:13:26 +00:00
|
|
|
/* remainder, if any, all match the preimage */
|
2008-10-25 13:31:36 +00:00
|
|
|
handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
|
2006-10-20 01:49:30 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* See if lines currently target is suspected for can be attributed to
|
|
|
|
* parent.
|
|
|
|
*/
|
2006-10-20 01:49:30 +00:00
|
|
|
static int find_move_in_parent(struct scoreboard *sb,
|
|
|
|
struct origin *target,
|
|
|
|
struct origin *parent)
|
|
|
|
{
|
2006-11-04 20:37:02 +00:00
|
|
|
int last_in_target, made_progress;
|
2006-10-21 07:41:38 +00:00
|
|
|
struct blame_entry *e, split[3];
|
2006-10-20 01:49:30 +00:00
|
|
|
mmfile_t file_p;
|
|
|
|
|
|
|
|
last_in_target = find_last_in_target(sb, target);
|
|
|
|
if (last_in_target < 0)
|
|
|
|
return 1; /* nothing remains for this target */
|
|
|
|
|
2010-06-15 13:58:48 +00:00
|
|
|
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
|
2006-11-05 19:51:41 +00:00
|
|
|
if (!file_p.ptr)
|
2006-10-20 01:49:30 +00:00
|
|
|
return 0;
|
|
|
|
|
2006-11-04 20:37:02 +00:00
|
|
|
made_progress = 1;
|
|
|
|
while (made_progress) {
|
|
|
|
made_progress = 0;
|
|
|
|
for (e = sb->ent; e; e = e->next) {
|
2008-07-15 22:00:58 +00:00
|
|
|
if (e->guilty || !same_suspect(e->suspect, target) ||
|
|
|
|
ent_score(sb, e) < blame_move_score)
|
2006-11-04 20:37:02 +00:00
|
|
|
continue;
|
|
|
|
find_copy_in_blob(sb, e, parent, split, &file_p);
|
|
|
|
if (split[1].suspect &&
|
|
|
|
blame_move_score < ent_score(sb, &split[1])) {
|
|
|
|
split_blame(sb, split, e);
|
|
|
|
made_progress = 1;
|
|
|
|
}
|
|
|
|
decref_split(split);
|
|
|
|
}
|
2006-10-20 01:49:30 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-11-05 00:39:03 +00:00
|
|
|
struct blame_list {
|
|
|
|
struct blame_entry *ent;
|
|
|
|
struct blame_entry split[3];
|
|
|
|
};
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Count the number of entries the target is suspected for,
|
|
|
|
* and prepare a list of entry and the best split.
|
|
|
|
*/
|
2006-11-05 00:39:03 +00:00
|
|
|
static struct blame_list *setup_blame_list(struct scoreboard *sb,
|
|
|
|
struct origin *target,
|
2008-07-15 22:00:58 +00:00
|
|
|
int min_score,
|
2006-11-05 00:39:03 +00:00
|
|
|
int *num_ents_p)
|
|
|
|
{
|
|
|
|
struct blame_entry *e;
|
|
|
|
int num_ents, i;
|
|
|
|
struct blame_list *blame_list = NULL;
|
|
|
|
|
|
|
|
for (e = sb->ent, num_ents = 0; e; e = e->next)
|
2008-07-15 22:00:58 +00:00
|
|
|
if (!e->scanned && !e->guilty &&
|
|
|
|
same_suspect(e->suspect, target) &&
|
|
|
|
min_score < ent_score(sb, e))
|
2006-11-05 00:39:03 +00:00
|
|
|
num_ents++;
|
|
|
|
if (num_ents) {
|
|
|
|
blame_list = xcalloc(num_ents, sizeof(struct blame_list));
|
|
|
|
for (e = sb->ent, i = 0; e; e = e->next)
|
2008-07-15 22:00:58 +00:00
|
|
|
if (!e->scanned && !e->guilty &&
|
|
|
|
same_suspect(e->suspect, target) &&
|
|
|
|
min_score < ent_score(sb, e))
|
2006-11-05 00:39:03 +00:00
|
|
|
blame_list[i++].ent = e;
|
|
|
|
}
|
|
|
|
*num_ents_p = num_ents;
|
|
|
|
return blame_list;
|
|
|
|
}
|
|
|
|
|
2008-07-15 22:05:20 +00:00
|
|
|
/*
|
|
|
|
* Reset the scanned status on all entries.
|
|
|
|
*/
|
|
|
|
static void reset_scanned_flag(struct scoreboard *sb)
|
|
|
|
{
|
|
|
|
struct blame_entry *e;
|
|
|
|
for (e = sb->ent; e; e = e->next)
|
|
|
|
e->scanned = 0;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2006-10-20 01:50:17 +00:00
|
|
|
static int find_copy_in_parent(struct scoreboard *sb,
|
|
|
|
struct origin *target,
|
|
|
|
struct commit *parent,
|
|
|
|
struct origin *porigin,
|
|
|
|
int opt)
|
|
|
|
{
|
|
|
|
struct diff_options diff_opts;
|
|
|
|
const char *paths[1];
|
2006-10-21 10:30:53 +00:00
|
|
|
int i, j;
|
2006-11-05 00:39:03 +00:00
|
|
|
int retval;
|
|
|
|
struct blame_list *blame_list;
|
2006-10-21 10:30:53 +00:00
|
|
|
int num_ents;
|
2006-10-20 01:50:17 +00:00
|
|
|
|
2008-07-15 22:00:58 +00:00
|
|
|
blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
|
2006-11-05 00:39:03 +00:00
|
|
|
if (!blame_list)
|
2006-10-20 01:50:17 +00:00
|
|
|
return 1; /* nothing remains for this target */
|
|
|
|
|
|
|
|
diff_setup(&diff_opts);
|
2007-11-10 19:05:14 +00:00
|
|
|
DIFF_OPT_SET(&diff_opts, RECURSIVE);
|
2006-10-20 01:50:17 +00:00
|
|
|
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
|
|
|
|
|
|
|
paths[0] = NULL;
|
|
|
|
diff_tree_setup_paths(paths, &diff_opts);
|
|
|
|
if (diff_setup_done(&diff_opts) < 0)
|
|
|
|
die("diff-setup");
|
2006-11-05 00:39:03 +00:00
|
|
|
|
|
|
|
/* 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.
|
|
|
|
*/
|
2007-05-06 04:18:57 +00:00
|
|
|
if ((opt & PICKAXE_BLAME_COPY_HARDEST)
|
|
|
|
|| ((opt & PICKAXE_BLAME_COPY_HARDER)
|
|
|
|
&& (!porigin || strcmp(target->path, porigin->path))))
|
2007-11-10 19:05:14 +00:00
|
|
|
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
|
2006-11-05 00:39:03 +00:00
|
|
|
|
2007-01-30 09:11:08 +00:00
|
|
|
if (is_null_sha1(target->commit->object.sha1))
|
|
|
|
do_diff_cache(parent->tree->object.sha1, &diff_opts);
|
|
|
|
else
|
|
|
|
diff_tree_sha1(parent->tree->object.sha1,
|
|
|
|
target->commit->tree->object.sha1,
|
|
|
|
"", &diff_opts);
|
2006-10-20 01:50:17 +00:00
|
|
|
|
2007-11-10 19:05:14 +00:00
|
|
|
if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
|
2006-11-05 00:39:03 +00:00
|
|
|
diffcore_std(&diff_opts);
|
2006-10-20 01:50:17 +00:00
|
|
|
|
2006-11-05 00:39:03 +00:00
|
|
|
retval = 0;
|
|
|
|
while (1) {
|
|
|
|
int made_progress = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < diff_queued_diff.nr; i++) {
|
|
|
|
struct diff_filepair *p = diff_queued_diff.queue[i];
|
|
|
|
struct origin *norigin;
|
|
|
|
mmfile_t file_p;
|
|
|
|
struct blame_entry this[3];
|
|
|
|
|
|
|
|
if (!DIFF_FILE_VALID(p->one))
|
|
|
|
continue; /* does not exist in parent */
|
2008-10-03 16:23:50 +00:00
|
|
|
if (S_ISGITLINK(p->one->mode))
|
|
|
|
continue; /* ignore git links */
|
2006-11-05 00:39:03 +00:00
|
|
|
if (porigin && !strcmp(p->one->path, porigin->path))
|
|
|
|
/* find_move already dealt with this path */
|
|
|
|
continue;
|
|
|
|
|
|
|
|
norigin = get_origin(sb, parent, p->one->path);
|
|
|
|
hashcpy(norigin->blob_sha1, p->one->sha1);
|
2010-09-29 11:35:24 +00:00
|
|
|
norigin->mode = p->one->mode;
|
2010-06-15 13:58:48 +00:00
|
|
|
fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
|
2006-11-05 00:39:03 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
origin_decref(norigin);
|
|
|
|
}
|
2006-10-20 01:50:17 +00:00
|
|
|
|
2006-10-21 10:30:53 +00:00
|
|
|
for (j = 0; j < num_ents; j++) {
|
2006-11-05 00:39:03 +00:00
|
|
|
struct blame_entry *split = blame_list[j].split;
|
|
|
|
if (split[1].suspect &&
|
|
|
|
blame_copy_score < ent_score(sb, &split[1])) {
|
|
|
|
split_blame(sb, split, blame_list[j].ent);
|
|
|
|
made_progress = 1;
|
|
|
|
}
|
2008-07-15 22:05:20 +00:00
|
|
|
else
|
|
|
|
blame_list[j].ent->scanned = 1;
|
2006-11-05 00:39:03 +00:00
|
|
|
decref_split(split);
|
2006-10-20 01:50:17 +00:00
|
|
|
}
|
2006-11-05 00:39:03 +00:00
|
|
|
free(blame_list);
|
2006-10-21 10:30:53 +00:00
|
|
|
|
2006-11-05 00:39:03 +00:00
|
|
|
if (!made_progress)
|
|
|
|
break;
|
2008-07-15 22:00:58 +00:00
|
|
|
blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
|
2006-11-05 00:39:03 +00:00
|
|
|
if (!blame_list) {
|
|
|
|
retval = 1;
|
|
|
|
break;
|
|
|
|
}
|
2006-10-20 01:50:17 +00:00
|
|
|
}
|
2008-07-15 22:05:20 +00:00
|
|
|
reset_scanned_flag(sb);
|
2006-11-05 00:39:03 +00:00
|
|
|
diff_flush(&diff_opts);
|
2007-12-11 21:59:55 +00:00
|
|
|
diff_tree_release_paths(&diff_opts);
|
2006-11-05 00:39:03 +00:00
|
|
|
return retval;
|
2006-10-20 01:50:17 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The blobs of origin and porigin exactly match, so everything
|
2006-11-05 19:51:41 +00:00
|
|
|
* origin is suspected for can be blamed on the parent.
|
|
|
|
*/
|
|
|
|
static void pass_whole_blame(struct scoreboard *sb,
|
|
|
|
struct origin *origin, struct origin *porigin)
|
|
|
|
{
|
|
|
|
struct blame_entry *e;
|
|
|
|
|
|
|
|
if (!porigin->file.ptr && origin->file.ptr) {
|
|
|
|
/* Steal its file */
|
|
|
|
porigin->file = origin->file;
|
|
|
|
origin->file.ptr = NULL;
|
|
|
|
}
|
|
|
|
for (e = sb->ent; e; e = e->next) {
|
2007-03-21 06:37:51 +00:00
|
|
|
if (!same_suspect(e->suspect, origin))
|
2006-11-05 19:51:41 +00:00
|
|
|
continue;
|
|
|
|
origin_incref(porigin);
|
|
|
|
origin_decref(e->suspect);
|
|
|
|
e->suspect = porigin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-03 07:56:23 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2008-04-03 05:17:53 +00:00
|
|
|
static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
|
2008-04-03 07:56:23 +00:00
|
|
|
{
|
2008-04-03 05:17:53 +00:00
|
|
|
if (!reverse)
|
|
|
|
return commit->parents;
|
|
|
|
return lookup_decoration(&revs->children, &commit->object);
|
2008-04-03 07:56:23 +00:00
|
|
|
}
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
static int num_scapegoats(struct rev_info *revs, struct commit *commit)
|
2008-04-03 07:56:23 +00:00
|
|
|
{
|
|
|
|
int cnt;
|
2008-04-03 05:17:53 +00:00
|
|
|
struct commit_list *l = first_scapegoat(revs, commit);
|
2008-04-03 07:56:23 +00:00
|
|
|
for (cnt = 0; l; l = l->next)
|
|
|
|
cnt++;
|
|
|
|
return cnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAXSG 16
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2006-10-20 01:49:30 +00:00
|
|
|
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
|
2006-10-19 23:00:04 +00:00
|
|
|
{
|
2008-04-03 05:17:53 +00:00
|
|
|
struct rev_info *revs = sb->revs;
|
2008-04-03 07:56:23 +00:00
|
|
|
int i, pass, num_sg;
|
2006-10-19 23:00:04 +00:00
|
|
|
struct commit *commit = origin->commit;
|
2008-04-03 07:56:23 +00:00
|
|
|
struct commit_list *sg;
|
|
|
|
struct origin *sg_buf[MAXSG];
|
|
|
|
struct origin *porigin, **sg_origin = sg_buf;
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
num_sg = num_scapegoats(revs, commit);
|
2008-04-03 07:56:23 +00:00
|
|
|
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));
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2008-04-03 07:56:23 +00:00
|
|
|
/*
|
|
|
|
* The first pass looks for unrenamed path to optimize for
|
2006-10-31 01:17:41 +00:00
|
|
|
* common cases, then we look for renames in the second pass.
|
|
|
|
*/
|
|
|
|
for (pass = 0; pass < 2; pass++) {
|
|
|
|
struct origin *(*find)(struct scoreboard *,
|
|
|
|
struct commit *, struct origin *);
|
|
|
|
find = pass ? find_rename : find_origin;
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
for (i = 0, sg = first_scapegoat(revs, commit);
|
2008-04-03 07:56:23 +00:00
|
|
|
i < num_sg && sg;
|
|
|
|
sg = sg->next, i++) {
|
|
|
|
struct commit *p = sg->item;
|
2006-11-04 20:20:09 +00:00
|
|
|
int j, same;
|
2006-10-31 01:17:41 +00:00
|
|
|
|
2008-04-03 07:56:23 +00:00
|
|
|
if (sg_origin[i])
|
2006-10-31 01:17:41 +00:00
|
|
|
continue;
|
|
|
|
if (parse_commit(p))
|
|
|
|
continue;
|
2006-10-31 09:00:01 +00:00
|
|
|
porigin = find(sb, p, origin);
|
2006-10-31 01:17:41 +00:00
|
|
|
if (!porigin)
|
|
|
|
continue;
|
|
|
|
if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
|
2006-11-05 19:51:41 +00:00
|
|
|
pass_whole_blame(sb, origin, porigin);
|
2006-10-31 01:17:41 +00:00
|
|
|
origin_decref(porigin);
|
|
|
|
goto finish;
|
|
|
|
}
|
2006-11-04 20:20:09 +00:00
|
|
|
for (j = same = 0; j < i; j++)
|
2008-04-03 07:56:23 +00:00
|
|
|
if (sg_origin[j] &&
|
|
|
|
!hashcmp(sg_origin[j]->blob_sha1,
|
2006-11-04 20:20:09 +00:00
|
|
|
porigin->blob_sha1)) {
|
|
|
|
same = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!same)
|
2008-04-03 07:56:23 +00:00
|
|
|
sg_origin[i] = porigin;
|
2006-11-04 20:20:09 +00:00
|
|
|
else
|
|
|
|
origin_decref(porigin);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-11-05 19:51:41 +00:00
|
|
|
num_commits++;
|
2008-04-03 05:17:53 +00:00
|
|
|
for (i = 0, sg = first_scapegoat(revs, commit);
|
2008-04-03 07:56:23 +00:00
|
|
|
i < num_sg && sg;
|
|
|
|
sg = sg->next, i++) {
|
|
|
|
struct origin *porigin = sg_origin[i];
|
2006-10-19 23:00:04 +00:00
|
|
|
if (!porigin)
|
|
|
|
continue;
|
2008-06-05 05:58:40 +00:00
|
|
|
if (!origin->previous) {
|
|
|
|
origin_incref(porigin);
|
|
|
|
origin->previous = porigin;
|
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
if (pass_blame_to_parent(sb, origin, porigin))
|
2006-10-29 11:07:40 +00:00
|
|
|
goto finish;
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
2006-10-20 01:49:30 +00:00
|
|
|
|
|
|
|
/*
|
2007-01-30 01:36:22 +00:00
|
|
|
* Optionally find moves in parents' files.
|
2006-10-20 01:49:30 +00:00
|
|
|
*/
|
|
|
|
if (opt & PICKAXE_BLAME_MOVE)
|
2008-04-03 05:17:53 +00:00
|
|
|
for (i = 0, sg = first_scapegoat(revs, commit);
|
2008-04-03 07:56:23 +00:00
|
|
|
i < num_sg && sg;
|
|
|
|
sg = sg->next, i++) {
|
|
|
|
struct origin *porigin = sg_origin[i];
|
2006-10-20 01:49:30 +00:00
|
|
|
if (!porigin)
|
|
|
|
continue;
|
|
|
|
if (find_move_in_parent(sb, origin, porigin))
|
2006-10-29 11:07:40 +00:00
|
|
|
goto finish;
|
2006-10-20 01:49:30 +00:00
|
|
|
}
|
|
|
|
|
2006-10-20 01:50:17 +00:00
|
|
|
/*
|
2007-01-30 01:36:22 +00:00
|
|
|
* Optionally find copies from parents' files.
|
2006-10-20 01:50:17 +00:00
|
|
|
*/
|
|
|
|
if (opt & PICKAXE_BLAME_COPY)
|
2008-04-03 05:17:53 +00:00
|
|
|
for (i = 0, sg = first_scapegoat(revs, commit);
|
2008-04-03 07:56:23 +00:00
|
|
|
i < num_sg && sg;
|
|
|
|
sg = sg->next, i++) {
|
|
|
|
struct origin *porigin = sg_origin[i];
|
|
|
|
if (find_copy_in_parent(sb, origin, sg->item,
|
2006-10-20 01:50:17 +00:00
|
|
|
porigin, opt))
|
2006-10-29 11:07:40 +00:00
|
|
|
goto finish;
|
2006-10-20 01:50:17 +00:00
|
|
|
}
|
2006-10-29 11:07:40 +00:00
|
|
|
|
|
|
|
finish:
|
2008-04-03 07:56:23 +00:00
|
|
|
for (i = 0; i < num_sg; i++) {
|
|
|
|
if (sg_origin[i]) {
|
|
|
|
drop_origin_blob(sg_origin[i]);
|
|
|
|
origin_decref(sg_origin[i]);
|
2007-12-12 00:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
drop_origin_blob(origin);
|
2008-04-03 07:56:23 +00:00
|
|
|
if (sg_buf != sg_origin)
|
|
|
|
free(sg_origin);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Information on commits, used for output.
|
|
|
|
*/
|
2011-03-16 07:08:34 +00:00
|
|
|
struct commit_info {
|
2007-03-07 01:44:17 +00:00
|
|
|
const char *author;
|
|
|
|
const char *author_mail;
|
2006-10-19 23:00:04 +00:00
|
|
|
unsigned long author_time;
|
2007-03-07 01:44:17 +00:00
|
|
|
const char *author_tz;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
/* filled only when asked for details */
|
2007-03-07 01:44:17 +00:00
|
|
|
const char *committer;
|
|
|
|
const char *committer_mail;
|
2006-10-19 23:00:04 +00:00
|
|
|
unsigned long committer_time;
|
2007-03-07 01:44:17 +00:00
|
|
|
const char *committer_tz;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2007-03-07 01:44:17 +00:00
|
|
|
const char *summary;
|
2006-10-19 23:00:04 +00:00
|
|
|
};
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Parse author/committer line in the commit object buffer
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void get_ac_line(const char *inbuf, const char *what,
|
2009-02-08 14:34:30 +00:00
|
|
|
int person_len, char *person,
|
|
|
|
int mail_len, char *mail,
|
2007-03-07 01:44:17 +00:00
|
|
|
unsigned long *time, const char **tz)
|
2006-10-19 23:00:04 +00:00
|
|
|
{
|
2007-04-27 07:42:15 +00:00
|
|
|
int len, tzlen, maillen;
|
2009-02-08 14:34:30 +00:00
|
|
|
char *tmp, *endp, *timepos, *mailpos;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
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;
|
2009-02-08 14:34:30 +00:00
|
|
|
if (person_len <= len) {
|
2006-10-19 23:00:04 +00:00
|
|
|
error_out:
|
|
|
|
/* Ugh */
|
2009-02-08 14:34:30 +00:00
|
|
|
*tz = "(unknown)";
|
2009-12-22 18:51:41 +00:00
|
|
|
strcpy(person, *tz);
|
2009-02-08 14:34:30 +00:00
|
|
|
strcpy(mail, *tz);
|
2006-10-19 23:00:04 +00:00
|
|
|
*time = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
memcpy(person, tmp, len);
|
|
|
|
|
|
|
|
tmp = person;
|
|
|
|
tmp += len;
|
|
|
|
*tmp = 0;
|
2009-12-22 18:51:41 +00:00
|
|
|
while (person < tmp && *tmp != ' ')
|
2006-10-19 23:00:04 +00:00
|
|
|
tmp--;
|
2009-12-22 18:51:41 +00:00
|
|
|
if (tmp <= person)
|
|
|
|
goto error_out;
|
2006-10-19 23:00:04 +00:00
|
|
|
*tz = tmp+1;
|
2007-04-27 07:42:15 +00:00
|
|
|
tzlen = (person+len)-(tmp+1);
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
*tmp = 0;
|
2009-12-22 18:51:41 +00:00
|
|
|
while (person < tmp && *tmp != ' ')
|
2006-10-19 23:00:04 +00:00
|
|
|
tmp--;
|
2009-12-22 18:51:41 +00:00
|
|
|
if (tmp <= person)
|
|
|
|
goto error_out;
|
2006-10-19 23:00:04 +00:00
|
|
|
*time = strtoul(tmp, NULL, 10);
|
2007-04-27 07:42:15 +00:00
|
|
|
timepos = tmp;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
*tmp = 0;
|
2011-04-21 22:07:36 +00:00
|
|
|
while (person < tmp && !(*tmp == ' ' && tmp[1] == '<'))
|
2006-10-19 23:00:04 +00:00
|
|
|
tmp--;
|
2009-12-22 18:51:41 +00:00
|
|
|
if (tmp <= person)
|
|
|
|
return;
|
2009-02-08 14:34:30 +00:00
|
|
|
mailpos = tmp + 1;
|
2006-10-19 23:00:04 +00:00
|
|
|
*tmp = 0;
|
2007-04-27 07:42:15 +00:00
|
|
|
maillen = timepos - tmp;
|
2009-02-08 14:34:30 +00:00
|
|
|
memcpy(mail, mailpos, maillen);
|
2007-04-27 07:42:15 +00:00
|
|
|
|
|
|
|
if (!mailmap.nr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mailmap expansion may make the name longer.
|
|
|
|
* make room by pushing stuff down.
|
|
|
|
*/
|
2009-02-08 14:34:30 +00:00
|
|
|
tmp = person + person_len - (tzlen + 1);
|
2007-04-27 07:42:15 +00:00
|
|
|
memmove(tmp, *tz, tzlen);
|
|
|
|
tmp[tzlen] = 0;
|
|
|
|
*tz = tmp;
|
|
|
|
|
|
|
|
/*
|
2009-02-08 14:34:30 +00:00
|
|
|
* Now, convert both name and e-mail using mailmap
|
2007-04-27 07:42:15 +00:00
|
|
|
*/
|
2009-09-01 05:35:10 +00:00
|
|
|
if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
|
2009-02-08 14:34:30 +00:00
|
|
|
/* Add a trailing '>' to email, since map_user returns plain emails
|
|
|
|
Note: It already has '<', since we replace from mail+1 */
|
|
|
|
mailpos = memchr(mail, '\0', mail_len);
|
|
|
|
if (mailpos && mailpos-mail < mail_len - 1) {
|
|
|
|
*mailpos = '>';
|
|
|
|
*(mailpos+1) = '\0';
|
|
|
|
}
|
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void get_commit_info(struct commit *commit,
|
|
|
|
struct commit_info *ret,
|
|
|
|
int detailed)
|
|
|
|
{
|
|
|
|
int len;
|
2010-07-22 13:18:35 +00:00
|
|
|
const char *subject;
|
|
|
|
char *reencoded, *message;
|
2009-02-08 14:34:30 +00:00
|
|
|
static char author_name[1024];
|
|
|
|
static char author_mail[1024];
|
|
|
|
static char committer_name[1024];
|
|
|
|
static char committer_mail[1024];
|
2006-10-19 23:00:04 +00:00
|
|
|
static char summary_buf[1024];
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* We've operated without save_commit_buffer, so
|
2006-10-21 06:49:31 +00:00
|
|
|
* we now need to populate them for output.
|
|
|
|
*/
|
|
|
|
if (!commit->buffer) {
|
2007-02-26 19:55:59 +00:00
|
|
|
enum object_type type;
|
2006-10-21 06:49:31 +00:00
|
|
|
unsigned long size;
|
|
|
|
commit->buffer =
|
2007-02-26 19:55:59 +00:00
|
|
|
read_sha1_file(commit->object.sha1, &type, &size);
|
2007-08-25 08:26:20 +00:00
|
|
|
if (!commit->buffer)
|
|
|
|
die("Cannot read commit %s",
|
|
|
|
sha1_to_hex(commit->object.sha1));
|
2006-10-21 06:49:31 +00:00
|
|
|
}
|
2008-10-21 20:55:57 +00:00
|
|
|
reencoded = reencode_commit_message(commit, NULL);
|
|
|
|
message = reencoded ? reencoded : commit->buffer;
|
2009-02-08 14:34:30 +00:00
|
|
|
ret->author = author_name;
|
|
|
|
ret->author_mail = author_mail;
|
2008-10-21 20:55:57 +00:00
|
|
|
get_ac_line(message, "\nauthor ",
|
2009-02-08 14:34:30 +00:00
|
|
|
sizeof(author_name), author_name,
|
|
|
|
sizeof(author_mail), author_mail,
|
2006-10-19 23:00:04 +00:00
|
|
|
&ret->author_time, &ret->author_tz);
|
|
|
|
|
2008-10-21 20:55:57 +00:00
|
|
|
if (!detailed) {
|
|
|
|
free(reencoded);
|
2006-10-19 23:00:04 +00:00
|
|
|
return;
|
2008-10-21 20:55:57 +00:00
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2009-02-08 14:34:30 +00:00
|
|
|
ret->committer = committer_name;
|
|
|
|
ret->committer_mail = committer_mail;
|
2008-10-21 20:55:57 +00:00
|
|
|
get_ac_line(message, "\ncommitter ",
|
2009-02-08 14:34:30 +00:00
|
|
|
sizeof(committer_name), committer_name,
|
|
|
|
sizeof(committer_mail), committer_mail,
|
2006-10-19 23:00:04 +00:00
|
|
|
&ret->committer_time, &ret->committer_tz);
|
|
|
|
|
|
|
|
ret->summary = summary_buf;
|
2010-07-22 13:18:35 +00:00
|
|
|
len = find_commit_subject(message, &subject);
|
|
|
|
if (len && len < sizeof(summary_buf)) {
|
|
|
|
memcpy(summary_buf, subject, len);
|
|
|
|
summary_buf[len] = 0;
|
|
|
|
} else {
|
2006-10-19 23:00:04 +00:00
|
|
|
sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
|
|
|
|
}
|
2008-10-21 20:55:57 +00:00
|
|
|
free(reencoded);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* To allow LF and other nonportable characters in pathnames,
|
|
|
|
* they are c-style quoted as needed.
|
|
|
|
*/
|
2007-01-28 09:42:31 +00:00
|
|
|
static void write_filename_info(const char *path)
|
|
|
|
{
|
|
|
|
printf("filename ");
|
Full rework of quote_c_style and write_name_quoted.
* quote_c_style works on a strbuf instead of a wild buffer.
* quote_c_style is now clever enough to not add double quotes if not needed.
* write_name_quoted inherits those advantages, but also take a different
set of arguments. Now instead of asking for quotes or not, you pass a
"terminator". If it's \0 then we assume you don't want to escape, else C
escaping is performed. In any case, the terminator is also appended to the
stream. It also no longer takes the prefix/prefix_len arguments, as it's
seldomly used, and makes some optimizations harder.
* write_name_quotedpfx is created to work like write_name_quoted and take
the prefix/prefix_len arguments.
Thanks to those API changes, diff.c has somehow lost weight, thanks to the
removal of functions that were wrappers around the old write_name_quoted
trying to give it a semantics like the new one, but performing a lot of
allocations for this goal. Now we always write directly to the stream, no
intermediate allocation is performed.
As a side effect of the refactor in builtin-apply.c, the length of the bar
graphs in diffstats are not affected anymore by the fact that the path was
clipped.
Signed-off-by: Pierre Habouzit <madcoder@debian.org>
2007-09-19 22:42:15 +00:00
|
|
|
write_name_quoted(path, stdout, '\n');
|
2007-01-28 09:42:31 +00:00
|
|
|
}
|
|
|
|
|
2008-06-05 05:48:42 +00:00
|
|
|
/*
|
|
|
|
* Porcelain/Incremental format wants to show a lot of details per
|
|
|
|
* commit. Instead of repeating this every line, emit it only once,
|
2011-05-09 13:34:02 +00:00
|
|
|
* the first time each commit appears in the output (unless the
|
|
|
|
* user has specifically asked for us to repeat).
|
2008-06-05 05:48:42 +00:00
|
|
|
*/
|
2011-05-09 13:34:02 +00:00
|
|
|
static int emit_one_suspect_detail(struct origin *suspect, int repeat)
|
2008-06-05 05:48:42 +00:00
|
|
|
{
|
|
|
|
struct commit_info ci;
|
|
|
|
|
2011-05-09 13:34:02 +00:00
|
|
|
if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
|
2008-06-05 05:48:42 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
suspect->commit->object.flags |= METAINFO_SHOWN;
|
|
|
|
get_commit_info(suspect->commit, &ci, 1);
|
|
|
|
printf("author %s\n", ci.author);
|
|
|
|
printf("author-mail %s\n", ci.author_mail);
|
|
|
|
printf("author-time %lu\n", ci.author_time);
|
|
|
|
printf("author-tz %s\n", ci.author_tz);
|
|
|
|
printf("committer %s\n", ci.committer);
|
|
|
|
printf("committer-mail %s\n", ci.committer_mail);
|
|
|
|
printf("committer-time %lu\n", ci.committer_time);
|
|
|
|
printf("committer-tz %s\n", ci.committer_tz);
|
|
|
|
printf("summary %s\n", ci.summary);
|
|
|
|
if (suspect->commit->object.flags & UNINTERESTING)
|
|
|
|
printf("boundary\n");
|
2008-06-05 05:58:40 +00:00
|
|
|
if (suspect->previous) {
|
|
|
|
struct origin *prev = suspect->previous;
|
|
|
|
printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
|
|
|
|
write_name_quoted(prev->path, stdout, '\n');
|
|
|
|
}
|
2008-06-05 05:48:42 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The blame_entry is found to be guilty for the range. Mark it
|
|
|
|
* as such, and show it in incremental output.
|
|
|
|
*/
|
2007-01-28 09:34:06 +00:00
|
|
|
static void found_guilty_entry(struct blame_entry *ent)
|
|
|
|
{
|
|
|
|
if (ent->guilty)
|
|
|
|
return;
|
|
|
|
ent->guilty = 1;
|
|
|
|
if (incremental) {
|
|
|
|
struct origin *suspect = ent->suspect;
|
|
|
|
|
|
|
|
printf("%s %d %d %d\n",
|
|
|
|
sha1_to_hex(suspect->commit->object.sha1),
|
|
|
|
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
|
2011-05-09 13:34:02 +00:00
|
|
|
emit_one_suspect_detail(suspect, 0);
|
2007-01-28 09:42:31 +00:00
|
|
|
write_filename_info(suspect->path);
|
2007-06-29 17:40:46 +00:00
|
|
|
maybe_flush_or_die(stdout, "stdout");
|
2007-01-28 09:34:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* The main loop -- while the scoreboard has lines whose true origin
|
2007-02-04 04:49:16 +00:00
|
|
|
* is still unknown, pick one blame_entry, and allow its current
|
2007-01-30 01:36:22 +00:00
|
|
|
* suspect to pass blames to its parents.
|
|
|
|
*/
|
2008-04-03 05:17:53 +00:00
|
|
|
static void assign_blame(struct scoreboard *sb, int opt)
|
2007-01-28 09:34:06 +00:00
|
|
|
{
|
2008-04-03 05:17:53 +00:00
|
|
|
struct rev_info *revs = sb->revs;
|
|
|
|
|
2007-01-28 09:34:06 +00:00
|
|
|
while (1) {
|
|
|
|
struct blame_entry *ent;
|
|
|
|
struct commit *commit;
|
|
|
|
struct origin *suspect = NULL;
|
|
|
|
|
|
|
|
/* find one suspect to break down */
|
|
|
|
for (ent = sb->ent; !suspect && ent; ent = ent->next)
|
|
|
|
if (!ent->guilty)
|
|
|
|
suspect = ent->suspect;
|
|
|
|
if (!suspect)
|
|
|
|
return; /* all done */
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* We will use this suspect later in the loop,
|
|
|
|
* so hold onto it in the meantime.
|
|
|
|
*/
|
2007-01-28 09:34:06 +00:00
|
|
|
origin_incref(suspect);
|
|
|
|
commit = suspect->commit;
|
|
|
|
if (!commit->object.parsed)
|
|
|
|
parse_commit(commit);
|
2008-04-03 05:17:53 +00:00
|
|
|
if (reverse ||
|
|
|
|
(!(commit->object.flags & UNINTERESTING) &&
|
|
|
|
!(revs->max_age != -1 && commit->date < revs->max_age)))
|
2007-01-28 09:34:06 +00:00
|
|
|
pass_blame(sb, suspect, opt);
|
|
|
|
else {
|
|
|
|
commit->object.flags |= UNINTERESTING;
|
|
|
|
if (commit->object.parsed)
|
|
|
|
mark_parents_uninteresting(commit);
|
|
|
|
}
|
|
|
|
/* treat root commit as boundary */
|
|
|
|
if (!commit->parents && !show_root)
|
|
|
|
commit->object.flags |= UNINTERESTING;
|
|
|
|
|
|
|
|
/* Take responsibility for the remaining entries */
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next)
|
2007-03-21 06:37:51 +00:00
|
|
|
if (same_suspect(ent->suspect, suspect))
|
2007-01-28 09:34:06 +00:00
|
|
|
found_guilty_entry(ent);
|
|
|
|
origin_decref(suspect);
|
|
|
|
|
|
|
|
if (DEBUG) /* sanity */
|
|
|
|
sanity_check_refcnt(sb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *format_time(unsigned long time, const char *tz_str,
|
|
|
|
int show_raw_time)
|
|
|
|
{
|
|
|
|
static char time_buf[128];
|
2009-02-20 22:51:11 +00:00
|
|
|
const char *time_str;
|
|
|
|
int time_len;
|
|
|
|
int tz;
|
2007-01-28 09:34:06 +00:00
|
|
|
|
|
|
|
if (show_raw_time) {
|
2011-12-08 10:25:54 +00:00
|
|
|
snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
|
2007-01-28 09:34:06 +00:00
|
|
|
}
|
2009-02-20 22:51:11 +00:00
|
|
|
else {
|
|
|
|
tz = atoi(tz_str);
|
|
|
|
time_str = show_date(time, tz, blame_date_mode);
|
|
|
|
time_len = strlen(time_str);
|
|
|
|
memcpy(time_buf, time_str, time_len);
|
|
|
|
memset(time_buf + time_len, ' ', blame_date_width - time_len);
|
|
|
|
}
|
2007-01-28 09:34:06 +00:00
|
|
|
return time_buf;
|
|
|
|
}
|
|
|
|
|
2006-10-19 23:00:04 +00:00
|
|
|
#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
|
2006-10-20 21:51:12 +00:00
|
|
|
#define OUTPUT_SHOW_SCORE 0100
|
2007-04-12 22:50:45 +00:00
|
|
|
#define OUTPUT_NO_AUTHOR 0200
|
2010-10-16 06:57:51 +00:00
|
|
|
#define OUTPUT_SHOW_EMAIL 0400
|
2011-05-09 13:34:42 +00:00
|
|
|
#define OUTPUT_LINE_PORCELAIN 01000
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2011-05-09 13:34:02 +00:00
|
|
|
static void emit_porcelain_details(struct origin *suspect, int repeat)
|
|
|
|
{
|
|
|
|
if (emit_one_suspect_detail(suspect, repeat) ||
|
|
|
|
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
|
|
|
|
write_filename_info(suspect->path);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
|
|
|
|
int opt)
|
2006-10-19 23:00:04 +00:00
|
|
|
{
|
2011-05-09 13:34:42 +00:00
|
|
|
int repeat = opt & OUTPUT_LINE_PORCELAIN;
|
2006-10-19 23:00:04 +00:00
|
|
|
int cnt;
|
|
|
|
const char *cp;
|
|
|
|
struct origin *suspect = ent->suspect;
|
|
|
|
char hex[41];
|
|
|
|
|
|
|
|
strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
|
|
|
|
printf("%s%c%d %d %d\n",
|
|
|
|
hex,
|
2010-06-04 09:32:11 +00:00
|
|
|
ent->guilty ? ' ' : '*', /* purely for debugging */
|
2006-10-19 23:00:04 +00:00
|
|
|
ent->s_lno + 1,
|
|
|
|
ent->lno + 1,
|
|
|
|
ent->num_lines);
|
2011-05-09 13:34:42 +00:00
|
|
|
emit_porcelain_details(suspect, repeat);
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
cp = nth_line(sb, ent->lno);
|
|
|
|
for (cnt = 0; cnt < ent->num_lines; cnt++) {
|
|
|
|
char ch;
|
2011-05-09 13:34:42 +00:00
|
|
|
if (cnt) {
|
2006-10-19 23:00:04 +00:00
|
|
|
printf("%s %d %d\n", hex,
|
|
|
|
ent->s_lno + 1 + cnt,
|
|
|
|
ent->lno + 1 + cnt);
|
2011-05-09 13:34:42 +00:00
|
|
|
if (repeat)
|
|
|
|
emit_porcelain_details(suspect, 1);
|
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
putchar('\t');
|
|
|
|
do {
|
|
|
|
ch = *cp++;
|
|
|
|
putchar(ch);
|
|
|
|
} while (ch != '\n' &&
|
|
|
|
cp < sb->final_buf + sb->final_buf_size);
|
|
|
|
}
|
2009-10-20 03:06:28 +00:00
|
|
|
|
|
|
|
if (sb->final_buf_size && cp[-1] != '\n')
|
|
|
|
putchar('\n');
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
|
|
|
|
{
|
|
|
|
int cnt;
|
|
|
|
const char *cp;
|
|
|
|
struct origin *suspect = ent->suspect;
|
|
|
|
struct commit_info ci;
|
|
|
|
char hex[41];
|
|
|
|
int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
|
|
|
|
|
|
|
|
get_commit_info(suspect->commit, &ci, 1);
|
|
|
|
strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
|
|
|
|
|
|
|
|
cp = nth_line(sb, ent->lno);
|
|
|
|
for (cnt = 0; cnt < ent->num_lines; cnt++) {
|
|
|
|
char ch;
|
2011-04-06 02:20:50 +00:00
|
|
|
int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev;
|
2006-12-02 04:45:45 +00:00
|
|
|
|
|
|
|
if (suspect->commit->object.flags & UNINTERESTING) {
|
2007-02-06 09:52:04 +00:00
|
|
|
if (blank_boundary)
|
|
|
|
memset(hex, ' ', length);
|
2008-09-05 07:57:35 +00:00
|
|
|
else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
|
2006-12-18 22:04:38 +00:00
|
|
|
length--;
|
|
|
|
putchar('^');
|
|
|
|
}
|
2006-12-02 04:45:45 +00:00
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2006-12-02 04:45:45 +00:00
|
|
|
printf("%.*s", length, hex);
|
2010-10-16 06:57:51 +00:00
|
|
|
if (opt & OUTPUT_ANNOTATE_COMPAT) {
|
|
|
|
const char *name;
|
|
|
|
if (opt & OUTPUT_SHOW_EMAIL)
|
|
|
|
name = ci.author_mail;
|
|
|
|
else
|
|
|
|
name = ci.author;
|
|
|
|
printf("\t(%10s\t%10s\t%d)", name,
|
2006-10-19 23:00:04 +00:00
|
|
|
format_time(ci.author_time, ci.author_tz,
|
|
|
|
show_raw_time),
|
|
|
|
ent->lno + 1 + cnt);
|
2010-10-16 06:57:51 +00:00
|
|
|
} else {
|
2006-10-20 21:51:12 +00:00
|
|
|
if (opt & OUTPUT_SHOW_SCORE)
|
2006-10-29 11:07:40 +00:00
|
|
|
printf(" %*d %02d",
|
|
|
|
max_score_digits, ent->score,
|
|
|
|
ent->suspect->refcnt);
|
2006-10-19 23:00:04 +00:00
|
|
|
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);
|
2007-04-12 22:50:45 +00:00
|
|
|
|
2009-01-30 09:41:29 +00:00
|
|
|
if (!(opt & OUTPUT_NO_AUTHOR)) {
|
2010-10-16 06:57:51 +00:00
|
|
|
const char *name;
|
|
|
|
int pad;
|
|
|
|
if (opt & OUTPUT_SHOW_EMAIL)
|
|
|
|
name = ci.author_mail;
|
|
|
|
else
|
|
|
|
name = ci.author;
|
|
|
|
pad = longest_author - utf8_strwidth(name);
|
2009-01-30 09:41:29 +00:00
|
|
|
printf(" (%s%*s %10s",
|
2010-10-16 06:57:51 +00:00
|
|
|
name, pad, "",
|
2007-04-12 22:50:45 +00:00
|
|
|
format_time(ci.author_time,
|
|
|
|
ci.author_tz,
|
|
|
|
show_raw_time));
|
2009-01-30 09:41:29 +00:00
|
|
|
}
|
2007-04-12 22:50:45 +00:00
|
|
|
printf(" %*d) ",
|
2006-10-19 23:00:04 +00:00
|
|
|
max_digits, ent->lno + 1 + cnt);
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
ch = *cp++;
|
|
|
|
putchar(ch);
|
|
|
|
} while (ch != '\n' &&
|
|
|
|
cp < sb->final_buf + sb->final_buf_size);
|
|
|
|
}
|
2009-10-20 03:06:28 +00:00
|
|
|
|
|
|
|
if (sb->final_buf_size && cp[-1] != '\n')
|
|
|
|
putchar('\n');
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void output(struct scoreboard *sb, int option)
|
|
|
|
{
|
|
|
|
struct blame_entry *ent;
|
|
|
|
|
|
|
|
if (option & OUTPUT_PORCELAIN) {
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
|
|
|
struct blame_entry *oth;
|
|
|
|
struct origin *suspect = ent->suspect;
|
|
|
|
struct commit *commit = suspect->commit;
|
|
|
|
if (commit->object.flags & MORE_THAN_ONE_PATH)
|
|
|
|
continue;
|
|
|
|
for (oth = ent->next; oth; oth = oth->next) {
|
|
|
|
if ((oth->suspect->commit != commit) ||
|
|
|
|
!strcmp(oth->suspect->path, suspect->path))
|
|
|
|
continue;
|
|
|
|
commit->object.flags |= MORE_THAN_ONE_PATH;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
|
|
|
if (option & OUTPUT_PORCELAIN)
|
2011-05-09 13:34:02 +00:00
|
|
|
emit_porcelain(sb, ent, option);
|
2006-10-20 21:51:12 +00:00
|
|
|
else {
|
2006-10-19 23:00:04 +00:00
|
|
|
emit_other(sb, ent, option);
|
2006-10-20 21:51:12 +00:00
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* To allow quick access to the contents of nth line in the
|
|
|
|
* final image, prepare an index in the scoreboard.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static int prepare_lines(struct scoreboard *sb)
|
|
|
|
{
|
|
|
|
const char *buf = sb->final_buf;
|
|
|
|
unsigned long len = sb->final_buf_size;
|
|
|
|
int num = 0, incomplete = 0, bol = 1;
|
|
|
|
|
|
|
|
if (len && buf[len-1] != '\n')
|
|
|
|
incomplete++; /* incomplete line at the end */
|
|
|
|
while (len--) {
|
|
|
|
if (bol) {
|
|
|
|
sb->lineno = xrealloc(sb->lineno,
|
2009-05-01 09:06:36 +00:00
|
|
|
sizeof(int *) * (num + 1));
|
2006-10-19 23:00:04 +00:00
|
|
|
sb->lineno[num] = buf - sb->final_buf;
|
|
|
|
bol = 0;
|
|
|
|
}
|
|
|
|
if (*buf++ == '\n') {
|
|
|
|
num++;
|
|
|
|
bol = 1;
|
|
|
|
}
|
|
|
|
}
|
2006-10-21 01:48:18 +00:00
|
|
|
sb->lineno = xrealloc(sb->lineno,
|
2009-05-01 09:06:36 +00:00
|
|
|
sizeof(int *) * (num + incomplete + 1));
|
2006-10-21 01:48:18 +00:00
|
|
|
sb->lineno[num + incomplete] = buf - sb->final_buf;
|
2006-10-19 23:00:04 +00:00
|
|
|
sb->num_lines = num + incomplete;
|
|
|
|
return sb->num_lines;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Add phony grafts for use with -S; this is primarily to
|
2008-08-30 11:12:53 +00:00
|
|
|
* support git's cvsserver that wants to give a linear history
|
2007-01-30 01:36:22 +00:00
|
|
|
* to its clients.
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static int read_ancestry(const char *graft_file)
|
|
|
|
{
|
|
|
|
FILE *fp = fopen(graft_file, "r");
|
|
|
|
char buf[1024];
|
|
|
|
if (!fp)
|
|
|
|
return -1;
|
|
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
|
|
/* The format is just "Commit Parent1 Parent2 ...\n" */
|
|
|
|
int len = strlen(buf);
|
|
|
|
struct commit_graft *graft = read_graft_line(buf, len);
|
2006-11-10 21:39:01 +00:00
|
|
|
if (graft)
|
|
|
|
register_commit_graft(graft, 0);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-07-02 07:54:00 +00:00
|
|
|
static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
|
|
|
|
{
|
|
|
|
const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
|
|
|
|
auto_abbrev);
|
|
|
|
int len = strlen(uniq);
|
|
|
|
if (auto_abbrev < len)
|
|
|
|
return len;
|
|
|
|
return auto_abbrev;
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* How many columns do we need to show line numbers, authors,
|
|
|
|
* and filenames?
|
|
|
|
*/
|
2006-10-19 23:00:04 +00:00
|
|
|
static void find_alignment(struct scoreboard *sb, int *option)
|
|
|
|
{
|
|
|
|
int longest_src_lines = 0;
|
|
|
|
int longest_dst_lines = 0;
|
2006-10-20 21:51:12 +00:00
|
|
|
unsigned largest_score = 0;
|
2006-10-19 23:00:04 +00:00
|
|
|
struct blame_entry *e;
|
2012-07-02 07:54:00 +00:00
|
|
|
int compute_auto_abbrev = (abbrev < 0);
|
|
|
|
int auto_abbrev = default_abbrev;
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
for (e = sb->ent; e; e = e->next) {
|
|
|
|
struct origin *suspect = e->suspect;
|
|
|
|
struct commit_info ci;
|
|
|
|
int num;
|
|
|
|
|
2012-07-02 07:54:00 +00:00
|
|
|
if (compute_auto_abbrev)
|
|
|
|
auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
|
2006-11-29 06:29:18 +00:00
|
|
|
if (strcmp(suspect->path, sb->path))
|
|
|
|
*option |= OUTPUT_SHOW_NAME;
|
|
|
|
num = strlen(suspect->path);
|
|
|
|
if (longest_file < num)
|
|
|
|
longest_file = num;
|
2006-10-19 23:00:04 +00:00
|
|
|
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
|
|
|
|
suspect->commit->object.flags |= METAINFO_SHOWN;
|
|
|
|
get_commit_info(suspect->commit, &ci, 1);
|
2010-10-16 06:57:51 +00:00
|
|
|
if (*option & OUTPUT_SHOW_EMAIL)
|
|
|
|
num = utf8_strwidth(ci.author_mail);
|
|
|
|
else
|
|
|
|
num = utf8_strwidth(ci.author);
|
2006-10-19 23:00:04 +00:00
|
|
|
if (longest_author < num)
|
|
|
|
longest_author = num;
|
|
|
|
}
|
|
|
|
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;
|
2006-10-20 21:51:12 +00:00
|
|
|
if (largest_score < ent_score(sb, e))
|
|
|
|
largest_score = ent_score(sb, e);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
2012-02-12 14:16:20 +00:00
|
|
|
max_orig_digits = decimal_width(longest_src_lines);
|
|
|
|
max_digits = decimal_width(longest_dst_lines);
|
|
|
|
max_score_digits = decimal_width(largest_score);
|
2012-07-02 07:54:00 +00:00
|
|
|
|
|
|
|
if (compute_auto_abbrev)
|
|
|
|
/* one more abbrev length is needed for the boundary commit */
|
|
|
|
abbrev = auto_abbrev + 1;
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* For debugging -- origin is refcounted, and this asserts that
|
|
|
|
* we do not underflow.
|
|
|
|
*/
|
2006-10-29 11:07:40 +00:00
|
|
|
static void sanity_check_refcnt(struct scoreboard *sb)
|
|
|
|
{
|
|
|
|
int baa = 0;
|
|
|
|
struct blame_entry *ent;
|
|
|
|
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
2006-10-30 22:27:52 +00:00
|
|
|
/* Nobody should have zero or negative refcnt */
|
2006-11-05 03:18:50 +00:00
|
|
|
if (ent->suspect->refcnt <= 0) {
|
|
|
|
fprintf(stderr, "%s in %s has negative refcnt %d\n",
|
|
|
|
ent->suspect->path,
|
|
|
|
sha1_to_hex(ent->suspect->commit->object.sha1),
|
|
|
|
ent->suspect->refcnt);
|
2006-10-30 22:27:52 +00:00
|
|
|
baa = 1;
|
2006-11-05 03:18:50 +00:00
|
|
|
}
|
2006-10-30 22:27:52 +00:00
|
|
|
}
|
2006-10-29 11:07:40 +00:00
|
|
|
if (baa) {
|
|
|
|
int opt = 0160;
|
|
|
|
find_alignment(sb, &opt);
|
|
|
|
output(sb, opt);
|
2006-11-05 03:18:50 +00:00
|
|
|
die("Baa %d!", baa);
|
2006-10-29 11:07:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Used for the command line parsing; check if the path exists
|
|
|
|
* in the working tree.
|
|
|
|
*/
|
2008-07-21 18:03:49 +00:00
|
|
|
static int has_string_in_work_tree(const char *path)
|
2006-10-19 23:00:04 +00:00
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
return !lstat(path, &st);
|
|
|
|
}
|
|
|
|
|
2006-10-20 22:37:12 +00:00
|
|
|
static unsigned parse_score(const char *arg)
|
|
|
|
{
|
|
|
|
char *end;
|
|
|
|
unsigned long score = strtoul(arg, &end, 10);
|
|
|
|
if (*end)
|
|
|
|
return 0;
|
|
|
|
return score;
|
|
|
|
}
|
|
|
|
|
2006-11-02 07:22:49 +00:00
|
|
|
static const char *add_prefix(const char *prefix, const char *path)
|
|
|
|
{
|
2008-02-01 04:07:04 +00:00
|
|
|
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
|
2006-11-02 07:22:49 +00:00
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Parsing of (comma separated) one item in the -L option
|
|
|
|
*/
|
2006-11-07 01:08:32 +00:00
|
|
|
static const char *parse_loc(const char *spec,
|
|
|
|
struct scoreboard *sb, long lno,
|
|
|
|
long begin, long *ret)
|
|
|
|
{
|
|
|
|
char *term;
|
|
|
|
const char *line;
|
|
|
|
long num;
|
|
|
|
int reg_error;
|
|
|
|
regex_t regexp;
|
|
|
|
regmatch_t match[1];
|
|
|
|
|
2006-11-08 00:20:02 +00:00
|
|
|
/* Allow "-L <something>,+20" to mean starting at <something>
|
|
|
|
* for 20 lines, or "-L <something>,-5" for 5 lines ending at
|
|
|
|
* <something>.
|
|
|
|
*/
|
|
|
|
if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
|
|
|
|
num = strtol(spec + 1, &term, 10);
|
|
|
|
if (term != spec + 1) {
|
|
|
|
if (spec[0] == '-')
|
|
|
|
num = 0 - num;
|
|
|
|
if (0 < num)
|
|
|
|
*ret = begin + num - 2;
|
|
|
|
else if (!num)
|
|
|
|
*ret = begin;
|
|
|
|
else
|
|
|
|
*ret = begin + num;
|
|
|
|
return term;
|
|
|
|
}
|
|
|
|
return spec;
|
|
|
|
}
|
2006-11-07 01:08:32 +00:00
|
|
|
num = strtol(spec, &term, 10);
|
|
|
|
if (term != spec) {
|
|
|
|
*ret = num;
|
|
|
|
return term;
|
|
|
|
}
|
|
|
|
if (spec[0] != '/')
|
|
|
|
return spec;
|
|
|
|
|
|
|
|
/* it could be a regexp of form /.../ */
|
2009-05-01 09:06:36 +00:00
|
|
|
for (term = (char *) spec + 1; *term && *term != '/'; term++) {
|
2006-11-07 01:08:32 +00:00
|
|
|
if (*term == '\\')
|
|
|
|
term++;
|
|
|
|
}
|
|
|
|
if (*term != '/')
|
|
|
|
return spec;
|
|
|
|
|
|
|
|
/* try [spec+1 .. term-1] as regexp */
|
|
|
|
*term = 0;
|
|
|
|
begin--; /* input is in human terms */
|
|
|
|
line = nth_line(sb, begin);
|
|
|
|
|
|
|
|
if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
|
|
|
|
!(reg_error = regexec(®exp, line, 1, match, 0))) {
|
|
|
|
const char *cp = line + match[0].rm_so;
|
|
|
|
const char *nline;
|
|
|
|
|
|
|
|
while (begin++ < lno) {
|
|
|
|
nline = nth_line(sb, begin);
|
|
|
|
if (line <= cp && cp < nline)
|
|
|
|
break;
|
|
|
|
line = nline;
|
|
|
|
}
|
|
|
|
*ret = begin;
|
|
|
|
regfree(®exp);
|
|
|
|
*term++ = '/';
|
|
|
|
return term;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
char errbuf[1024];
|
|
|
|
regerror(reg_error, ®exp, errbuf, 1024);
|
|
|
|
die("-L parameter '%s': %s", spec + 1, errbuf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* Parsing of -L option
|
|
|
|
*/
|
2006-11-07 01:08:32 +00:00
|
|
|
static void prepare_blame_range(struct scoreboard *sb,
|
|
|
|
const char *bottomtop,
|
|
|
|
long lno,
|
|
|
|
long *bottom, long *top)
|
|
|
|
{
|
|
|
|
const char *term;
|
|
|
|
|
|
|
|
term = parse_loc(bottomtop, sb, lno, 1, bottom);
|
|
|
|
if (*term == ',') {
|
|
|
|
term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
|
|
|
|
if (*term)
|
2006-11-09 02:47:54 +00:00
|
|
|
usage(blame_usage);
|
2006-11-07 01:08:32 +00:00
|
|
|
}
|
|
|
|
if (*term)
|
2006-11-09 02:47:54 +00:00
|
|
|
usage(blame_usage);
|
2006-11-07 01:08:32 +00:00
|
|
|
}
|
|
|
|
|
2008-05-14 17:46:53 +00:00
|
|
|
static int git_blame_config(const char *var, const char *value, void *cb)
|
2006-12-18 22:04:38 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2009-02-20 22:51:11 +00:00
|
|
|
if (!strcmp(var, "blame.date")) {
|
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(var);
|
|
|
|
blame_date_mode = parse_date_format(value);
|
|
|
|
return 0;
|
|
|
|
}
|
2010-06-15 13:58:48 +00:00
|
|
|
|
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-07 18:23:02 +00:00
|
|
|
if (userdiff_config(var, value) < 0)
|
2010-06-15 13:58:48 +00:00
|
|
|
return -1;
|
|
|
|
|
2008-05-14 17:46:53 +00:00
|
|
|
return git_default_config(var, value, cb);
|
2006-12-18 22:04:38 +00:00
|
|
|
}
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
/*
|
|
|
|
* Prepare a dummy commit that represents the work tree (or staged) item.
|
|
|
|
* Note that annotating work tree item never works in the reverse.
|
|
|
|
*/
|
2010-06-15 13:58:48 +00:00
|
|
|
static struct commit *fake_working_tree_commit(struct diff_options *opt,
|
|
|
|
const char *path,
|
|
|
|
const char *contents_from)
|
2007-01-30 09:11:08 +00:00
|
|
|
{
|
|
|
|
struct commit *commit;
|
|
|
|
struct origin *origin;
|
|
|
|
unsigned char head_sha1[20];
|
2008-10-09 19:12:12 +00:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2007-01-30 09:11:08 +00:00
|
|
|
const char *ident;
|
|
|
|
time_t now;
|
|
|
|
int size, len;
|
|
|
|
struct cache_entry *ce;
|
|
|
|
unsigned mode;
|
|
|
|
|
|
|
|
if (get_sha1("HEAD", head_sha1))
|
|
|
|
die("No such ref: HEAD");
|
|
|
|
|
|
|
|
time(&now);
|
|
|
|
commit = xcalloc(1, sizeof(*commit));
|
|
|
|
commit->parents = xcalloc(1, sizeof(*commit->parents));
|
|
|
|
commit->parents->item = lookup_commit_reference(head_sha1);
|
|
|
|
commit->object.parsed = 1;
|
|
|
|
commit->date = now;
|
|
|
|
commit->object.type = OBJ_COMMIT;
|
|
|
|
|
|
|
|
origin = make_origin(commit, path);
|
|
|
|
|
|
|
|
if (!contents_from || strcmp("-", contents_from)) {
|
|
|
|
struct stat st;
|
|
|
|
const char *read_from;
|
2011-11-07 17:33:34 +00:00
|
|
|
char *buf_ptr;
|
2010-06-15 13:58:48 +00:00
|
|
|
unsigned long buf_len;
|
2007-01-30 09:11:08 +00:00
|
|
|
|
|
|
|
if (contents_from) {
|
|
|
|
if (stat(contents_from, &st) < 0)
|
2009-06-27 15:58:47 +00:00
|
|
|
die_errno("Cannot stat '%s'", contents_from);
|
2007-01-30 09:11:08 +00:00
|
|
|
read_from = contents_from;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (lstat(path, &st) < 0)
|
2009-06-27 15:58:47 +00:00
|
|
|
die_errno("Cannot lstat '%s'", path);
|
2007-01-30 09:11:08 +00:00
|
|
|
read_from = path;
|
|
|
|
}
|
|
|
|
mode = canon_mode(st.st_mode);
|
2010-06-15 13:58:48 +00:00
|
|
|
|
2007-01-30 09:11:08 +00:00
|
|
|
switch (st.st_mode & S_IFMT) {
|
|
|
|
case S_IFREG:
|
2010-06-15 13:58:48 +00:00
|
|
|
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
|
2011-11-07 17:33:34 +00:00
|
|
|
textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
|
|
|
|
strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
|
2010-06-15 13:58:48 +00:00
|
|
|
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
|
2009-06-27 15:58:47 +00:00
|
|
|
die_errno("cannot open or read '%s'", read_from);
|
2007-01-30 09:11:08 +00:00
|
|
|
break;
|
|
|
|
case S_IFLNK:
|
2008-12-17 20:37:53 +00:00
|
|
|
if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
|
2009-06-27 15:58:47 +00:00
|
|
|
die_errno("cannot readlink '%s'", read_from);
|
2007-01-30 09:11:08 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
die("unsupported file type %s", read_from);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Reading from stdin */
|
|
|
|
contents_from = "standard input";
|
|
|
|
mode = 0;
|
2007-09-10 10:35:04 +00:00
|
|
|
if (strbuf_read(&buf, 0, 0) < 0)
|
2009-06-27 15:58:46 +00:00
|
|
|
die_errno("failed to read from stdin");
|
2007-01-30 09:11:08 +00:00
|
|
|
}
|
safecrlf: Add mechanism to warn about irreversible crlf conversions
CRLF conversion bears a slight chance of corrupting data.
autocrlf=true will convert CRLF to LF during commit and LF to
CRLF during checkout. A file that contains a mixture of LF and
CRLF before the commit cannot be recreated by git. For text
files this is the right thing to do: it corrects line endings
such that we have only LF line endings in the repository.
But for binary files that are accidentally classified as text the
conversion can corrupt data.
If you recognize such corruption early you can easily fix it by
setting the conversion type explicitly in .gitattributes. Right
after committing you still have the original file in your work
tree and this file is not yet corrupted. You can explicitly tell
git that this file is binary and git will handle the file
appropriately.
Unfortunately, the desired effect of cleaning up text files with
mixed line endings and the undesired effect of corrupting binary
files cannot be distinguished. In both cases CRLFs are removed
in an irreversible way. For text files this is the right thing
to do because CRLFs are line endings, while for binary files
converting CRLFs corrupts data.
This patch adds a mechanism that can either warn the user about
an irreversible conversion or can even refuse to convert. The
mechanism is controlled by the variable core.safecrlf, with the
following values:
- false: disable safecrlf mechanism
- warn: warn about irreversible conversions
- true: refuse irreversible conversions
The default is to warn. Users are only affected by this default
if core.autocrlf is set. But the current default of git is to
leave core.autocrlf unset, so users will not see warnings unless
they deliberately chose to activate the autocrlf mechanism.
The safecrlf mechanism's details depend on the git command. The
general principles when safecrlf is active (not false) are:
- we warn/error out if files in the work tree can modified in an
irreversible way without giving the user a chance to backup the
original file.
- for read-only operations that do not modify files in the work tree
we do not not print annoying warnings.
There are exceptions. Even though...
- "git add" itself does not touch the files in the work tree, the
next checkout would, so the safety triggers;
- "git apply" to update a text file with a patch does touch the files
in the work tree, but the operation is about text files and CRLF
conversion is about fixing the line ending inconsistencies, so the
safety does not trigger;
- "git diff" itself does not touch the files in the work tree, it is
often run to inspect the changes you intend to next "git add". To
catch potential problems early, safety triggers.
The concept of a safety check was originally proposed in a similar
way by Linus Torvalds. Thanks to Dimitry Potapov for insisting
on getting the naked LF/autocrlf=true case right.
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
2008-02-06 11:25:58 +00:00
|
|
|
convert_to_git(path, buf.buf, buf.len, &buf, 0);
|
2007-09-06 11:20:09 +00:00
|
|
|
origin->file.ptr = buf.buf;
|
|
|
|
origin->file.size = buf.len;
|
|
|
|
pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
|
2007-01-30 09:11:08 +00:00
|
|
|
commit->util = origin;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the current index, replace the path entry with
|
|
|
|
* origin->blob_sha1 without mucking with its mode or type
|
|
|
|
* bits; we are not going to write this index out -- we just
|
|
|
|
* want to run "diff-index --cached".
|
|
|
|
*/
|
|
|
|
discard_cache();
|
|
|
|
read_cache();
|
|
|
|
|
|
|
|
len = strlen(path);
|
|
|
|
if (!mode) {
|
|
|
|
int pos = cache_name_pos(path, len);
|
|
|
|
if (0 <= pos)
|
2008-01-15 00:03:17 +00:00
|
|
|
mode = active_cache[pos]->ce_mode;
|
2007-01-30 09:11:08 +00:00
|
|
|
else
|
|
|
|
/* Let's not bother reading from HEAD tree */
|
|
|
|
mode = S_IFREG | 0644;
|
|
|
|
}
|
|
|
|
size = cache_entry_size(len);
|
|
|
|
ce = xcalloc(1, size);
|
|
|
|
hashcpy(ce->sha1, origin->blob_sha1);
|
|
|
|
memcpy(ce->name, path, len);
|
2012-07-11 09:22:37 +00:00
|
|
|
ce->ce_flags = create_ce_flags(0);
|
|
|
|
ce->ce_namelen = len;
|
2007-01-30 09:11:08 +00:00
|
|
|
ce->ce_mode = create_ce_mode(mode);
|
|
|
|
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We are not going to write this out, so this does not matter
|
|
|
|
* right now, but someday we might optimize diff-index --cached
|
|
|
|
* with cache-tree information.
|
|
|
|
*/
|
|
|
|
cache_tree_invalidate_path(active_cache_tree, path);
|
|
|
|
|
|
|
|
commit->buffer = xmalloc(400);
|
|
|
|
ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
|
2007-04-14 21:26:20 +00:00
|
|
|
snprintf(commit->buffer, 400,
|
2007-01-30 09:11:08 +00:00
|
|
|
"tree 0000000000000000000000000000000000000000\n"
|
|
|
|
"parent %s\n"
|
|
|
|
"author %s\n"
|
|
|
|
"committer %s\n\n"
|
|
|
|
"Version of %s from %s\n",
|
|
|
|
sha1_to_hex(head_sha1),
|
|
|
|
ident, ident, path, contents_from ? contents_from : path);
|
|
|
|
return commit;
|
|
|
|
}
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
static const char *prepare_final(struct scoreboard *sb)
|
2008-04-03 05:17:53 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
const char *final_commit_name = NULL;
|
2008-04-03 05:17:53 +00:00
|
|
|
struct rev_info *revs = sb->revs;
|
2008-04-03 05:17:53 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* There must be one and only one positive commit in the
|
|
|
|
* revs->pending array.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < revs->pending.nr; i++) {
|
|
|
|
struct object *obj = revs->pending.objects[i].item;
|
|
|
|
if (obj->flags & UNINTERESTING)
|
|
|
|
continue;
|
|
|
|
while (obj->type == OBJ_TAG)
|
|
|
|
obj = deref_tag(obj, NULL, 0);
|
|
|
|
if (obj->type != OBJ_COMMIT)
|
|
|
|
die("Non commit %s?", revs->pending.objects[i].name);
|
|
|
|
if (sb->final)
|
|
|
|
die("More than one commit to dig from %s and %s?",
|
|
|
|
revs->pending.objects[i].name,
|
|
|
|
final_commit_name);
|
|
|
|
sb->final = (struct commit *) obj;
|
|
|
|
final_commit_name = revs->pending.objects[i].name;
|
|
|
|
}
|
|
|
|
return final_commit_name;
|
|
|
|
}
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
static const char *prepare_initial(struct scoreboard *sb)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
const char *final_commit_name = NULL;
|
|
|
|
struct rev_info *revs = sb->revs;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* There must be one and only one negative commit, and it must be
|
|
|
|
* the boundary.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < revs->pending.nr; i++) {
|
|
|
|
struct object *obj = revs->pending.objects[i].item;
|
|
|
|
if (!(obj->flags & UNINTERESTING))
|
|
|
|
continue;
|
|
|
|
while (obj->type == OBJ_TAG)
|
|
|
|
obj = deref_tag(obj, NULL, 0);
|
|
|
|
if (obj->type != OBJ_COMMIT)
|
|
|
|
die("Non commit %s?", revs->pending.objects[i].name);
|
|
|
|
if (sb->final)
|
|
|
|
die("More than one commit to dig down to %s and %s?",
|
|
|
|
revs->pending.objects[i].name,
|
|
|
|
final_commit_name);
|
|
|
|
sb->final = (struct commit *) obj;
|
|
|
|
final_commit_name = revs->pending.objects[i].name;
|
|
|
|
}
|
|
|
|
if (!final_commit_name)
|
|
|
|
die("No commit to dig down to?");
|
|
|
|
return final_commit_name;
|
|
|
|
}
|
|
|
|
|
2008-07-08 13:19:34 +00: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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
|
|
|
|
{
|
|
|
|
const char **bottomtop = option->value;
|
|
|
|
if (!arg)
|
|
|
|
return -1;
|
|
|
|
if (*bottomtop)
|
|
|
|
die("More than one '-L n,m' option given");
|
|
|
|
*bottomtop = arg;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-11-09 02:47:54 +00:00
|
|
|
int cmd_blame(int argc, const char **argv, const char *prefix)
|
2006-10-19 23:00:04 +00:00
|
|
|
{
|
|
|
|
struct rev_info revs;
|
|
|
|
const char *path;
|
|
|
|
struct scoreboard sb;
|
|
|
|
struct origin *o;
|
|
|
|
struct blame_entry *ent;
|
2008-07-08 13:19:35 +00:00
|
|
|
long dashdash_pos, bottom, top, lno;
|
2006-10-19 23:00:04 +00:00
|
|
|
const char *final_commit_name = NULL;
|
2007-02-26 19:55:59 +00:00
|
|
|
enum object_type type;
|
2008-07-08 13:19:34 +00:00
|
|
|
|
|
|
|
static const char *bottomtop = NULL;
|
|
|
|
static int output_option = 0, opt = 0;
|
|
|
|
static int show_stats = 0;
|
|
|
|
static const char *revs_file = NULL;
|
|
|
|
static const char *contents_from = NULL;
|
|
|
|
static const struct option options[] = {
|
|
|
|
OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
|
|
|
|
OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
|
|
|
|
OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
|
|
|
|
OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
|
|
|
|
OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
|
|
|
|
OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
|
|
|
|
OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
|
|
|
|
OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
|
2011-05-09 13:34:42 +00:00
|
|
|
OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
|
2008-07-08 13:19:34 +00:00
|
|
|
OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
|
|
|
|
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
|
|
|
|
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
|
|
|
|
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
|
2010-10-16 06:57:51 +00:00
|
|
|
OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
|
2008-07-08 13:19:34 +00:00
|
|
|
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
|
2012-04-11 20:08:08 +00:00
|
|
|
OPT_BIT(0, "minimal", &xdl_opts, "Spend extra cycles to find better match", XDF_NEED_MINIMAL),
|
2008-07-08 13:19:34 +00:00
|
|
|
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
|
|
|
|
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
|
|
|
|
{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
|
|
|
|
{ OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
|
|
|
|
OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
|
2011-04-06 02:20:50 +00:00
|
|
|
OPT__ABBREV(&abbrev),
|
2008-07-08 13:19:34 +00:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
|
|
|
struct parse_opt_ctx_t ctx;
|
2008-09-05 07:57:35 +00:00
|
|
|
int cmd_is_annotate = !strcmp(argv[0], "annotate");
|
2007-02-06 09:52:04 +00:00
|
|
|
|
2008-05-14 17:46:53 +00:00
|
|
|
git_config(git_blame_config, NULL);
|
2008-07-08 13:19:34 +00:00
|
|
|
init_revisions(&revs, NULL);
|
2009-02-20 22:51:11 +00:00
|
|
|
revs.date_mode = blame_date_mode;
|
2010-06-15 13:58:48 +00:00
|
|
|
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
|
2009-02-20 22:51:11 +00:00
|
|
|
|
2006-10-21 06:49:31 +00:00
|
|
|
save_commit_buffer = 0;
|
2008-07-08 13:19:35 +00:00
|
|
|
dashdash_pos = 0;
|
2006-10-21 06:49:31 +00:00
|
|
|
|
2010-12-06 07:57:42 +00:00
|
|
|
parse_options_start(&ctx, argc, argv, prefix, options,
|
|
|
|
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
|
2008-07-08 13:19:34 +00:00
|
|
|
for (;;) {
|
|
|
|
switch (parse_options_step(&ctx, options, blame_opt_usage)) {
|
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
exit(129);
|
|
|
|
case PARSE_OPT_DONE:
|
2008-07-08 13:19:35 +00:00
|
|
|
if (ctx.argv[0])
|
|
|
|
dashdash_pos = ctx.cpidx;
|
2008-07-08 13:19:34 +00:00
|
|
|
goto parse_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(ctx.argv[0], "--reverse")) {
|
|
|
|
ctx.argv[0] = "--children";
|
|
|
|
reverse = 1;
|
|
|
|
}
|
2008-07-09 21:38:34 +00:00
|
|
|
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
|
2008-07-08 13:19:34 +00:00
|
|
|
}
|
|
|
|
parse_done:
|
|
|
|
argc = parse_options_end(&ctx);
|
|
|
|
|
2012-07-02 07:54:00 +00:00
|
|
|
if (0 < abbrev)
|
|
|
|
/* one more abbrev length is needed for the boundary commit */
|
|
|
|
abbrev++;
|
2011-04-06 02:20:50 +00:00
|
|
|
|
2009-03-18 07:13:03 +00:00
|
|
|
if (revs_file && read_ancestry(revs_file))
|
2009-06-27 15:58:46 +00:00
|
|
|
die_errno("reading graft file '%s' failed", revs_file);
|
2009-03-18 07:13:03 +00:00
|
|
|
|
2009-02-20 22:51:11 +00:00
|
|
|
if (cmd_is_annotate) {
|
2008-09-05 07:57:35 +00:00
|
|
|
output_option |= OUTPUT_ANNOTATE_COMPAT;
|
2009-02-20 22:51:11 +00:00
|
|
|
blame_date_mode = DATE_ISO8601;
|
|
|
|
} else {
|
|
|
|
blame_date_mode = revs.date_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The maximum width used to show the dates */
|
|
|
|
switch (blame_date_mode) {
|
|
|
|
case DATE_RFC2822:
|
|
|
|
blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
|
|
|
|
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_SHORT:
|
|
|
|
blame_date_width = sizeof("2006-10-19");
|
|
|
|
break;
|
|
|
|
case DATE_RELATIVE:
|
|
|
|
/* "normal" is used as the fallback for "relative" */
|
|
|
|
case DATE_LOCAL:
|
|
|
|
case DATE_NORMAL:
|
|
|
|
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
blame_date_width -= 1; /* strip the null */
|
2008-09-05 07:57:35 +00:00
|
|
|
|
2008-07-31 07:05:22 +00:00
|
|
|
if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
|
|
|
|
opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
|
|
|
|
PICKAXE_BLAME_COPY_HARDER);
|
|
|
|
|
2006-10-20 22:37:12 +00:00
|
|
|
if (!blame_move_score)
|
|
|
|
blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
|
|
|
|
if (!blame_copy_score)
|
|
|
|
blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
|
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* We have collected options unknown to us in argv[1..unk]
|
2006-10-19 23:00:04 +00:00
|
|
|
* which are to be passed to revision machinery if we are
|
2007-02-04 04:49:16 +00:00
|
|
|
* going to do the "bottom" processing.
|
2006-10-19 23:00:04 +00:00
|
|
|
*
|
|
|
|
* The remaining are:
|
|
|
|
*
|
2010-08-22 11:12:12 +00:00
|
|
|
* (1) if dashdash_pos != 0, it is either
|
2008-07-08 13:19:35 +00:00
|
|
|
* "blame [revisions] -- <path>" or
|
|
|
|
* "blame -- <path> <rev>"
|
2006-10-19 23:00:04 +00:00
|
|
|
*
|
2010-08-22 11:12:12 +00:00
|
|
|
* (2) otherwise, it is one of the two:
|
2008-07-08 13:19:35 +00:00
|
|
|
* "blame [revisions] <path>"
|
|
|
|
* "blame <path> <rev>"
|
2006-10-19 23:00:04 +00:00
|
|
|
*
|
2008-07-08 13:19:35 +00:00
|
|
|
* Note that we must strip out <path> from the arguments: we do not
|
|
|
|
* want the path pruning but we may want "bottom" processing.
|
2006-10-19 23:00:04 +00:00
|
|
|
*/
|
2008-07-08 13:19:35 +00:00
|
|
|
if (dashdash_pos) {
|
|
|
|
switch (argc - dashdash_pos - 1) {
|
|
|
|
case 2: /* (1b) */
|
|
|
|
if (argc != 4)
|
2008-07-08 13:19:34 +00:00
|
|
|
usage_with_options(blame_opt_usage, options);
|
2008-07-08 13:19:35 +00:00
|
|
|
/* 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);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
2008-07-08 13:19:35 +00:00
|
|
|
} else {
|
|
|
|
if (argc < 2)
|
2008-07-08 13:19:34 +00:00
|
|
|
usage_with_options(blame_opt_usage, options);
|
2008-07-08 13:19:35 +00:00
|
|
|
path = add_prefix(prefix, argv[argc - 1]);
|
2008-07-21 18:03:49 +00:00
|
|
|
if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
|
2008-07-08 13:19:35 +00:00
|
|
|
path = add_prefix(prefix, argv[1]);
|
|
|
|
argv[1] = argv[2];
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
2008-07-08 13:19:35 +00:00
|
|
|
argv[argc - 1] = "--";
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2008-07-08 13:19:35 +00:00
|
|
|
setup_work_tree();
|
2008-07-21 18:03:49 +00:00
|
|
|
if (!has_string_in_work_tree(path))
|
2009-06-27 15:58:46 +00:00
|
|
|
die_errno("cannot stat path '%s'", path);
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2009-11-03 14:59:18 +00:00
|
|
|
revs.disable_stdin = 1;
|
2008-07-08 13:19:35 +00:00
|
|
|
setup_revisions(argc, argv, &revs, NULL);
|
2006-10-19 23:00:04 +00:00
|
|
|
memset(&sb, 0, sizeof(sb));
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
sb.revs = &revs;
|
|
|
|
if (!reverse)
|
|
|
|
final_commit_name = prepare_final(&sb);
|
|
|
|
else if (contents_from)
|
|
|
|
die("--contents and --children do not blend well.");
|
|
|
|
else
|
|
|
|
final_commit_name = prepare_initial(&sb);
|
2006-10-19 23:00:04 +00:00
|
|
|
|
|
|
|
if (!sb.final) {
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* "--not A B -- path" without anything positive;
|
2007-01-30 09:11:08 +00:00
|
|
|
* do not default to HEAD, but use the working tree
|
|
|
|
* or "--contents".
|
2007-01-30 01:36:22 +00:00
|
|
|
*/
|
2007-11-03 12:22:55 +00:00
|
|
|
setup_work_tree();
|
2010-06-15 13:58:48 +00:00
|
|
|
sb.final = fake_working_tree_commit(&sb.revs->diffopt,
|
|
|
|
path, contents_from);
|
2007-01-30 09:11:08 +00:00
|
|
|
add_pending_object(&revs, &(sb.final->object), ":");
|
2006-10-19 23:00:04 +00:00
|
|
|
}
|
2007-01-30 09:11:08 +00:00
|
|
|
else if (contents_from)
|
|
|
|
die("Cannot use --contents with final commit object name");
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2007-01-30 01:36:22 +00:00
|
|
|
/*
|
|
|
|
* If we have bottom, this will mark the ancestors of the
|
2006-10-19 23:00:04 +00:00
|
|
|
* bottom commits we would reach while traversing as
|
|
|
|
* uninteresting.
|
|
|
|
*/
|
2008-02-18 07:31:56 +00:00
|
|
|
if (prepare_revision_walk(&revs))
|
|
|
|
die("revision walk setup failed");
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2007-01-30 09:11:08 +00:00
|
|
|
if (is_null_sha1(sb.final->object.sha1)) {
|
|
|
|
char *buf;
|
|
|
|
o = sb.final->util;
|
|
|
|
buf = xmalloc(o->file.size + 1);
|
|
|
|
memcpy(buf, o->file.ptr, o->file.size + 1);
|
|
|
|
sb.final_buf = buf;
|
|
|
|
sb.final_buf_size = o->file.size;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
o = get_origin(&sb, sb.final, path);
|
2010-09-29 11:35:24 +00:00
|
|
|
if (fill_blob_sha1_and_mode(o))
|
2007-01-30 09:11:08 +00:00
|
|
|
die("no such path %s in %s", path, final_commit_name);
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2010-06-15 13:58:48 +00:00
|
|
|
if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
|
2010-09-29 11:35:24 +00:00
|
|
|
textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf,
|
2010-06-15 13:58:48 +00:00
|
|
|
&sb.final_buf_size))
|
|
|
|
;
|
|
|
|
else
|
|
|
|
sb.final_buf = read_sha1_file(o->blob_sha1, &type,
|
|
|
|
&sb.final_buf_size);
|
|
|
|
|
2007-08-25 08:26:20 +00:00
|
|
|
if (!sb.final_buf)
|
|
|
|
die("Cannot read blob %s for path %s",
|
|
|
|
sha1_to_hex(o->blob_sha1),
|
|
|
|
path);
|
2007-01-30 09:11:08 +00:00
|
|
|
}
|
2006-11-05 19:51:41 +00:00
|
|
|
num_read_blob++;
|
2006-10-19 23:00:04 +00:00
|
|
|
lno = prepare_lines(&sb);
|
|
|
|
|
2006-11-07 01:08:32 +00:00
|
|
|
bottom = top = 0;
|
|
|
|
if (bottomtop)
|
|
|
|
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
|
|
|
|
if (bottom && top && top < bottom) {
|
|
|
|
long tmp;
|
|
|
|
tmp = top; top = bottom; bottom = tmp;
|
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
if (bottom < 1)
|
|
|
|
bottom = 1;
|
|
|
|
if (top < 1)
|
|
|
|
top = lno;
|
|
|
|
bottom--;
|
2010-02-09 03:48:13 +00:00
|
|
|
if (lno < top || lno < bottom)
|
2006-10-19 23:00:04 +00:00
|
|
|
die("file %s has only %lu lines", path, lno);
|
|
|
|
|
|
|
|
ent = xcalloc(1, sizeof(*ent));
|
|
|
|
ent->lno = bottom;
|
|
|
|
ent->num_lines = top - bottom;
|
|
|
|
ent->suspect = o;
|
|
|
|
ent->s_lno = bottom;
|
|
|
|
|
|
|
|
sb.ent = ent;
|
|
|
|
sb.path = path;
|
|
|
|
|
2009-02-08 14:34:27 +00:00
|
|
|
read_mailmap(&mailmap, NULL);
|
2007-04-27 07:42:15 +00:00
|
|
|
|
2007-11-03 12:22:53 +00:00
|
|
|
if (!incremental)
|
|
|
|
setup_pager();
|
|
|
|
|
2008-04-03 05:17:53 +00:00
|
|
|
assign_blame(&sb, opt);
|
2006-10-19 23:00:04 +00:00
|
|
|
|
2007-01-28 09:34:06 +00:00
|
|
|
if (incremental)
|
|
|
|
return 0;
|
|
|
|
|
2006-10-19 23:00:04 +00:00
|
|
|
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;
|
|
|
|
}
|
2006-11-05 19:51:41 +00:00
|
|
|
|
2006-11-05 19:52:43 +00:00
|
|
|
if (show_stats) {
|
2006-11-05 19:51:41 +00:00
|
|
|
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);
|
|
|
|
}
|
2006-10-19 23:00:04 +00:00
|
|
|
return 0;
|
|
|
|
}
|