2006-10-20 01:00:04 +02:00
|
|
|
/*
|
2009-02-20 23:51:11 +01:00
|
|
|
* Blame
|
2006-10-20 01:00:04 +02:00
|
|
|
*
|
2014-04-26 01:56:49 +02:00
|
|
|
* Copyright (c) 2006, 2014 by its authors
|
|
|
|
* See COPYING for licensing conditions
|
2006-10-20 01:00:04 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "cache.h"
|
|
|
|
#include "builtin.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "revision.h"
|
2007-01-28 10:34:06 +01:00
|
|
|
#include "quote.h"
|
2008-07-21 20:03:49 +02:00
|
|
|
#include "string-list.h"
|
2007-04-27 09:42:15 +02:00
|
|
|
#include "mailmap.h"
|
2008-07-08 15:19:34 +02:00
|
|
|
#include "parse-options.h"
|
2014-04-26 01:56:49 +02:00
|
|
|
#include "prio-queue.h"
|
2009-01-30 10:41:29 +01:00
|
|
|
#include "utf8.h"
|
2010-06-15 15:58:48 +02:00
|
|
|
#include "userdiff.h"
|
2013-03-28 17:47:30 +01:00
|
|
|
#include "line-range.h"
|
2013-08-06 15:59:38 +02:00
|
|
|
#include "line-log.h"
|
2015-05-19 23:44:23 +02:00
|
|
|
#include "dir.h"
|
2015-12-13 01:51:03 +01:00
|
|
|
#include "progress.h"
|
2017-05-24 07:15:32 +02:00
|
|
|
#include "blame.h"
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2015-04-02 23:26:56 +02:00
|
|
|
static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
|
2008-07-08 15:19:34 +02:00
|
|
|
|
|
|
|
static const char *blame_opt_usage[] = {
|
|
|
|
blame_usage,
|
|
|
|
"",
|
2015-01-13 08:44:47 +01:00
|
|
|
N_("<rev-opts> are documented in git-rev-list(1)"),
|
2008-07-08 15:19:34 +02:00
|
|
|
NULL
|
|
|
|
};
|
2006-10-20 01:00:04 +02:00
|
|
|
|
|
|
|
static int longest_file;
|
|
|
|
static int longest_author;
|
|
|
|
static int max_orig_digits;
|
|
|
|
static int max_digits;
|
2006-10-20 23:51:12 +02:00
|
|
|
static int max_score_digits;
|
2006-12-18 23:04:38 +01:00
|
|
|
static int show_root;
|
2008-04-03 07:17:53 +02:00
|
|
|
static int reverse;
|
2006-12-18 23:04:38 +01:00
|
|
|
static int blank_boundary;
|
2007-01-28 10:34:06 +01:00
|
|
|
static int incremental;
|
2010-05-02 15:04:41 +02:00
|
|
|
static int xdl_opts;
|
2011-04-06 04:20:50 +02:00
|
|
|
static int abbrev = -1;
|
2012-09-21 22:52:25 +02:00
|
|
|
static int no_whole_file_rename;
|
2015-12-13 01:51:03 +01:00
|
|
|
static int show_progress;
|
2009-02-20 23:51:11 +01:00
|
|
|
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 18:55:02 +02:00
|
|
|
static struct date_mode blame_date_mode = { DATE_ISO8601 };
|
2009-02-20 23:51:11 +01:00
|
|
|
static size_t blame_date_width;
|
|
|
|
|
2016-06-13 12:04:20 +02:00
|
|
|
static struct string_list mailmap = STRING_LIST_INIT_NODUP;
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2006-10-29 12:07:40 +01:00
|
|
|
#ifndef DEBUG
|
|
|
|
#define DEBUG 0
|
|
|
|
#endif
|
|
|
|
|
2006-10-21 00:37:12 +02:00
|
|
|
static unsigned blame_move_score;
|
|
|
|
static unsigned blame_copy_score;
|
2006-10-20 03:49:30 +02:00
|
|
|
|
2014-03-25 14:23:26 +01:00
|
|
|
/* Remember to update object flag allocation in object.h */
|
2006-10-20 01:00:04 +02:00
|
|
|
#define METAINFO_SHOWN (1u<<12)
|
|
|
|
#define MORE_THAN_ONE_PATH (1u<<13)
|
2006-10-20 03:49:30 +02:00
|
|
|
|
2015-12-13 01:51:03 +01:00
|
|
|
struct progress_info {
|
|
|
|
struct progress *progress;
|
|
|
|
int blamed_lines;
|
2006-11-05 01:39:03 +01:00
|
|
|
};
|
|
|
|
|
2013-03-28 17:47:30 +01:00
|
|
|
static const char *nth_line_cb(void *data, long lno)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
2017-05-24 07:15:17 +02:00
|
|
|
return blame_nth_line((struct blame_scoreboard *)data, lno);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
|
|
|
* Information on commits, used for output.
|
|
|
|
*/
|
2011-03-16 08:08:34 +01:00
|
|
|
struct commit_info {
|
2013-01-05 22:26:40 +01:00
|
|
|
struct strbuf author;
|
|
|
|
struct strbuf author_mail;
|
2017-04-26 21:29:31 +02:00
|
|
|
timestamp_t author_time;
|
2013-01-05 22:26:40 +01:00
|
|
|
struct strbuf author_tz;
|
2006-10-20 01:00:04 +02:00
|
|
|
|
|
|
|
/* filled only when asked for details */
|
2013-01-05 22:26:40 +01:00
|
|
|
struct strbuf committer;
|
|
|
|
struct strbuf committer_mail;
|
2017-04-26 21:29:31 +02:00
|
|
|
timestamp_t committer_time;
|
2013-01-05 22:26:40 +01:00
|
|
|
struct strbuf committer_tz;
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2013-01-05 22:26:40 +01:00
|
|
|
struct strbuf summary;
|
2006-10-20 01:00:04 +02:00
|
|
|
};
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
|
|
|
* Parse author/committer line in the commit object buffer
|
|
|
|
*/
|
2006-10-20 01:00:04 +02:00
|
|
|
static void get_ac_line(const char *inbuf, const char *what,
|
2013-01-05 22:26:40 +01:00
|
|
|
struct strbuf *name, struct strbuf *mail,
|
2017-04-26 21:29:31 +02:00
|
|
|
timestamp_t *time, struct strbuf *tz)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
2013-01-05 22:26:38 +01:00
|
|
|
struct ident_split ident;
|
2013-01-05 22:26:40 +01:00
|
|
|
size_t len, maillen, namelen;
|
|
|
|
char *tmp, *endp;
|
|
|
|
const char *namebuf, *mailbuf;
|
2006-10-20 01:00:04 +02: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;
|
2013-01-05 22:26:38 +01:00
|
|
|
|
|
|
|
if (split_ident_line(&ident, tmp, len)) {
|
2006-10-20 01:00:04 +02:00
|
|
|
error_out:
|
|
|
|
/* Ugh */
|
2013-01-05 22:26:40 +01:00
|
|
|
tmp = "(unknown)";
|
|
|
|
strbuf_addstr(name, tmp);
|
|
|
|
strbuf_addstr(mail, tmp);
|
|
|
|
strbuf_addstr(tz, tmp);
|
2006-10-20 01:00:04 +02:00
|
|
|
*time = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-05 22:26:38 +01:00
|
|
|
namelen = ident.name_end - ident.name_begin;
|
2013-01-05 22:26:40 +01:00
|
|
|
namebuf = ident.name_begin;
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2013-01-05 22:26:40 +01:00
|
|
|
maillen = ident.mail_end - ident.mail_begin;
|
|
|
|
mailbuf = ident.mail_begin;
|
2007-04-27 09:42:15 +02:00
|
|
|
|
2013-04-17 20:33:54 +02:00
|
|
|
if (ident.date_begin && ident.date_end)
|
|
|
|
*time = strtoul(ident.date_begin, NULL, 10);
|
|
|
|
else
|
|
|
|
*time = 0;
|
2007-04-27 09:42:15 +02:00
|
|
|
|
2013-04-17 20:33:54 +02:00
|
|
|
if (ident.tz_begin && ident.tz_end)
|
|
|
|
strbuf_add(tz, ident.tz_begin, ident.tz_end - ident.tz_begin);
|
|
|
|
else
|
|
|
|
strbuf_addstr(tz, "(unknown)");
|
2007-04-27 09:42:15 +02:00
|
|
|
|
|
|
|
/*
|
2009-02-08 15:34:30 +01:00
|
|
|
* Now, convert both name and e-mail using mailmap
|
2007-04-27 09:42:15 +02:00
|
|
|
*/
|
2013-01-05 22:26:40 +01:00
|
|
|
map_user(&mailmap, &mailbuf, &maillen,
|
|
|
|
&namebuf, &namelen);
|
|
|
|
|
|
|
|
strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf);
|
|
|
|
strbuf_add(name, namebuf, namelen);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void commit_info_init(struct commit_info *ci)
|
|
|
|
{
|
|
|
|
|
|
|
|
strbuf_init(&ci->author, 0);
|
|
|
|
strbuf_init(&ci->author_mail, 0);
|
|
|
|
strbuf_init(&ci->author_tz, 0);
|
|
|
|
strbuf_init(&ci->committer, 0);
|
|
|
|
strbuf_init(&ci->committer_mail, 0);
|
|
|
|
strbuf_init(&ci->committer_tz, 0);
|
|
|
|
strbuf_init(&ci->summary, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void commit_info_destroy(struct commit_info *ci)
|
|
|
|
{
|
|
|
|
|
|
|
|
strbuf_release(&ci->author);
|
|
|
|
strbuf_release(&ci->author_mail);
|
|
|
|
strbuf_release(&ci->author_tz);
|
|
|
|
strbuf_release(&ci->committer);
|
|
|
|
strbuf_release(&ci->committer_mail);
|
|
|
|
strbuf_release(&ci->committer_tz);
|
|
|
|
strbuf_release(&ci->summary);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void get_commit_info(struct commit *commit,
|
|
|
|
struct commit_info *ret,
|
|
|
|
int detailed)
|
|
|
|
{
|
|
|
|
int len;
|
2012-10-18 02:12:55 +02:00
|
|
|
const char *subject, *encoding;
|
2014-06-10 23:39:30 +02:00
|
|
|
const char *message;
|
2013-01-05 22:26:40 +01:00
|
|
|
|
|
|
|
commit_info_init(ret);
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2012-10-18 02:12:55 +02:00
|
|
|
encoding = get_log_output_encoding();
|
2013-04-19 01:08:40 +02:00
|
|
|
message = logmsg_reencode(commit, NULL, encoding);
|
2008-10-21 22:55:57 +02:00
|
|
|
get_ac_line(message, "\nauthor ",
|
2013-01-05 22:26:40 +01:00
|
|
|
&ret->author, &ret->author_mail,
|
2006-10-20 01:00:04 +02:00
|
|
|
&ret->author_time, &ret->author_tz);
|
|
|
|
|
2008-10-21 22:55:57 +02:00
|
|
|
if (!detailed) {
|
2014-06-10 23:41:39 +02:00
|
|
|
unuse_commit_buffer(commit, message);
|
2006-10-20 01:00:04 +02:00
|
|
|
return;
|
2008-10-21 22:55:57 +02:00
|
|
|
}
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2008-10-21 22:55:57 +02:00
|
|
|
get_ac_line(message, "\ncommitter ",
|
2013-01-05 22:26:40 +01:00
|
|
|
&ret->committer, &ret->committer_mail,
|
2006-10-20 01:00:04 +02:00
|
|
|
&ret->committer_time, &ret->committer_tz);
|
|
|
|
|
2010-07-22 15:18:35 +02:00
|
|
|
len = find_commit_subject(message, &subject);
|
2013-01-05 22:26:40 +01:00
|
|
|
if (len)
|
|
|
|
strbuf_add(&ret->summary, subject, len);
|
|
|
|
else
|
2015-11-10 03:22:28 +01:00
|
|
|
strbuf_addf(&ret->summary, "(%s)", oid_to_hex(&commit->object.oid));
|
2013-01-05 22:26:40 +01:00
|
|
|
|
2014-06-10 23:41:39 +02:00
|
|
|
unuse_commit_buffer(commit, message);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
blame: output porcelain "previous" header for each file
It's possible for content currently found in one file to
have originated in two separate files, each of which may
have been modified in some single older commit. The
--porcelain output generates an incorrect "previous" header
in this case, whereas --line-porcelain gets it right. The
problem is that the porcelain output tries to omit repeated
details of commits, and treats "previous" as a property of
the commit, when it is really a property of the blamed block
of lines.
Let's look at an example. In a case like this, you might see
this output from --line-porcelain:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
author ...
committer ...
previous SOME_SHA1^ file_two
filename file_two
...some different content....
The "filename" fields tell us that the two lines are from
two different files. But notice that the filename also
appears in the "previous" field, which tells us where to
start a re-blame. The second content line never appeared in
file_one at all, so we would obviously need to re-blame from
file_two (or possibly even some other file, if had just been
renamed to file_two in SOME_SHA1).
So far so good. Now here's what --porcelain looks like:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
filename file_two
...some different content....
We've dropped the author and committer fields from the
second line, as they would just be repeats. But we can't
omit "filename", because it depends on the actual block of
blamed lines, not just the commit. This is handled by
emit_porcelain_details(), which will show the filename
either if it is the first mention of the commit _or_ if the
commit has multiple paths in it.
But we don't give "previous" the same handling. It's written
inside emit_one_suspect_detail(), which bails early if we've
already seen that commit. And so the output above is wrong;
a reader would assume that the correct place to re-blame
line two is from file_one, but that's obviously nonsense.
Let's treat "previous" the same as "filename", and show it
fresh whenever we know we are in a confusing case like this.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:20:51 +01:00
|
|
|
* Write out any suspect information which depends on the path. This must be
|
|
|
|
* handled separately from emit_one_suspect_detail(), because a given commit
|
|
|
|
* may have changes in multiple paths. So this needs to appear each time
|
|
|
|
* we mention a new group.
|
|
|
|
*
|
2007-01-30 02:36:22 +01:00
|
|
|
* To allow LF and other nonportable characters in pathnames,
|
|
|
|
* they are c-style quoted as needed.
|
|
|
|
*/
|
2017-05-24 07:15:12 +02:00
|
|
|
static void write_filename_info(struct blame_origin *suspect)
|
2007-01-28 10:42:31 +01:00
|
|
|
{
|
blame: output porcelain "previous" header for each file
It's possible for content currently found in one file to
have originated in two separate files, each of which may
have been modified in some single older commit. The
--porcelain output generates an incorrect "previous" header
in this case, whereas --line-porcelain gets it right. The
problem is that the porcelain output tries to omit repeated
details of commits, and treats "previous" as a property of
the commit, when it is really a property of the blamed block
of lines.
Let's look at an example. In a case like this, you might see
this output from --line-porcelain:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
author ...
committer ...
previous SOME_SHA1^ file_two
filename file_two
...some different content....
The "filename" fields tell us that the two lines are from
two different files. But notice that the filename also
appears in the "previous" field, which tells us where to
start a re-blame. The second content line never appeared in
file_one at all, so we would obviously need to re-blame from
file_two (or possibly even some other file, if had just been
renamed to file_two in SOME_SHA1).
So far so good. Now here's what --porcelain looks like:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
filename file_two
...some different content....
We've dropped the author and committer fields from the
second line, as they would just be repeats. But we can't
omit "filename", because it depends on the actual block of
blamed lines, not just the commit. This is handled by
emit_porcelain_details(), which will show the filename
either if it is the first mention of the commit _or_ if the
commit has multiple paths in it.
But we don't give "previous" the same handling. It's written
inside emit_one_suspect_detail(), which bails early if we've
already seen that commit. And so the output above is wrong;
a reader would assume that the correct place to re-blame
line two is from file_one, but that's obviously nonsense.
Let's treat "previous" the same as "filename", and show it
fresh whenever we know we are in a confusing case like this.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:20:51 +01:00
|
|
|
if (suspect->previous) {
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *prev = suspect->previous;
|
blame: output porcelain "previous" header for each file
It's possible for content currently found in one file to
have originated in two separate files, each of which may
have been modified in some single older commit. The
--porcelain output generates an incorrect "previous" header
in this case, whereas --line-porcelain gets it right. The
problem is that the porcelain output tries to omit repeated
details of commits, and treats "previous" as a property of
the commit, when it is really a property of the blamed block
of lines.
Let's look at an example. In a case like this, you might see
this output from --line-porcelain:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
author ...
committer ...
previous SOME_SHA1^ file_two
filename file_two
...some different content....
The "filename" fields tell us that the two lines are from
two different files. But notice that the filename also
appears in the "previous" field, which tells us where to
start a re-blame. The second content line never appeared in
file_one at all, so we would obviously need to re-blame from
file_two (or possibly even some other file, if had just been
renamed to file_two in SOME_SHA1).
So far so good. Now here's what --porcelain looks like:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
filename file_two
...some different content....
We've dropped the author and committer fields from the
second line, as they would just be repeats. But we can't
omit "filename", because it depends on the actual block of
blamed lines, not just the commit. This is handled by
emit_porcelain_details(), which will show the filename
either if it is the first mention of the commit _or_ if the
commit has multiple paths in it.
But we don't give "previous" the same handling. It's written
inside emit_one_suspect_detail(), which bails early if we've
already seen that commit. And so the output above is wrong;
a reader would assume that the correct place to re-blame
line two is from file_one, but that's obviously nonsense.
Let's treat "previous" the same as "filename", and show it
fresh whenever we know we are in a confusing case like this.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:20:51 +01:00
|
|
|
printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
|
|
|
|
write_name_quoted(prev->path, stdout, '\n');
|
|
|
|
}
|
2007-01-28 10:42:31 +01:00
|
|
|
printf("filename ");
|
blame: output porcelain "previous" header for each file
It's possible for content currently found in one file to
have originated in two separate files, each of which may
have been modified in some single older commit. The
--porcelain output generates an incorrect "previous" header
in this case, whereas --line-porcelain gets it right. The
problem is that the porcelain output tries to omit repeated
details of commits, and treats "previous" as a property of
the commit, when it is really a property of the blamed block
of lines.
Let's look at an example. In a case like this, you might see
this output from --line-porcelain:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
author ...
committer ...
previous SOME_SHA1^ file_two
filename file_two
...some different content....
The "filename" fields tell us that the two lines are from
two different files. But notice that the filename also
appears in the "previous" field, which tells us where to
start a re-blame. The second content line never appeared in
file_one at all, so we would obviously need to re-blame from
file_two (or possibly even some other file, if had just been
renamed to file_two in SOME_SHA1).
So far so good. Now here's what --porcelain looks like:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
filename file_two
...some different content....
We've dropped the author and committer fields from the
second line, as they would just be repeats. But we can't
omit "filename", because it depends on the actual block of
blamed lines, not just the commit. This is handled by
emit_porcelain_details(), which will show the filename
either if it is the first mention of the commit _or_ if the
commit has multiple paths in it.
But we don't give "previous" the same handling. It's written
inside emit_one_suspect_detail(), which bails early if we've
already seen that commit. And so the output above is wrong;
a reader would assume that the correct place to re-blame
line two is from file_one, but that's obviously nonsense.
Let's treat "previous" the same as "filename", and show it
fresh whenever we know we are in a confusing case like this.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:20:51 +01:00
|
|
|
write_name_quoted(suspect->path, stdout, '\n');
|
2007-01-28 10:42:31 +01:00
|
|
|
}
|
|
|
|
|
2008-06-05 07:48:42 +02: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 15:34:02 +02:00
|
|
|
* the first time each commit appears in the output (unless the
|
|
|
|
* user has specifically asked for us to repeat).
|
2008-06-05 07:48:42 +02:00
|
|
|
*/
|
2017-05-24 07:15:12 +02:00
|
|
|
static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat)
|
2008-06-05 07:48:42 +02:00
|
|
|
{
|
|
|
|
struct commit_info ci;
|
|
|
|
|
2011-05-09 15:34:02 +02:00
|
|
|
if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
|
2008-06-05 07:48:42 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
suspect->commit->object.flags |= METAINFO_SHOWN;
|
|
|
|
get_commit_info(suspect->commit, &ci, 1);
|
2013-01-05 22:26:40 +01:00
|
|
|
printf("author %s\n", ci.author.buf);
|
|
|
|
printf("author-mail %s\n", ci.author_mail.buf);
|
2017-04-21 12:45:48 +02:00
|
|
|
printf("author-time %"PRItime"\n", ci.author_time);
|
2013-01-05 22:26:40 +01:00
|
|
|
printf("author-tz %s\n", ci.author_tz.buf);
|
|
|
|
printf("committer %s\n", ci.committer.buf);
|
|
|
|
printf("committer-mail %s\n", ci.committer_mail.buf);
|
2017-04-21 12:45:48 +02:00
|
|
|
printf("committer-time %"PRItime"\n", ci.committer_time);
|
2013-01-05 22:26:40 +01:00
|
|
|
printf("committer-tz %s\n", ci.committer_tz.buf);
|
|
|
|
printf("summary %s\n", ci.summary.buf);
|
2008-06-05 07:48:42 +02:00
|
|
|
if (suspect->commit->object.flags & UNINTERESTING)
|
|
|
|
printf("boundary\n");
|
2013-01-05 22:26:40 +01:00
|
|
|
|
|
|
|
commit_info_destroy(&ci);
|
|
|
|
|
2008-06-05 07:48:42 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
2014-04-26 01:56:49 +02:00
|
|
|
* The blame_entry is found to be guilty for the range.
|
|
|
|
* Show it in incremental output.
|
2007-01-30 02:36:22 +01:00
|
|
|
*/
|
2017-05-24 07:15:26 +02:00
|
|
|
static void found_guilty_entry(struct blame_entry *ent, void *data)
|
2007-01-28 10:34:06 +01:00
|
|
|
{
|
2017-05-24 07:15:26 +02:00
|
|
|
struct progress_info *pi = (struct progress_info *)data;
|
|
|
|
|
2007-01-28 10:34:06 +01:00
|
|
|
if (incremental) {
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *suspect = ent->suspect;
|
2007-01-28 10:34:06 +01:00
|
|
|
|
|
|
|
printf("%s %d %d %d\n",
|
2015-11-10 03:22:28 +01:00
|
|
|
oid_to_hex(&suspect->commit->object.oid),
|
2007-01-28 10:34:06 +01:00
|
|
|
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
|
2011-05-09 15:34:02 +02:00
|
|
|
emit_one_suspect_detail(suspect, 0);
|
blame: output porcelain "previous" header for each file
It's possible for content currently found in one file to
have originated in two separate files, each of which may
have been modified in some single older commit. The
--porcelain output generates an incorrect "previous" header
in this case, whereas --line-porcelain gets it right. The
problem is that the porcelain output tries to omit repeated
details of commits, and treats "previous" as a property of
the commit, when it is really a property of the blamed block
of lines.
Let's look at an example. In a case like this, you might see
this output from --line-porcelain:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
author ...
committer ...
previous SOME_SHA1^ file_two
filename file_two
...some different content....
The "filename" fields tell us that the two lines are from
two different files. But notice that the filename also
appears in the "previous" field, which tells us where to
start a re-blame. The second content line never appeared in
file_one at all, so we would obviously need to re-blame from
file_two (or possibly even some other file, if had just been
renamed to file_two in SOME_SHA1).
So far so good. Now here's what --porcelain looks like:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
filename file_two
...some different content....
We've dropped the author and committer fields from the
second line, as they would just be repeats. But we can't
omit "filename", because it depends on the actual block of
blamed lines, not just the commit. This is handled by
emit_porcelain_details(), which will show the filename
either if it is the first mention of the commit _or_ if the
commit has multiple paths in it.
But we don't give "previous" the same handling. It's written
inside emit_one_suspect_detail(), which bails early if we've
already seen that commit. And so the output above is wrong;
a reader would assume that the correct place to re-blame
line two is from file_one, but that's obviously nonsense.
Let's treat "previous" the same as "filename", and show it
fresh whenever we know we are in a confusing case like this.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:20:51 +01:00
|
|
|
write_filename_info(suspect);
|
2007-06-29 19:40:46 +02:00
|
|
|
maybe_flush_or_die(stdout, "stdout");
|
2007-01-28 10:34:06 +01:00
|
|
|
}
|
2015-12-13 01:51:03 +01:00
|
|
|
pi->blamed_lines += ent->num_lines;
|
|
|
|
display_progress(pi->progress, pi->blamed_lines);
|
2007-01-28 10:34:06 +01:00
|
|
|
}
|
|
|
|
|
2017-04-26 21:29:31 +02:00
|
|
|
static const char *format_time(timestamp_t time, const char *tz_str,
|
2007-01-28 10:34:06 +01:00
|
|
|
int show_raw_time)
|
|
|
|
{
|
blame: fix broken time_buf paddings in relative timestamp
Command `git blame --date relative` aligns the date field with a
fixed-width (defined by blame_date_width), and if time_str is shorter
than that, it adds spaces for padding. But there are two bugs in the
following codes:
time_len = strlen(time_str);
...
memset(time_buf + time_len, ' ', blame_date_width - time_len);
1. The type of blame_date_width is size_t, which is unsigned. If
time_len is greater than blame_date_width, the result of
"blame_date_width - time_len" will never be a negative number, but a
really big positive number, and will cause memory overwrite.
This bug can be triggered if either l10n message for function
show_date_relative() in date.c is longer than 30 characters, then
`git blame --date relative` may exit abnormally.
2. When show blame information with relative time, the UTF-8 characters
in time_str will break the alignment of columns after the date field.
This is because the time_buf padding with spaces should have a
constant display width, not a fixed strlen size. So we should call
utf8_strwidth() instead of strlen() for width calibration.
Helped-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-04-21 08:02:03 +02:00
|
|
|
static struct strbuf time_buf = STRBUF_INIT;
|
2007-01-28 10:34:06 +01:00
|
|
|
|
blame: fix broken time_buf paddings in relative timestamp
Command `git blame --date relative` aligns the date field with a
fixed-width (defined by blame_date_width), and if time_str is shorter
than that, it adds spaces for padding. But there are two bugs in the
following codes:
time_len = strlen(time_str);
...
memset(time_buf + time_len, ' ', blame_date_width - time_len);
1. The type of blame_date_width is size_t, which is unsigned. If
time_len is greater than blame_date_width, the result of
"blame_date_width - time_len" will never be a negative number, but a
really big positive number, and will cause memory overwrite.
This bug can be triggered if either l10n message for function
show_date_relative() in date.c is longer than 30 characters, then
`git blame --date relative` may exit abnormally.
2. When show blame information with relative time, the UTF-8 characters
in time_str will break the alignment of columns after the date field.
This is because the time_buf padding with spaces should have a
constant display width, not a fixed strlen size. So we should call
utf8_strwidth() instead of strlen() for width calibration.
Helped-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-04-21 08:02:03 +02:00
|
|
|
strbuf_reset(&time_buf);
|
2007-01-28 10:34:06 +01:00
|
|
|
if (show_raw_time) {
|
2017-04-21 12:45:48 +02:00
|
|
|
strbuf_addf(&time_buf, "%"PRItime" %s", time, tz_str);
|
2007-01-28 10:34:06 +01:00
|
|
|
}
|
2009-02-20 23:51:11 +01:00
|
|
|
else {
|
2014-01-29 14:33:15 +01:00
|
|
|
const char *time_str;
|
blame: fix broken time_buf paddings in relative timestamp
Command `git blame --date relative` aligns the date field with a
fixed-width (defined by blame_date_width), and if time_str is shorter
than that, it adds spaces for padding. But there are two bugs in the
following codes:
time_len = strlen(time_str);
...
memset(time_buf + time_len, ' ', blame_date_width - time_len);
1. The type of blame_date_width is size_t, which is unsigned. If
time_len is greater than blame_date_width, the result of
"blame_date_width - time_len" will never be a negative number, but a
really big positive number, and will cause memory overwrite.
This bug can be triggered if either l10n message for function
show_date_relative() in date.c is longer than 30 characters, then
`git blame --date relative` may exit abnormally.
2. When show blame information with relative time, the UTF-8 characters
in time_str will break the alignment of columns after the date field.
This is because the time_buf padding with spaces should have a
constant display width, not a fixed strlen size. So we should call
utf8_strwidth() instead of strlen() for width calibration.
Helped-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-04-21 08:02:03 +02:00
|
|
|
size_t time_width;
|
2014-01-29 14:33:15 +01:00
|
|
|
int tz;
|
2009-02-20 23:51:11 +01:00
|
|
|
tz = atoi(tz_str);
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 18:55:02 +02:00
|
|
|
time_str = show_date(time, tz, &blame_date_mode);
|
blame: fix broken time_buf paddings in relative timestamp
Command `git blame --date relative` aligns the date field with a
fixed-width (defined by blame_date_width), and if time_str is shorter
than that, it adds spaces for padding. But there are two bugs in the
following codes:
time_len = strlen(time_str);
...
memset(time_buf + time_len, ' ', blame_date_width - time_len);
1. The type of blame_date_width is size_t, which is unsigned. If
time_len is greater than blame_date_width, the result of
"blame_date_width - time_len" will never be a negative number, but a
really big positive number, and will cause memory overwrite.
This bug can be triggered if either l10n message for function
show_date_relative() in date.c is longer than 30 characters, then
`git blame --date relative` may exit abnormally.
2. When show blame information with relative time, the UTF-8 characters
in time_str will break the alignment of columns after the date field.
This is because the time_buf padding with spaces should have a
constant display width, not a fixed strlen size. So we should call
utf8_strwidth() instead of strlen() for width calibration.
Helped-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-04-21 08:02:03 +02:00
|
|
|
strbuf_addstr(&time_buf, time_str);
|
|
|
|
/*
|
|
|
|
* Add space paddings to time_buf to display a fixed width
|
|
|
|
* string, and use time_width for display width calibration.
|
|
|
|
*/
|
|
|
|
for (time_width = utf8_strwidth(time_str);
|
|
|
|
time_width < blame_date_width;
|
|
|
|
time_width++)
|
|
|
|
strbuf_addch(&time_buf, ' ');
|
2009-02-20 23:51:11 +01:00
|
|
|
}
|
blame: fix broken time_buf paddings in relative timestamp
Command `git blame --date relative` aligns the date field with a
fixed-width (defined by blame_date_width), and if time_str is shorter
than that, it adds spaces for padding. But there are two bugs in the
following codes:
time_len = strlen(time_str);
...
memset(time_buf + time_len, ' ', blame_date_width - time_len);
1. The type of blame_date_width is size_t, which is unsigned. If
time_len is greater than blame_date_width, the result of
"blame_date_width - time_len" will never be a negative number, but a
really big positive number, and will cause memory overwrite.
This bug can be triggered if either l10n message for function
show_date_relative() in date.c is longer than 30 characters, then
`git blame --date relative` may exit abnormally.
2. When show blame information with relative time, the UTF-8 characters
in time_str will break the alignment of columns after the date field.
This is because the time_buf padding with spaces should have a
constant display width, not a fixed strlen size. So we should call
utf8_strwidth() instead of strlen() for width calibration.
Helped-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-04-21 08:02:03 +02:00
|
|
|
return time_buf.buf;
|
2007-01-28 10:34:06 +01:00
|
|
|
}
|
|
|
|
|
2006-10-20 01:00:04 +02: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 23:51:12 +02:00
|
|
|
#define OUTPUT_SHOW_SCORE 0100
|
2007-04-13 00:50:45 +02:00
|
|
|
#define OUTPUT_NO_AUTHOR 0200
|
2010-10-16 08:57:51 +02:00
|
|
|
#define OUTPUT_SHOW_EMAIL 0400
|
2011-05-09 15:34:42 +02:00
|
|
|
#define OUTPUT_LINE_PORCELAIN 01000
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2017-05-24 07:15:12 +02:00
|
|
|
static void emit_porcelain_details(struct blame_origin *suspect, int repeat)
|
2011-05-09 15:34:02 +02:00
|
|
|
{
|
|
|
|
if (emit_one_suspect_detail(suspect, repeat) ||
|
|
|
|
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
|
blame: output porcelain "previous" header for each file
It's possible for content currently found in one file to
have originated in two separate files, each of which may
have been modified in some single older commit. The
--porcelain output generates an incorrect "previous" header
in this case, whereas --line-porcelain gets it right. The
problem is that the porcelain output tries to omit repeated
details of commits, and treats "previous" as a property of
the commit, when it is really a property of the blamed block
of lines.
Let's look at an example. In a case like this, you might see
this output from --line-porcelain:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
author ...
committer ...
previous SOME_SHA1^ file_two
filename file_two
...some different content....
The "filename" fields tell us that the two lines are from
two different files. But notice that the filename also
appears in the "previous" field, which tells us where to
start a re-blame. The second content line never appeared in
file_one at all, so we would obviously need to re-blame from
file_two (or possibly even some other file, if had just been
renamed to file_two in SOME_SHA1).
So far so good. Now here's what --porcelain looks like:
SOME_SHA1 1 1 1
author ...
committer ...
previous SOME_SHA1^ file_one
filename file_one
...some line content...
SOME_SHA1 2 1 1
filename file_two
...some different content....
We've dropped the author and committer fields from the
second line, as they would just be repeats. But we can't
omit "filename", because it depends on the actual block of
blamed lines, not just the commit. This is handled by
emit_porcelain_details(), which will show the filename
either if it is the first mention of the commit _or_ if the
commit has multiple paths in it.
But we don't give "previous" the same handling. It's written
inside emit_one_suspect_detail(), which bails early if we've
already seen that commit. And so the output above is wrong;
a reader would assume that the correct place to re-blame
line two is from file_one, but that's obviously nonsense.
Let's treat "previous" the same as "filename", and show it
fresh whenever we know we are in a confusing case like this.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:20:51 +01:00
|
|
|
write_filename_info(suspect);
|
2011-05-09 15:34:02 +02:00
|
|
|
}
|
|
|
|
|
2017-05-24 07:15:13 +02:00
|
|
|
static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
|
2011-05-09 15:34:02 +02:00
|
|
|
int opt)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
2011-05-09 15:34:42 +02:00
|
|
|
int repeat = opt & OUTPUT_LINE_PORCELAIN;
|
2006-10-20 01:00:04 +02:00
|
|
|
int cnt;
|
|
|
|
const char *cp;
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *suspect = ent->suspect;
|
2017-03-26 18:01:24 +02:00
|
|
|
char hex[GIT_MAX_HEXSZ + 1];
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2017-01-28 23:03:03 +01:00
|
|
|
oid_to_hex_r(hex, &suspect->commit->object.oid);
|
2014-04-26 01:56:49 +02:00
|
|
|
printf("%s %d %d %d\n",
|
2006-10-20 01:00:04 +02:00
|
|
|
hex,
|
|
|
|
ent->s_lno + 1,
|
|
|
|
ent->lno + 1,
|
|
|
|
ent->num_lines);
|
2011-05-09 15:34:42 +02:00
|
|
|
emit_porcelain_details(suspect, repeat);
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2017-05-24 07:15:17 +02:00
|
|
|
cp = blame_nth_line(sb, ent->lno);
|
2006-10-20 01:00:04 +02:00
|
|
|
for (cnt = 0; cnt < ent->num_lines; cnt++) {
|
|
|
|
char ch;
|
2011-05-09 15:34:42 +02:00
|
|
|
if (cnt) {
|
2006-10-20 01:00:04 +02:00
|
|
|
printf("%s %d %d\n", hex,
|
|
|
|
ent->s_lno + 1 + cnt,
|
|
|
|
ent->lno + 1 + cnt);
|
2011-05-09 15:34:42 +02:00
|
|
|
if (repeat)
|
|
|
|
emit_porcelain_details(suspect, 1);
|
|
|
|
}
|
2006-10-20 01:00:04 +02:00
|
|
|
putchar('\t');
|
|
|
|
do {
|
|
|
|
ch = *cp++;
|
|
|
|
putchar(ch);
|
|
|
|
} while (ch != '\n' &&
|
|
|
|
cp < sb->final_buf + sb->final_buf_size);
|
|
|
|
}
|
2009-10-20 05:06:28 +02:00
|
|
|
|
|
|
|
if (sb->final_buf_size && cp[-1] != '\n')
|
|
|
|
putchar('\n');
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
2017-05-24 07:15:13 +02:00
|
|
|
static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
|
|
|
int cnt;
|
|
|
|
const char *cp;
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *suspect = ent->suspect;
|
2006-10-20 01:00:04 +02:00
|
|
|
struct commit_info ci;
|
2017-03-26 18:01:24 +02:00
|
|
|
char hex[GIT_MAX_HEXSZ + 1];
|
2006-10-20 01:00:04 +02:00
|
|
|
int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
|
|
|
|
|
|
|
|
get_commit_info(suspect->commit, &ci, 1);
|
2017-01-28 23:03:03 +01:00
|
|
|
oid_to_hex_r(hex, &suspect->commit->object.oid);
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2017-05-24 07:15:17 +02:00
|
|
|
cp = blame_nth_line(sb, ent->lno);
|
2006-10-20 01:00:04 +02:00
|
|
|
for (cnt = 0; cnt < ent->num_lines; cnt++) {
|
|
|
|
char ch;
|
2016-09-05 22:08:03 +02:00
|
|
|
int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
|
2006-12-02 05:45:45 +01:00
|
|
|
|
|
|
|
if (suspect->commit->object.flags & UNINTERESTING) {
|
2007-02-06 10:52:04 +01:00
|
|
|
if (blank_boundary)
|
|
|
|
memset(hex, ' ', length);
|
2008-09-05 09:57:35 +02:00
|
|
|
else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
|
2006-12-18 23:04:38 +01:00
|
|
|
length--;
|
|
|
|
putchar('^');
|
|
|
|
}
|
2006-12-02 05:45:45 +01:00
|
|
|
}
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2006-12-02 05:45:45 +01:00
|
|
|
printf("%.*s", length, hex);
|
2010-10-16 08:57:51 +02:00
|
|
|
if (opt & OUTPUT_ANNOTATE_COMPAT) {
|
|
|
|
const char *name;
|
|
|
|
if (opt & OUTPUT_SHOW_EMAIL)
|
2013-01-05 22:26:40 +01:00
|
|
|
name = ci.author_mail.buf;
|
2010-10-16 08:57:51 +02:00
|
|
|
else
|
2013-01-05 22:26:40 +01:00
|
|
|
name = ci.author.buf;
|
2010-10-16 08:57:51 +02:00
|
|
|
printf("\t(%10s\t%10s\t%d)", name,
|
2013-01-05 22:26:40 +01:00
|
|
|
format_time(ci.author_time, ci.author_tz.buf,
|
2006-10-20 01:00:04 +02:00
|
|
|
show_raw_time),
|
|
|
|
ent->lno + 1 + cnt);
|
2010-10-16 08:57:51 +02:00
|
|
|
} else {
|
2006-10-20 23:51:12 +02:00
|
|
|
if (opt & OUTPUT_SHOW_SCORE)
|
2006-10-29 12:07:40 +01:00
|
|
|
printf(" %*d %02d",
|
|
|
|
max_score_digits, ent->score,
|
|
|
|
ent->suspect->refcnt);
|
2006-10-20 01:00:04 +02: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-13 00:50:45 +02:00
|
|
|
|
2009-01-30 10:41:29 +01:00
|
|
|
if (!(opt & OUTPUT_NO_AUTHOR)) {
|
2010-10-16 08:57:51 +02:00
|
|
|
const char *name;
|
|
|
|
int pad;
|
|
|
|
if (opt & OUTPUT_SHOW_EMAIL)
|
2013-01-05 22:26:40 +01:00
|
|
|
name = ci.author_mail.buf;
|
2010-10-16 08:57:51 +02:00
|
|
|
else
|
2013-01-05 22:26:40 +01:00
|
|
|
name = ci.author.buf;
|
2010-10-16 08:57:51 +02:00
|
|
|
pad = longest_author - utf8_strwidth(name);
|
2009-01-30 10:41:29 +01:00
|
|
|
printf(" (%s%*s %10s",
|
2010-10-16 08:57:51 +02:00
|
|
|
name, pad, "",
|
2007-04-13 00:50:45 +02:00
|
|
|
format_time(ci.author_time,
|
2013-01-05 22:26:40 +01:00
|
|
|
ci.author_tz.buf,
|
2007-04-13 00:50:45 +02:00
|
|
|
show_raw_time));
|
2009-01-30 10:41:29 +01:00
|
|
|
}
|
2007-04-13 00:50:45 +02:00
|
|
|
printf(" %*d) ",
|
2006-10-20 01:00:04 +02: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 05:06:28 +02:00
|
|
|
|
|
|
|
if (sb->final_buf_size && cp[-1] != '\n')
|
|
|
|
putchar('\n');
|
2013-01-05 22:26:40 +01:00
|
|
|
|
|
|
|
commit_info_destroy(&ci);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
2017-05-24 07:15:13 +02:00
|
|
|
static void output(struct blame_scoreboard *sb, int option)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
|
|
|
struct blame_entry *ent;
|
|
|
|
|
|
|
|
if (option & OUTPUT_PORCELAIN) {
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
2014-04-26 01:56:49 +02:00
|
|
|
int count = 0;
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *suspect;
|
2014-04-26 01:56:49 +02:00
|
|
|
struct commit *commit = ent->suspect->commit;
|
2006-10-20 01:00:04 +02:00
|
|
|
if (commit->object.flags & MORE_THAN_ONE_PATH)
|
|
|
|
continue;
|
2014-04-26 01:56:49 +02:00
|
|
|
for (suspect = commit->util; suspect; suspect = suspect->next) {
|
|
|
|
if (suspect->guilty && count++) {
|
|
|
|
commit->object.flags |= MORE_THAN_ONE_PATH;
|
|
|
|
break;
|
|
|
|
}
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ent = sb->ent; ent; ent = ent->next) {
|
|
|
|
if (option & OUTPUT_PORCELAIN)
|
2011-05-09 15:34:02 +02:00
|
|
|
emit_porcelain(sb, ent, option);
|
2006-10-20 23:51:12 +02:00
|
|
|
else {
|
2006-10-20 01:00:04 +02:00
|
|
|
emit_other(sb, ent, option);
|
2006-10-20 23:51:12 +02:00
|
|
|
}
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
|
|
|
* Add phony grafts for use with -S; this is primarily to
|
2008-08-30 13:12:53 +02:00
|
|
|
* support git's cvsserver that wants to give a linear history
|
2007-01-30 02:36:22 +01:00
|
|
|
* to its clients.
|
|
|
|
*/
|
2006-10-20 01:00:04 +02:00
|
|
|
static int read_ancestry(const char *graft_file)
|
|
|
|
{
|
2017-05-03 12:16:50 +02:00
|
|
|
FILE *fp = fopen_or_warn(graft_file, "r");
|
2013-12-27 21:49:57 +01:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2006-10-20 01:00:04 +02:00
|
|
|
if (!fp)
|
|
|
|
return -1;
|
2013-12-27 21:49:57 +01:00
|
|
|
while (!strbuf_getwholeline(&buf, fp, '\n')) {
|
2006-10-20 01:00:04 +02:00
|
|
|
/* The format is just "Commit Parent1 Parent2 ...\n" */
|
2013-12-27 21:49:57 +01:00
|
|
|
struct commit_graft *graft = read_graft_line(buf.buf, buf.len);
|
2006-11-10 22:39:01 +01:00
|
|
|
if (graft)
|
|
|
|
register_commit_graft(graft, 0);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
fclose(fp);
|
2013-12-27 21:49:57 +01:00
|
|
|
strbuf_release(&buf);
|
2006-10-20 01:00:04 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-24 07:15:12 +02:00
|
|
|
static int update_auto_abbrev(int auto_abbrev, struct blame_origin *suspect)
|
2012-07-02 09:54:00 +02:00
|
|
|
{
|
2015-11-10 03:22:29 +01:00
|
|
|
const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash,
|
2012-07-02 09:54:00 +02:00
|
|
|
auto_abbrev);
|
|
|
|
int len = strlen(uniq);
|
|
|
|
if (auto_abbrev < len)
|
|
|
|
return len;
|
|
|
|
return auto_abbrev;
|
|
|
|
}
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
|
|
|
* How many columns do we need to show line numbers, authors,
|
|
|
|
* and filenames?
|
|
|
|
*/
|
2017-05-24 07:15:13 +02:00
|
|
|
static void find_alignment(struct blame_scoreboard *sb, int *option)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
|
|
|
int longest_src_lines = 0;
|
|
|
|
int longest_dst_lines = 0;
|
2006-10-20 23:51:12 +02:00
|
|
|
unsigned largest_score = 0;
|
2006-10-20 01:00:04 +02:00
|
|
|
struct blame_entry *e;
|
2012-07-02 09:54:00 +02:00
|
|
|
int compute_auto_abbrev = (abbrev < 0);
|
2016-09-28 23:56:00 +02:00
|
|
|
int auto_abbrev = DEFAULT_ABBREV;
|
2006-10-20 01:00:04 +02:00
|
|
|
|
|
|
|
for (e = sb->ent; e; e = e->next) {
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *suspect = e->suspect;
|
2006-10-20 01:00:04 +02:00
|
|
|
int num;
|
|
|
|
|
2012-07-02 09:54:00 +02:00
|
|
|
if (compute_auto_abbrev)
|
|
|
|
auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
|
2006-11-29 07:29:18 +01:00
|
|
|
if (strcmp(suspect->path, sb->path))
|
|
|
|
*option |= OUTPUT_SHOW_NAME;
|
|
|
|
num = strlen(suspect->path);
|
|
|
|
if (longest_file < num)
|
|
|
|
longest_file = num;
|
2006-10-20 01:00:04 +02:00
|
|
|
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
|
2015-02-09 22:28:07 +01:00
|
|
|
struct commit_info ci;
|
2006-10-20 01:00:04 +02:00
|
|
|
suspect->commit->object.flags |= METAINFO_SHOWN;
|
|
|
|
get_commit_info(suspect->commit, &ci, 1);
|
2010-10-16 08:57:51 +02:00
|
|
|
if (*option & OUTPUT_SHOW_EMAIL)
|
2013-01-05 22:26:40 +01:00
|
|
|
num = utf8_strwidth(ci.author_mail.buf);
|
2010-10-16 08:57:51 +02:00
|
|
|
else
|
2013-01-05 22:26:40 +01:00
|
|
|
num = utf8_strwidth(ci.author.buf);
|
2006-10-20 01:00:04 +02:00
|
|
|
if (longest_author < num)
|
|
|
|
longest_author = num;
|
2015-02-09 22:28:07 +01:00
|
|
|
commit_info_destroy(&ci);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
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;
|
2017-05-24 07:15:16 +02:00
|
|
|
if (largest_score < blame_entry_score(sb, e))
|
|
|
|
largest_score = blame_entry_score(sb, e);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
2012-02-12 15:16:20 +01: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 09:54:00 +02:00
|
|
|
|
|
|
|
if (compute_auto_abbrev)
|
|
|
|
/* one more abbrev length is needed for the boundary commit */
|
|
|
|
abbrev = auto_abbrev + 1;
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
2017-05-24 07:15:25 +02:00
|
|
|
static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa)
|
2006-10-29 12:07:40 +01:00
|
|
|
{
|
2017-05-24 07:15:25 +02:00
|
|
|
int opt = OUTPUT_SHOW_SCORE | OUTPUT_SHOW_NUMBER | OUTPUT_SHOW_NAME;
|
|
|
|
find_alignment(sb, &opt);
|
|
|
|
output(sb, opt);
|
|
|
|
die("Baa %d!", baa);
|
2006-10-29 12:07:40 +01:00
|
|
|
}
|
|
|
|
|
2006-10-21 00:37:12 +02: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 08:22:49 +01:00
|
|
|
static const char *add_prefix(const char *prefix, const char *path)
|
|
|
|
{
|
2008-02-01 05:07:04 +01:00
|
|
|
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
|
2006-11-02 08:22:49 +01:00
|
|
|
}
|
|
|
|
|
2008-05-14 19:46:53 +02:00
|
|
|
static int git_blame_config(const char *var, const char *value, void *cb)
|
2006-12-18 23:04:38 +01: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;
|
|
|
|
}
|
2015-05-31 21:27:37 +02:00
|
|
|
if (!strcmp(var, "blame.showemail")) {
|
|
|
|
int *output_option = cb;
|
|
|
|
if (git_config_bool(var, value))
|
|
|
|
*output_option |= OUTPUT_SHOW_EMAIL;
|
|
|
|
else
|
|
|
|
*output_option &= ~OUTPUT_SHOW_EMAIL;
|
|
|
|
return 0;
|
|
|
|
}
|
2009-02-20 23:51:11 +01:00
|
|
|
if (!strcmp(var, "blame.date")) {
|
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(var);
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 18:55:02 +02:00
|
|
|
parse_date_format(value, &blame_date_mode);
|
2009-02-20 23:51:11 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2010-06-15 15:58:48 +02:00
|
|
|
|
2016-09-05 11:44:53 +02:00
|
|
|
if (git_diff_heuristic_config(var, value, cb) < 0)
|
|
|
|
return -1;
|
drop odd return value semantics from userdiff_config
When the userdiff_config function was introduced in be58e70
(diff: unify external diff and funcname parsing code,
2008-10-05), it used a return value convention unlike any
other config callback. Like other callbacks, it used "-1" to
signal error. But it returned "1" to indicate that it found
something, and "0" otherwise; other callbacks simply
returned "0" to indicate that no error occurred.
This distinction was necessary at the time, because the
userdiff namespace overlapped slightly with the color
configuration namespace. So "diff.color.foo" could mean "the
'foo' slot of diff coloring" or "the 'foo' component of the
"color" userdiff driver". Because the color-parsing code
would die on an unknown color slot, we needed the userdiff
code to indicate that it had matched the variable, letting
us bypass the color-parsing code entirely.
Later, in 8b8e862 (ignore unknown color configuration,
2009-12-12), the color-parsing code learned to silently
ignore unknown slots. This means we no longer need to
protect userdiff-matched variables from reaching the
color-parsing code.
We can therefore change the userdiff_config calling
convention to a more normal one. This drops some code from
each caller, which is nice. But more importantly, it reduces
the cognitive load for readers who may wonder why
userdiff_config is unlike every other config callback.
There's no need to add a new test confirming that this
works; t4020 already contains a test that sets
diff.color.external.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-07 19:23:02 +01:00
|
|
|
if (userdiff_config(var, value) < 0)
|
2010-06-15 15:58:48 +02:00
|
|
|
return -1;
|
|
|
|
|
2008-05-14 19:46:53 +02:00
|
|
|
return git_default_config(var, value, cb);
|
2006-12-18 23:04:38 +01:00
|
|
|
}
|
|
|
|
|
2008-07-08 15:19:34 +02: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;
|
|
|
|
}
|
|
|
|
|
2006-11-09 03:47:54 +01:00
|
|
|
int cmd_blame(int argc, const char **argv, const char *prefix)
|
2006-10-20 01:00:04 +02:00
|
|
|
{
|
|
|
|
struct rev_info revs;
|
|
|
|
const char *path;
|
2017-05-24 07:15:13 +02:00
|
|
|
struct blame_scoreboard sb;
|
2017-05-24 07:15:12 +02:00
|
|
|
struct blame_origin *o;
|
2013-08-06 15:59:38 +02:00
|
|
|
struct blame_entry *ent = NULL;
|
|
|
|
long dashdash_pos, lno;
|
2017-05-24 07:15:26 +02:00
|
|
|
struct progress_info pi = { NULL, 0 };
|
2008-07-08 15:19:34 +02:00
|
|
|
|
2016-06-13 07:39:28 +02:00
|
|
|
struct string_list range_list = STRING_LIST_INIT_NODUP;
|
|
|
|
int output_option = 0, opt = 0;
|
|
|
|
int show_stats = 0;
|
|
|
|
const char *revs_file = NULL;
|
|
|
|
const char *contents_from = NULL;
|
|
|
|
const struct option options[] = {
|
2013-08-03 13:51:19 +02:00
|
|
|
OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
|
|
|
|
OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
|
|
|
|
OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
|
|
|
|
OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
|
2015-12-13 01:51:03 +01:00
|
|
|
OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")),
|
2012-08-20 14:31:54 +02:00
|
|
|
OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
|
|
|
|
OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
|
|
|
|
OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
|
|
|
|
OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
|
|
|
|
OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
|
|
|
|
OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
|
|
|
|
OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
|
|
|
|
OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
|
|
|
|
OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
|
|
|
|
OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
|
|
|
|
OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
|
2016-09-05 11:44:53 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The following two options are parsed by parse_revision_opt()
|
|
|
|
* and are only included here to get included in the "-h"
|
|
|
|
* output:
|
|
|
|
*/
|
2016-12-23 21:32:22 +01:00
|
|
|
{ OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb },
|
2016-09-05 11:44:53 +02:00
|
|
|
|
2012-08-20 14:31:54 +02:00
|
|
|
OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
|
|
|
|
OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
|
|
|
|
OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
|
|
|
|
{ OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
|
|
|
|
{ OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
|
2013-08-06 15:59:38 +02:00
|
|
|
OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
|
2011-04-06 04:20:50 +02:00
|
|
|
OPT__ABBREV(&abbrev),
|
2008-07-08 15:19:34 +02:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
|
|
|
struct parse_opt_ctx_t ctx;
|
2008-09-05 09:57:35 +02:00
|
|
|
int cmd_is_annotate = !strcmp(argv[0], "annotate");
|
2013-08-06 15:59:38 +02:00
|
|
|
struct range_set ranges;
|
|
|
|
unsigned int range_i;
|
2013-08-06 15:59:42 +02:00
|
|
|
long anchor;
|
2007-02-06 10:52:04 +01:00
|
|
|
|
2015-05-31 21:27:37 +02:00
|
|
|
git_config(git_blame_config, &output_option);
|
2008-07-08 15:19:34 +02:00
|
|
|
init_revisions(&revs, NULL);
|
2009-02-20 23:51:11 +01:00
|
|
|
revs.date_mode = blame_date_mode;
|
2010-06-15 15:58:48 +02:00
|
|
|
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
|
2012-09-21 22:52:25 +02:00
|
|
|
DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES);
|
2009-02-20 23:51:11 +01:00
|
|
|
|
2006-10-21 08:49:31 +02:00
|
|
|
save_commit_buffer = 0;
|
2008-07-08 15:19:35 +02:00
|
|
|
dashdash_pos = 0;
|
2015-12-13 01:51:03 +01:00
|
|
|
show_progress = -1;
|
2006-10-21 08:49:31 +02:00
|
|
|
|
2010-12-06 08:57:42 +01:00
|
|
|
parse_options_start(&ctx, argc, argv, prefix, options,
|
|
|
|
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
|
2008-07-08 15:19:34 +02:00
|
|
|
for (;;) {
|
|
|
|
switch (parse_options_step(&ctx, options, blame_opt_usage)) {
|
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
exit(129);
|
|
|
|
case PARSE_OPT_DONE:
|
2008-07-08 15:19:35 +02:00
|
|
|
if (ctx.argv[0])
|
|
|
|
dashdash_pos = ctx.cpidx;
|
2008-07-08 15:19:34 +02:00
|
|
|
goto parse_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(ctx.argv[0], "--reverse")) {
|
|
|
|
ctx.argv[0] = "--children";
|
|
|
|
reverse = 1;
|
|
|
|
}
|
2008-07-09 23:38:34 +02:00
|
|
|
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
|
2008-07-08 15:19:34 +02:00
|
|
|
}
|
|
|
|
parse_done:
|
2012-09-21 22:52:25 +02:00
|
|
|
no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES);
|
2016-12-23 21:32:22 +01:00
|
|
|
xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
|
2012-09-21 22:52:25 +02:00
|
|
|
DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
|
2008-07-08 15:19:34 +02:00
|
|
|
argc = parse_options_end(&ctx);
|
|
|
|
|
2015-12-13 01:51:03 +01:00
|
|
|
if (incremental || (output_option & OUTPUT_PORCELAIN)) {
|
|
|
|
if (show_progress > 0)
|
2016-09-15 16:58:58 +02:00
|
|
|
die(_("--progress can't be used with --incremental or porcelain formats"));
|
2015-12-13 01:51:03 +01:00
|
|
|
show_progress = 0;
|
|
|
|
} else if (show_progress < 0)
|
|
|
|
show_progress = isatty(2);
|
|
|
|
|
blame: fix alignment with --abbrev=40
The blame command internally adds 1 to any requested sha1
abbreviation length, and then subtracts it when outputting a
boundary commit. This lets regular and boundary sha1s line
up visually, but it misses one corner case.
When the requested length is 40, we bump the value to 41.
But since we only have 40 characters, that's all we can show
(fortunately the truncation is done by a printf precision
field, so it never tries to read past the end of the
buffer). So a normal sha1 shows 40 hex characters, and a
boundary sha1 shows "^" plus 40 hex characters. The result
is misaligned.
The "-l" option to show long sha1s gets around this by
skipping the "abbrev" variable entirely and just always
using GIT_SHA1_HEXSZ. This avoids the "+1" issue, but it
does mean that boundary commits only have 39 characters
printed. This is somewhat odd, but it does look good
visually: the results are aligned and left-justified. The
alternative would be to allocate an extra column that would
contain either an extra space or the "^" boundary marker.
As this is by definition the human-readable view, it's
probably not that big a deal either way (and of course
--porcelain, etc, correctly produce correct 40-hex sha1s).
But for consistency, this patch teaches --abbrev=40 to
produce the same output as "-l" (always left-aligned, with
40-hex for normal sha1s, and "^" plus 39-hex for
boundaries).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-01-06 05:17:40 +01:00
|
|
|
if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ)
|
2012-07-02 09:54:00 +02:00
|
|
|
/* one more abbrev length is needed for the boundary commit */
|
|
|
|
abbrev++;
|
2017-01-06 05:18:08 +01:00
|
|
|
else if (!abbrev)
|
|
|
|
abbrev = GIT_SHA1_HEXSZ;
|
2011-04-06 04:20:50 +02:00
|
|
|
|
2009-03-18 08:13:03 +01:00
|
|
|
if (revs_file && read_ancestry(revs_file))
|
2009-06-27 17:58:46 +02:00
|
|
|
die_errno("reading graft file '%s' failed", revs_file);
|
2009-03-18 08:13:03 +01:00
|
|
|
|
2009-02-20 23:51:11 +01:00
|
|
|
if (cmd_is_annotate) {
|
2008-09-05 09:57:35 +02:00
|
|
|
output_option |= OUTPUT_ANNOTATE_COMPAT;
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 18:55:02 +02:00
|
|
|
blame_date_mode.type = DATE_ISO8601;
|
2009-02-20 23:51:11 +01:00
|
|
|
} else {
|
|
|
|
blame_date_mode = revs.date_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The maximum width used to show the dates */
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 18:55:02 +02:00
|
|
|
switch (blame_date_mode.type) {
|
2009-02-20 23:51:11 +01:00
|
|
|
case DATE_RFC2822:
|
|
|
|
blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
|
|
|
|
break;
|
2014-08-29 18:58:42 +02:00
|
|
|
case DATE_ISO8601_STRICT:
|
|
|
|
blame_date_width = sizeof("2006-10-19T16:00:04-07:00");
|
|
|
|
break;
|
2009-02-20 23:51:11 +01:00
|
|
|
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;
|
2016-07-22 21:51:49 +02:00
|
|
|
case DATE_UNIX:
|
|
|
|
blame_date_width = sizeof("1161298804");
|
|
|
|
break;
|
2009-02-20 23:51:11 +01:00
|
|
|
case DATE_SHORT:
|
|
|
|
blame_date_width = sizeof("2006-10-19");
|
|
|
|
break;
|
|
|
|
case DATE_RELATIVE:
|
C style: use standard style for "TRANSLATORS" comments
Change all the "TRANSLATORS: [...]" comments in the C code to use the
regular Git coding style, and amend the style guide so that the
example there uses that style.
This custom style was necessary back in 2010 when the gettext support
was initially added, and was subsequently documented in commit
cbcfd4e3ea ("i18n: mention "TRANSLATORS:" marker in
Documentation/CodingGuidelines", 2014-04-18).
GNU xgettext hasn't had the parsing limitation that necessitated this
exception for almost 3 years. Since its 0.19 release on 2014-06-02
it's been able to recognize TRANSLATOR comments in the standard Git
comment syntax[1].
Usually we'd like to keep compatibility with software that's that
young, but in this case literally the only person who needs to be
using a gettext newer than 3 years old is Jiang Xin (the only person
who runs & commits "make pot" results), so I think in this case we can
make an exception.
This xgettext parsing feature was added after a thread on the Git
mailing list[2] which continued on the bug-gettext[3] list, but we
never subsequently changed our style & styleguide, do so.
There are already longstanding changes in git that use the standard
comment style & have their TRANSLATORS comments extracted properly
without getting the literal "*"'s mixed up in the text, as would
happen before xgettext 0.19.
Commit 7ff2683253 ("builtin-am: implement -i/--interactive",
2015-08-04) added one such comment, which in commit df0617bfa7 ("l10n:
git.pot: v2.6.0 round 1 (123 new, 41 removed)", 2015-09-05) got picked
up in the po/git.pot file with the right format, showing that Jiang
already runs a modern xgettext.
The xgettext parser does not handle the sort of non-standard comment
style that I'm amending here in sequencer.c, but that isn't standard
Git comment syntax anyway. With this change to sequencer.c & "make
pot" the comment in the pot file is now correct:
#. TRANSLATORS: %s will be "revert", "cherry-pick" or
-#. * "rebase -i".
+#. "rebase -i".
1. http://git.savannah.gnu.org/cgit/gettext.git/commit/?id=10af7fe6bd
2. <2ce9ec406501d112e032c8208417f8100bed04c6.1397712142.git.worldhello.net@gmail.com>
(https://public-inbox.org/git/2ce9ec406501d112e032c8208417f8100bed04c6.1397712142.git.worldhello.net@gmail.com/)
3. https://lists.gnu.org/archive/html/bug-gettext/2014-04/msg00016.html
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-05-11 23:20:12 +02:00
|
|
|
/*
|
|
|
|
* TRANSLATORS: This string is used to tell us the
|
|
|
|
* maximum display width for a relative timestamp in
|
|
|
|
* "git blame" output. For C locale, "4 years, 11
|
|
|
|
* months ago", which takes 22 places, is the longest
|
|
|
|
* among various forms of relative timestamps, but
|
|
|
|
* your language may need more or fewer display
|
|
|
|
* columns.
|
|
|
|
*/
|
2014-04-22 16:39:10 +02:00
|
|
|
blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
|
|
|
|
break;
|
2009-02-20 23:51:11 +01:00
|
|
|
case DATE_NORMAL:
|
|
|
|
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
|
|
|
|
break;
|
2015-06-25 18:55:45 +02:00
|
|
|
case DATE_STRFTIME:
|
|
|
|
blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */
|
|
|
|
break;
|
2009-02-20 23:51:11 +01:00
|
|
|
}
|
|
|
|
blame_date_width -= 1; /* strip the null */
|
2008-09-05 09:57:35 +02:00
|
|
|
|
2008-07-31 09:05:22 +02:00
|
|
|
if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
|
|
|
|
opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
|
|
|
|
PICKAXE_BLAME_COPY_HARDER);
|
|
|
|
|
2007-01-30 02:36:22 +01:00
|
|
|
/*
|
|
|
|
* We have collected options unknown to us in argv[1..unk]
|
2006-10-20 01:00:04 +02:00
|
|
|
* which are to be passed to revision machinery if we are
|
2007-02-04 05:49:16 +01:00
|
|
|
* going to do the "bottom" processing.
|
2006-10-20 01:00:04 +02:00
|
|
|
*
|
|
|
|
* The remaining are:
|
|
|
|
*
|
2010-08-22 13:12:12 +02:00
|
|
|
* (1) if dashdash_pos != 0, it is either
|
2008-07-08 15:19:35 +02:00
|
|
|
* "blame [revisions] -- <path>" or
|
|
|
|
* "blame -- <path> <rev>"
|
2006-10-20 01:00:04 +02:00
|
|
|
*
|
2010-08-22 13:12:12 +02:00
|
|
|
* (2) otherwise, it is one of the two:
|
2008-07-08 15:19:35 +02:00
|
|
|
* "blame [revisions] <path>"
|
|
|
|
* "blame <path> <rev>"
|
2006-10-20 01:00:04 +02:00
|
|
|
*
|
2008-07-08 15:19:35 +02: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-20 01:00:04 +02:00
|
|
|
*/
|
2008-07-08 15:19:35 +02:00
|
|
|
if (dashdash_pos) {
|
|
|
|
switch (argc - dashdash_pos - 1) {
|
|
|
|
case 2: /* (1b) */
|
|
|
|
if (argc != 4)
|
2008-07-08 15:19:34 +02:00
|
|
|
usage_with_options(blame_opt_usage, options);
|
2008-07-08 15:19:35 +02: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-20 01:00:04 +02:00
|
|
|
}
|
2008-07-08 15:19:35 +02:00
|
|
|
} else {
|
|
|
|
if (argc < 2)
|
2008-07-08 15:19:34 +02:00
|
|
|
usage_with_options(blame_opt_usage, options);
|
2008-07-08 15:19:35 +02:00
|
|
|
path = add_prefix(prefix, argv[argc - 1]);
|
2015-05-19 23:44:23 +02:00
|
|
|
if (argc == 3 && !file_exists(path)) { /* (2b) */
|
2008-07-08 15:19:35 +02:00
|
|
|
path = add_prefix(prefix, argv[1]);
|
|
|
|
argv[1] = argv[2];
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
2008-07-08 15:19:35 +02:00
|
|
|
argv[argc - 1] = "--";
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2008-07-08 15:19:35 +02:00
|
|
|
setup_work_tree();
|
2015-05-19 23:44:23 +02:00
|
|
|
if (!file_exists(path))
|
2009-06-27 17:58:46 +02:00
|
|
|
die_errno("cannot stat path '%s'", path);
|
2006-10-20 01:00:04 +02:00
|
|
|
}
|
|
|
|
|
2009-11-03 15:59:18 +01:00
|
|
|
revs.disable_stdin = 1;
|
2008-07-08 15:19:35 +02:00
|
|
|
setup_revisions(argc, argv, &revs, NULL);
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2017-05-24 07:15:29 +02:00
|
|
|
init_scoreboard(&sb);
|
2008-04-03 07:17:53 +02:00
|
|
|
sb.revs = &revs;
|
2017-05-24 07:15:20 +02:00
|
|
|
sb.contents_from = contents_from;
|
2017-05-24 07:15:21 +02:00
|
|
|
sb.reverse = reverse;
|
2017-05-24 07:15:30 +02:00
|
|
|
setup_scoreboard(&sb, path, &o);
|
|
|
|
lno = sb.num_lines;
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2013-08-06 15:59:38 +02:00
|
|
|
if (lno && !range_list.nr)
|
2016-08-02 12:52:53 +02:00
|
|
|
string_list_append(&range_list, "1");
|
2013-08-06 15:59:38 +02:00
|
|
|
|
2013-08-06 15:59:42 +02:00
|
|
|
anchor = 1;
|
2013-08-06 15:59:38 +02:00
|
|
|
range_set_init(&ranges, range_list.nr);
|
|
|
|
for (range_i = 0; range_i < range_list.nr; ++range_i) {
|
|
|
|
long bottom, top;
|
|
|
|
if (parse_range_arg(range_list.items[range_i].string,
|
2013-08-06 15:59:42 +02:00
|
|
|
nth_line_cb, &sb, lno, anchor,
|
2013-08-06 15:59:38 +02:00
|
|
|
&bottom, &top, sb.path))
|
|
|
|
usage(blame_usage);
|
|
|
|
if (lno < top || ((lno || bottom) && lno < bottom))
|
2016-09-15 16:58:58 +02:00
|
|
|
die(Q_("file %s has only %lu line",
|
|
|
|
"file %s has only %lu lines",
|
|
|
|
lno), path, lno);
|
2013-08-06 15:59:38 +02:00
|
|
|
if (bottom < 1)
|
|
|
|
bottom = 1;
|
|
|
|
if (top < 1)
|
|
|
|
top = lno;
|
|
|
|
bottom--;
|
|
|
|
range_set_append_unsafe(&ranges, bottom, top);
|
2013-08-06 15:59:42 +02:00
|
|
|
anchor = top + 1;
|
2013-08-06 15:59:38 +02:00
|
|
|
}
|
|
|
|
sort_and_merge_range_set(&ranges);
|
|
|
|
|
|
|
|
for (range_i = ranges.nr; range_i > 0; --range_i) {
|
|
|
|
const struct range *r = &ranges.ranges[range_i - 1];
|
2017-05-24 07:15:31 +02:00
|
|
|
ent = blame_entry_prepend(ent, r->start, r->end, o);
|
2013-08-06 15:59:38 +02:00
|
|
|
}
|
2014-04-26 01:56:49 +02:00
|
|
|
|
|
|
|
o->suspects = ent;
|
|
|
|
prio_queue_put(&sb.commits, o->commit);
|
|
|
|
|
2017-05-24 07:15:14 +02:00
|
|
|
blame_origin_decref(o);
|
2013-08-06 15:59:38 +02:00
|
|
|
|
|
|
|
range_set_release(&ranges);
|
|
|
|
string_list_clear(&range_list, 0);
|
2006-10-20 01:00:04 +02:00
|
|
|
|
2014-04-26 01:56:49 +02:00
|
|
|
sb.ent = NULL;
|
2006-10-20 01:00:04 +02:00
|
|
|
sb.path = path;
|
|
|
|
|
2017-05-24 07:15:19 +02:00
|
|
|
if (blame_move_score)
|
|
|
|
sb.move_score = blame_move_score;
|
|
|
|
if (blame_copy_score)
|
|
|
|
sb.copy_score = blame_copy_score;
|
|
|
|
|
2017-05-24 07:15:25 +02:00
|
|
|
sb.debug = DEBUG;
|
|
|
|
sb.on_sanity_fail = &sanity_check_on_fail;
|
|
|
|
|
2017-05-24 07:15:22 +02:00
|
|
|
sb.show_root = show_root;
|
2017-05-24 07:15:23 +02:00
|
|
|
sb.xdl_opts = xdl_opts;
|
2017-05-24 07:15:24 +02:00
|
|
|
sb.no_whole_file_rename = no_whole_file_rename;
|
2017-05-24 07:15:22 +02:00
|
|
|
|
2009-02-08 15:34:27 +01:00
|
|
|
read_mailmap(&mailmap, NULL);
|
2007-04-27 09:42:15 +02:00
|
|
|
|
2017-05-24 07:15:26 +02:00
|
|
|
sb.found_guilty_entry = &found_guilty_entry;
|
|
|
|
sb.found_guilty_entry_data = π
|
|
|
|
if (show_progress)
|
|
|
|
pi.progress = start_progress_delay(_("Blaming lines"),
|
|
|
|
sb.num_lines, 50, 1);
|
|
|
|
|
2015-12-13 01:51:03 +01:00
|
|
|
assign_blame(&sb, opt);
|
|
|
|
|
2017-05-24 07:15:26 +02:00
|
|
|
stop_progress(&pi.progress);
|
|
|
|
|
2007-11-03 13:22:53 +01:00
|
|
|
if (!incremental)
|
|
|
|
setup_pager();
|
2017-05-24 07:15:28 +02:00
|
|
|
else
|
2007-01-28 10:34:06 +01:00
|
|
|
return 0;
|
|
|
|
|
2017-05-24 07:15:27 +02:00
|
|
|
blame_sort_final(&sb);
|
2014-04-26 01:56:49 +02:00
|
|
|
|
2017-05-24 07:15:15 +02:00
|
|
|
blame_coalesce(&sb);
|
2006-10-20 01:00:04 +02:00
|
|
|
|
|
|
|
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 20:51:41 +01:00
|
|
|
|
2006-11-05 20:52:43 +01:00
|
|
|
if (show_stats) {
|
2017-05-24 07:15:18 +02:00
|
|
|
printf("num read blob: %d\n", sb.num_read_blob);
|
|
|
|
printf("num get patch: %d\n", sb.num_get_patch);
|
|
|
|
printf("num commits: %d\n", sb.num_commits);
|
2006-11-05 20:51:41 +01:00
|
|
|
}
|
2006-10-20 01:00:04 +02:00
|
|
|
return 0;
|
|
|
|
}
|