summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-blame.txt39
-rw-r--r--Makefile7
-rw-r--r--blame.c453
-rw-r--r--builtin-annotate.c25
-rw-r--r--builtin.h1
-rwxr-xr-xgit-annotate.perl708
-rw-r--r--git.c1
-rwxr-xr-xgitweb/gitweb.perl132
8 files changed, 483 insertions, 883 deletions
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index e1f89444a9..9891c1d377 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -7,7 +7,7 @@ git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
-'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>]
+'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] <file> [<rev>]
DESCRIPTION
-----------
@@ -45,10 +45,47 @@ OPTIONS
-S, --rev-file <revs-file>::
Use revs from revs-file instead of calling gitlink:git-rev-list[1].
+-f, --show-name::
+ Show filename in the original commit. By default
+ filename is shown if there is any line that came from a
+ file with different name, due to rename detection.
+
+-n, --show-number::
+ Show line number in the original commit (Default: off).
+
+-p, --porcelain::
+ Show in a format designed for machine consumption.
+
-h, --help::
Show help message.
+THE PORCELAIN FORMAT
+--------------------
+
+In this format, each line is output after a header; the
+header at the minumum has the first line which has:
+
+- 40-byte SHA-1 of the commit the line is attributed to;
+- the line number of the line in the original file;
+- the line number of the line in the final file;
+- on a line that starts a group of line from a different
+ commit than the previous one, the number of lines in this
+ group. On subsequent lines this field is absent.
+
+This header line is followed by the following information
+at least once for each commit:
+
+- author name ("author"), email ("author-mail"), time
+ ("author-time"), and timezone ("author-tz"); similarly
+ for committer.
+- filename in the commit the line is attributed to.
+- the first line of the commit log message ("summary").
+
+The contents of the actual line is output after the above
+header, prefixed by a TAB. This is to allow adding more
+header elements later.
+
SEE ALSO
--------
gitlink:git-annotate[1]
diff --git a/Makefile b/Makefile
index 66c8b4b127..95f37fe777 100644
--- a/Makefile
+++ b/Makefile
@@ -132,6 +132,8 @@ GITWEB_HOMETEXT = indextext.html
GITWEB_CSS = gitweb.css
GITWEB_LOGO = git-logo.png
GITWEB_FAVICON = git-favicon.png
+GITWEB_SITE_HEADER =
+GITWEB_SITE_FOOTER =
export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
@@ -173,7 +175,7 @@ SCRIPT_SH = \
SCRIPT_PERL = \
git-archimport.perl git-cvsimport.perl git-relink.perl \
git-shortlog.perl git-rerere.perl \
- git-annotate.perl git-cvsserver.perl \
+ git-cvsserver.perl \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
@@ -265,6 +267,7 @@ LIB_OBJS = \
BUILTIN_OBJS = \
builtin-add.o \
+ builtin-annotate.o \
builtin-apply.o \
builtin-archive.o \
builtin-cat-file.o \
@@ -675,6 +678,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
-e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
-e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
-e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
$< >$@+
chmod +x $@+
mv $@+ $@
diff --git a/blame.c b/blame.c
index 8cfd5d94c7..e664813cb4 100644
--- a/blame.c
+++ b/blame.c
@@ -17,19 +17,24 @@
#include "diffcore.h"
#include "revision.h"
#include "xdiff-interface.h"
+#include "quote.h"
#define DEBUG 0
-static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n"
- " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
- " -l, --long Show long commit SHA1 (Default: off)\n"
- " -t, --time Show raw timestamp (Default: off)\n"
- " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n"
- " -h, --help This message";
+static const char blame_usage[] =
+"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] file [commit]\n"
+" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+" -l, --long Show long commit SHA1 (Default: off)\n"
+" -t, --time Show raw timestamp (Default: off)\n"
+" -f, --show-name Show original filename (Default: auto)\n"
+" -n, --show-number Show original linenumber (Default: off)\n"
+" -p, --porcelain Show in a format designed for machine consumption\n"
+" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"
+" -h, --help This message";
static struct commit **blame_lines;
static int num_blame_lines;
-static char* blame_contents;
+static char *blame_contents;
static int blame_len;
struct util_info {
@@ -38,9 +43,10 @@ struct util_info {
char *buf;
unsigned long size;
int num_lines;
- const char* pathname;
+ const char *pathname;
+ unsigned meta_given:1;
- void* topo_data;
+ void *topo_data;
};
struct chunk {
@@ -156,11 +162,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
unsigned mode, int stage);
static unsigned char blob_sha1[20];
-static const char* blame_file;
+static const char *blame_file;
static int get_blob_sha1(struct tree *t, const char *pathname,
unsigned char *sha1)
{
- int i;
const char *pathspec[2];
blame_file = pathname;
pathspec[0] = pathname;
@@ -168,12 +173,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
hashclr(blob_sha1);
read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
- for (i = 0; i < 20; i++) {
- if (blob_sha1[i] != 0)
- break;
- }
-
- if (i == 20)
+ if (is_null_sha1(blob_sha1))
return -1;
hashcpy(sha1, blob_sha1);
@@ -239,7 +239,8 @@ static void print_map(struct commit *cmit, struct commit *other)
if (i < util->num_lines) {
num = util->line_map[i];
printf("%d\t", num);
- } else
+ }
+ else
printf("\t");
if (i < util2->num_lines) {
@@ -247,7 +248,8 @@ static void print_map(struct commit *cmit, struct commit *other)
printf("%d\t", num2);
if (num != -1 && num2 != num)
printf("---");
- } else
+ }
+ else
printf("\t");
printf("\n");
@@ -266,12 +268,12 @@ static void fill_line_map(struct commit *commit, struct commit *other,
int cur_chunk = 0;
int i1, i2;
- if (p->num && DEBUG)
- print_patch(p);
-
- if (DEBUG)
+ if (DEBUG) {
+ if (p->num)
+ print_patch(p);
printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
util2->num_lines);
+ }
for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
struct chunk *chunk = NULL;
@@ -293,7 +295,8 @@ static void fill_line_map(struct commit *commit, struct commit *other,
i2 += chunk->len2;
cur_chunk++;
- } else {
+ }
+ else {
if (i2 >= util2->num_lines)
break;
@@ -327,19 +330,15 @@ static int map_line(struct commit *commit, int line)
return info->line_map[line];
}
-static struct util_info* get_util(struct commit *commit)
+static struct util_info *get_util(struct commit *commit)
{
struct util_info *util = commit->util;
if (util)
return util;
- util = xmalloc(sizeof(struct util_info));
- util->buf = NULL;
- util->size = 0;
- util->line_map = NULL;
+ util = xcalloc(1, sizeof(struct util_info));
util->num_lines = -1;
- util->pathname = NULL;
commit->util = util;
return util;
}
@@ -369,7 +368,7 @@ static void alloc_line_map(struct commit *commit)
if (util->buf[i] == '\n')
util->num_lines++;
}
- if(util->buf[util->size - 1] != '\n')
+ if (util->buf[util->size - 1] != '\n')
util->num_lines++;
util->line_map = xmalloc(sizeof(int) * util->num_lines);
@@ -378,9 +377,9 @@ static void alloc_line_map(struct commit *commit)
util->line_map[i] = -1;
}
-static void init_first_commit(struct commit* commit, const char* filename)
+static void init_first_commit(struct commit *commit, const char *filename)
{
- struct util_info* util = commit->util;
+ struct util_info *util = commit->util;
int i;
util->pathname = filename;
@@ -395,18 +394,17 @@ static void init_first_commit(struct commit* commit, const char* filename)
util->line_map[i] = i;
}
-
static void process_commits(struct rev_info *rev, const char *path,
- struct commit** initial)
+ struct commit **initial)
{
int i;
- struct util_info* util;
+ struct util_info *util;
int lines_left;
int *blame_p;
int *new_lines;
int new_lines_len;
- struct commit* commit = get_revision(rev);
+ struct commit *commit = get_revision(rev);
assert(commit);
init_first_commit(commit, path);
@@ -442,7 +440,7 @@ static void process_commits(struct rev_info *rev, const char *path,
parents != NULL; parents = parents->next)
num_parents++;
- if(num_parents == 0)
+ if (num_parents == 0)
*initial = commit;
if (fill_util_info(commit))
@@ -503,13 +501,12 @@ static void process_commits(struct rev_info *rev, const char *path,
} while ((commit = get_revision(rev)) != NULL);
}
-
-static int compare_tree_path(struct rev_info* revs,
- struct commit* c1, struct commit* c2)
+static int compare_tree_path(struct rev_info *revs,
+ struct commit *c1, struct commit *c2)
{
int ret;
- const char* paths[2];
- struct util_info* util = c2->util;
+ const char *paths[2];
+ struct util_info *util = c2->util;
paths[0] = util->pathname;
paths[1] = NULL;
@@ -520,12 +517,11 @@ static int compare_tree_path(struct rev_info* revs,
return ret;
}
-
-static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
- const char* path)
+static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1,
+ const char *path)
{
int ret;
- const char* paths[2];
+ const char *paths[2];
paths[0] = path;
paths[1] = NULL;
@@ -536,9 +532,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
return ret;
}
-static const char* find_rename(struct commit* commit, struct commit* parent)
+static const char *find_rename(struct commit *commit, struct commit *parent)
{
- struct util_info* cutil = commit->util;
+ struct util_info *cutil = commit->util;
struct diff_options diff_opts;
const char *paths[1];
int i;
@@ -564,9 +560,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent)
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
- if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
+ if (p->status == 'R' &&
+ !strcmp(p->one->path, cutil->pathname)) {
if (DEBUG)
- printf("rename %s -> %s\n", p->one->path, p->two->path);
+ printf("rename %s -> %s\n",
+ p->one->path, p->two->path);
return p->two->path;
}
}
@@ -582,7 +580,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
return;
if (!commit->parents) {
- struct util_info* util = commit->util;
+ struct util_info *util = commit->util;
if (!same_tree_as_empty_path(revs, commit->tree,
util->pathname))
commit->object.flags |= TREECHANGE;
@@ -608,17 +606,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
case REV_TREE_NEW:
{
-
- struct util_info* util = commit->util;
+ struct util_info *util = commit->util;
if (revs->remove_empty_trees &&
same_tree_as_empty_path(revs, p->tree,
util->pathname)) {
- const char* new_name = find_rename(commit, p);
+ const char *new_name = find_rename(commit, p);
if (new_name) {
- struct util_info* putil = get_util(p);
+ struct util_info *putil = get_util(p);
if (!putil->pathname)
putil->pathname = xstrdup(new_name);
- } else {
+ }
+ else {
*pp = parent->next;
continue;
}
@@ -639,47 +637,106 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREECHANGE;
}
-
struct commit_info
{
- char* author;
- char* author_mail;
+ char *author;
+ char *author_mail;
unsigned long author_time;
- char* author_tz;
+ char *author_tz;
+
+ /* filled only when asked for details */
+ char *committer;
+ char *committer_mail;
+ unsigned long committer_time;
+ char *committer_tz;
+
+ char *summary;
};
-static void get_commit_info(struct commit* commit, struct commit_info* ret)
+static void get_ac_line(const char *inbuf, const char *what,
+ int bufsz, char *person, char **mail,
+ unsigned long *time, char **tz)
{
int len;
- char* tmp;
- static char author_buf[1024];
-
- tmp = strstr(commit->buffer, "\nauthor ") + 8;
- len = strchr(tmp, '\n') - tmp;
- ret->author = author_buf;
- memcpy(ret->author, tmp, len);
+ char *tmp, *endp;
+
+ tmp = strstr(inbuf, what);
+ if (!tmp)
+ goto error_out;
+ tmp += strlen(what);
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ len = strlen(tmp);
+ else
+ len = endp - tmp;
+ if (bufsz <= len) {
+ error_out:
+ /* Ugh */
+ person = *mail = *tz = "(unknown)";
+ *time = 0;
+ return;
+ }
+ memcpy(person, tmp, len);
- tmp = ret->author;
+ tmp = person;
tmp += len;
*tmp = 0;
- while(*tmp != ' ')
+ while (*tmp != ' ')
tmp--;
- ret->author_tz = tmp+1;
+ *tz = tmp+1;
*tmp = 0;
- while(*tmp != ' ')
+ while (*tmp != ' ')
tmp--;
- ret->author_time = strtoul(tmp, NULL, 10);
+ *time = strtoul(tmp, NULL, 10);
*tmp = 0;
- while(*tmp != ' ')
+ while (*tmp != ' ')
tmp--;
- ret->author_mail = tmp + 1;
-
+ *mail = tmp + 1;
*tmp = 0;
}
-static const char* format_time(unsigned long time, const char* tz_str,
+static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed)
+{
+ int len;
+ char *tmp, *endp;
+ static char author_buf[1024];
+ static char committer_buf[1024];
+ static char summary_buf[1024];
+
+ ret->author = author_buf;
+ get_ac_line(commit->buffer, "\nauthor ",
+ sizeof(author_buf), author_buf, &ret->author_mail,
+ &ret->author_time, &ret->author_tz);
+
+ if (!detailed)
+ return;
+
+ ret->committer = committer_buf;
+ get_ac_line(commit->buffer, "\ncommitter ",
+ sizeof(committer_buf), committer_buf, &ret->committer_mail,
+ &ret->committer_time, &ret->committer_tz);
+
+ ret->summary = summary_buf;
+ tmp = strstr(commit->buffer, "\n\n");
+ if (!tmp) {
+ error_out:
+ sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+ return;
+ }
+ tmp += 2;
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ goto error_out;
+ len = endp - tmp;
+ if (len >= sizeof(summary_buf))
+ goto error_out;
+ memcpy(summary_buf, tmp, len);
+ summary_buf[len] = 0;
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
int show_raw_time)
{
static char time_buf[128];
@@ -704,15 +761,15 @@ static const char* format_time(unsigned long time, const char* tz_str,
return time_buf;
}
-static void topo_setter(struct commit* c, void* data)
+static void topo_setter(struct commit *c, void *data)
{
- struct util_info* util = c->util;
+ struct util_info *util = c->util;
util->topo_data = data;
}
-static void* topo_getter(struct commit* c)
+static void *topo_getter(struct commit *c)
{
- struct util_info* util = c->util;
+ struct util_info *util = c->util;
return util->topo_data;
}
@@ -735,6 +792,101 @@ static int read_ancestry(const char *graft_file,
return 0;
}
+static int lineno_width(int lines)
+{
+ int i, width;
+
+ for (width = 1, i = 10; i <= lines + 1; width++)
+ i *= 10;
+ return width;
+}
+
+static int find_orig_linenum(struct util_info *u, int lineno)
+{
+ int i;
+
+ for (i = 0; i < u->num_lines; i++)
+ if (lineno == u->line_map[i])
+ return i + 1;
+ return 0;
+}
+
+static void emit_meta(struct commit *c, int lno,
+ int sha1_len, int compatibility, int porcelain,
+ int show_name, int show_number, int show_raw_time,
+ int longest_file, int longest_author,
+ int max_digits, int max_orig_digits)
+{
+ struct util_info *u;
+ int lineno;
+ struct commit_info ci;
+
+ u = c->util;
+ lineno = find_orig_linenum(u, lno);
+
+ if (porcelain) {
+ int group_size = -1;
+ struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1];
+ if (cc != c) {
+ /* This is the beginning of this group */
+ int i;
+ for (i = lno + 1; i < num_blame_lines; i++)
+ if (blame_lines[i] != c)
+ break;
+ group_size = i - lno;
+ }
+ if (0 < group_size)
+ printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1),
+ lineno, lno + 1, group_size);
+ else
+ printf("%s %d %d\n", sha1_to_hex(c->object.sha1),
+ lineno, lno + 1);
+ if (!u->meta_given) {
+ get_commit_info(c, &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("filename ");
+ if (quote_c_style(u->pathname, NULL, NULL, 0))
+ quote_c_style(u->pathname, NULL, stdout, 0);
+ else
+ fputs(u->pathname, stdout);
+ printf("\nsummary %s\n", ci.summary);
+
+ u->meta_given = 1;
+ }
+ putchar('\t');
+ return;
+ }
+
+ get_commit_info(c, &ci, 0);
+ fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+ if (compatibility) {
+ printf("\t(%10s\t%10s\t%d)", ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ lno + 1);
+ }
+ else {
+ if (show_name)
+ printf(" %-*.*s", longest_file, longest_file,
+ u->pathname);
+ if (show_number)
+ printf(" %*d", max_orig_digits,
+ lineno);
+ printf(" (%-*.*s %10s %*d) ",
+ longest_author, longest_author, ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ max_digits, lno + 1);
+ }
+}
+
int main(int argc, const char **argv)
{
int i;
@@ -747,38 +899,43 @@ int main(int argc, const char **argv)
int compatibility = 0;
int show_raw_time = 0;
int options = 1;
- struct commit* start_commit;
+ struct commit *start_commit;
- const char* args[10];
+ const char *args[10];
struct rev_info rev;
struct commit_info ci;
const char *buf;
- int max_digits;
- int longest_file, longest_author;
- int found_rename;
+ int max_digits, max_orig_digits;
+ int longest_file, longest_author, longest_file_lines;
+ int show_name = 0;
+ int show_number = 0;
+ int porcelain = 0;
- const char* prefix = setup_git_directory();
+ const char *prefix = setup_git_directory();
git_config(git_default_config);
- for(i = 1; i < argc; i++) {
- if(options) {
- if(!strcmp(argv[i], "-h") ||
+ for (i = 1; i < argc; i++) {
+ if (options) {
+ if (!strcmp(argv[i], "-h") ||
!strcmp(argv[i], "--help"))
usage(blame_usage);
- else if(!strcmp(argv[i], "-l") ||
- !strcmp(argv[i], "--long")) {
+ if (!strcmp(argv[i], "-l") ||
+ !strcmp(argv[i], "--long")) {
sha1_len = 40;
continue;
- } else if(!strcmp(argv[i], "-c") ||
- !strcmp(argv[i], "--compatibility")) {
+ }
+ if (!strcmp(argv[i], "-c") ||
+ !strcmp(argv[i], "--compatibility")) {
compatibility = 1;
continue;
- } else if(!strcmp(argv[i], "-t") ||
- !strcmp(argv[i], "--time")) {
+ }
+ if (!strcmp(argv[i], "-t") ||
+ !strcmp(argv[i], "--time")) {
show_raw_time = 1;
continue;
- } else if(!strcmp(argv[i], "-S")) {
+ }
+ if (!strcmp(argv[i], "-S")) {
if (i + 1 < argc &&
!read_ancestry(argv[i + 1], &sha1_p)) {
compatibility = 1;
@@ -786,33 +943,51 @@ int main(int argc, const char **argv)
continue;
}
usage(blame_usage);
- } else if(!strcmp(argv[i], "--")) {
+ }
+ if (!strcmp(argv[i], "-f") ||
+ !strcmp(argv[i], "--show-name")) {
+ show_name = 1;
+ continue;
+ }
+ if (!strcmp(argv[i], "-n") ||
+ !strcmp(argv[i], "--show-number")) {
+ show_number = 1;
+ continue;
+ }
+ if (!strcmp(argv[i], "-p") ||
+ !strcmp(argv[i], "--porcelain")) {
+ porcelain = 1;
+ sha1_len = 40;
+ show_raw_time = 1;
+ continue;
+ }
+ if (!strcmp(argv[i], "--")) {
options = 0;
continue;
- } else if(argv[i][0] == '-')
+ }
+ if (argv[i][0] == '-')
usage(blame_usage);
- else
- options = 0;
+ options = 0;
}
- if(!options) {
- if(!filename)
+ if (!options) {
+ if (!filename)
filename = argv[i];
- else if(!commit)
+ else if (!commit)
commit = argv[i];
else
usage(blame_usage);
}
}
- if(!filename)
+ if (!filename)
usage(blame_usage);
if (commit && sha1_p)
usage(blame_usage);
- else if(!commit)
+ else if (!commit)
commit = "HEAD";
- if(prefix)
+ if (prefix)
sprintf(filename_buf, "%s%s", prefix, filename);
else
strcpy(filename_buf, filename);
@@ -830,7 +1005,6 @@ int main(int argc, const char **argv)
return 1;
}
-
init_revisions(&rev, setup_git_directory());
rev.remove_empty_trees = 1;
rev.topo_order = 1;
@@ -848,62 +1022,49 @@ int main(int argc, const char **argv)
prepare_revision_walk(&rev);
process_commits(&rev, filename, &initial);
+ for (i = 0; i < num_blame_lines; i++)
+ if (!blame_lines[i])
+ blame_lines[i] = initial;
+
buf = blame_contents;
- for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
- i *= 10;
+ max_digits = lineno_width(num_blame_lines);
longest_file = 0;
longest_author = 0;
- found_rename = 0;
+ longest_file_lines = 0;
for (i = 0; i < num_blame_lines; i++) {
struct commit *c = blame_lines[i];
- struct util_info* u;
- if (!c)
- c = initial;
+ struct util_info *u;
u = c->util;
- if (!found_rename && strcmp(filename, u->pathname))
- found_rename = 1;
+ if (!show_name && strcmp(filename, u->pathname))
+ show_name = 1;
if (longest_file < strlen(u->pathname))
longest_file = strlen(u->pathname);
- get_commit_info(c, &ci);
+ if (longest_file_lines < u->num_lines)
+ longest_file_lines = u->num_lines;
+ get_commit_info(c, &ci, 0);
if (longest_author < strlen(ci.author))
longest_author = strlen(ci.author);
}
- for (i = 0; i < num_blame_lines; i++) {
- struct commit *c = blame_lines[i];
- struct util_info* u;
+ max_orig_digits = lineno_width(longest_file_lines);
- if (!c)
- c = initial;
-
- u = c->util;
- get_commit_info(c, &ci);
- fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
- if(compatibility) {
- printf("\t(%10s\t%10s\t%d)", ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- i+1);
- } else {
- if (found_rename)
- printf(" %-*.*s", longest_file, longest_file,
- u->pathname);
- printf(" (%-*.*s %10s %*d) ",
- longest_author, longest_author, ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- max_digits, i+1);
- }
+ for (i = 0; i < num_blame_lines; i++) {
+ emit_meta(blame_lines[i], i,
+ sha1_len, compatibility, porcelain,
+ show_name, show_number, show_raw_time,
+ longest_file, longest_author,
+ max_digits, max_orig_digits);
- if(i == num_blame_lines - 1) {
+ if (i == num_blame_lines - 1) {
fwrite(buf, blame_len - (buf - blame_contents),
1, stdout);
- if(blame_contents[blame_len-1] != '\n')
+ if (blame_contents[blame_len-1] != '\n')
putc('\n', stdout);
- } else {
- char* next_buf = strchr(buf, '\n') + 1;
+ }
+ else {
+ char *next_buf = strchr(buf, '\n') + 1;
fwrite(buf, next_buf - buf, 1, stdout);
buf = next_buf;
}
diff --git a/builtin-annotate.c b/builtin-annotate.c
new file mode 100644
index 0000000000..25ad473711
--- /dev/null
+++ b/builtin-annotate.c
@@ -0,0 +1,25 @@
+/*
+ * "git annotate" builtin alias
+ *
+ * Copyright (C) 2006 Ryan Anderson
+ */
+#include "git-compat-util.h"
+#include "exec_cmd.h"
+
+int cmd_annotate(int argc, const char **argv, const char *prefix)
+{
+ const char **nargv;
+ int i;
+ nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+ nargv[0] = "blame";
+ nargv[1] = "-c";
+
+ for (i = 1; i < argc; i++) {
+ nargv[i+1] = argv[i];
+ }
+ nargv[argc + 1] = NULL;
+
+ return execv_git_cmd(nargv);
+}
+
diff --git a/builtin.h b/builtin.h
index f71b9629b9..4ac32d7780 100644
--- a/builtin.h
+++ b/builtin.h
@@ -14,6 +14,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern void prune_packed_objects(int);
extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
extern int cmd_archive(int argc, const char **argv, const char *prefix);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
diff --git a/git-annotate.perl b/git-annotate.perl
deleted file mode 100755
index 215ed26f3a..0000000000
--- a/git-annotate.perl
+++ /dev/null
@@ -1,708 +0,0 @@
-#!/usr/bin/perl
-# Copyright 2006, Ryan Anderson <ryan@michonline.com>
-#
-# GPL v2 (See COPYING)
-#
-# This file is licensed under the GPL v2, or a later version
-# at the discretion of Linus Torvalds.
-
-use warnings;
-use strict;
-use Getopt::Long;
-use POSIX qw(strftime gmtime);
-use File::Basename qw(basename dirname);
-
-sub usage() {
- print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
- -l, --long
- Show long rev (Defaults off)
- -t, --time
- Show raw timestamp (Defaults off)
- -r, --rename
- Follow renames (Defaults on).
- -S, --rev-file revs-file
- Use revs from revs-file instead of calling git-rev-list
- -h, --help
- This message.
-";
-
- exit(1);
-}
-
-our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
-
-my $rc = GetOptions( "long|l" => \$longrev,
- "time|t" => \$rawtime,
- "help|h" => \$help,
- "rename|r" => \$rename,
- "rev-file|S=s" => \$rev_file);
-if (!$rc or $help or !@ARGV) {
- usage();
-}
-
-my $filename = shift @ARGV;
-if (@ARGV) {
- $starting_rev = shift @ARGV;
-}
-
-my @stack = (
- {
- 'rev' => defined $starting_rev ? $starting_rev : "HEAD",
- 'filename' => $filename,
- },
-);
-
-our @filelines = ();
-
-if (defined $starting_rev) {
- @filelines = git_cat_file($starting_rev, $filename);
-} else {
- open(F,"<",$filename)
- or die "Failed to open filename: $!";
-
- while(<F>) {
- chomp;
- push @filelines, $_;
- }
- close(F);
-
-}
-
-our %revs;
-our @revqueue;
-our $head;
-
-my $revsprocessed = 0;
-while (my $bound = pop @stack) {
- my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});
- foreach my $revinst (@revisions) {
- my ($rev, @parents) = @$revinst;
- $head ||= $rev;
-
- if (!defined($rev)) {
- $rev = "";
- }
- $revs{$rev}{'filename'} = $bound->{'filename'};
- if (scalar @parents > 0) {
- $revs{$rev}{'parents'} = \@parents;
- next;
- }
-
- if (!$rename) {
- next;
- }
-
- my $newbound = find_parent_renames($rev, $bound->{'filename'});
- if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {
- push @stack, $newbound;
- $revs{$rev}{'parents'} = [$newbound->{'rev'}];
- }
- }
-}
-push @revqueue, $head;
-init_claim( defined $starting_rev ? $head : 'dirty');
-unless (defined $starting_rev) {
- my $diff = open_pipe("git","diff","HEAD", "--",$filename)
- or die "Failed to call git diff to check for dirty state: $!";
-
- _git_diff_parse($diff, [$head], "dirty", (
- 'author' => gitvar_name("GIT_AUTHOR_IDENT"),
- 'author_date' => sprintf("%s +0000",time()),
- )
- );
- close($diff);
-}
-handle_rev();
-
-
-my $i = 0;
-foreach my $l (@filelines) {
- my ($output, $rev, $committer, $date);
- if (ref $l eq 'ARRAY') {
- ($output, $rev, $committer, $date) = @$l;
- if (!$longrev && length($rev) > 8) {
- $rev = substr($rev,0,8);
- }
- } else {
- $output = $l;
- ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown');
- }
-
- printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
- format_date($date), ++$i, $output);
-}
-
-sub init_claim {
- my ($rev) = @_;
- for (my $i = 0; $i < @filelines; $i++) {
- $filelines[$i] = [ $filelines[$i], '', '', '', 1];
- # line,
- # rev,
- # author,
- # date,
- # 1 <-- belongs to the original file.
- }
- $revs{$rev}{'lines'} = \@filelines;
-}
-
-
-sub handle_rev {
- my $revseen = 0;
- my %seen;
- while (my $rev = shift @revqueue) {
- next if $seen{$rev}++;
-
- my %revinfo = git_commit_info($rev);
-
- if (exists $revs{$rev}{parents} &&
- scalar @{$revs{$rev}{parents}} != 0) {
-
- git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo);
- push @revqueue, @{$revs{$rev}{'parents'}};
-
- } else {
- # We must be at the initial rev here, so claim everything that is left.
- for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
- if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
- claim_line($i, $rev, $revs{$rev}{lines}, %revinfo);
- }
- }
- }
- }
-}
-
-
-sub git_rev_list {
- my ($rev, $file) = @_;
-
- my $revlist;
- if ($rev_file) {
- open($revlist, '<' . $rev_file)
- or die "Failed to open $rev_file : $!";
- } else {
- $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
- or die "Failed to exec git-rev-list: $!";
- }
-
- my @revs;
- while(my $line = <$revlist>) {
- chomp $line;
- my ($rev, @parents) = split /\s+/, $line;
- push @revs, [ $rev, @parents ];
- }
- close($revlist);
-
- printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0);
- return @revs;
-}
-
-sub find_parent_renames {
- my ($rev, $file) = @_;
-
- my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev")
- or die "Failed to exec git-diff: $!";
-
- local $/ = "\0";
- my %bound;
- my $junk = <$patch>;
- while (my $change = <$patch>) {
- chomp $change;
- my $filename = <$patch>;
- if (!defined $filename) {
- next;
- }
- chomp $filename;
-
- if ($change =~ m/^[AMD]$/ ) {
- next;
- } elsif ($change =~ m/^R/ ) {
- my $oldfilename = $filename;
- $filename = <$patch>;
- chomp $filename;
- if ( $file eq $filename ) {
- my $parent = git_find_parent($rev, $oldfilename);
- @bound{'rev','filename'} = ($parent, $oldfilename);
- last;
- }
- }
- }
- close($patch);
-
- return \%bound;
-}
-
-
-sub git_find_parent {
- my ($rev, $filename) = @_;
-
- my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename)
- or die "Failed to open git-rev-list to find a single parent: $!";
-
- my $parentline = <$revparent>;
- chomp $parentline;
- my ($revfound,$parent) = split m/\s+/, $parentline;
-
- close($revparent);
-
- return $parent;
-}
-
-sub git_find_all_parents {
- my ($rev) = @_;
-
- my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev")
- or die "Failed to open git-rev-list to find a single parent: $!";
-
- my $parentline = <$revparent>;
- chomp $parentline;
- my ($origrev, @parents) = split m/\s+/, $parentline;
-
- close($revparent);
-
- return @parents;
-}
-
-sub git_merge_base {
- my ($rev1, $rev2) = @_;
-
- my $mb = open_pipe("git-merge-base", $rev1, $rev2)
- or die "Failed to open git-merge-base: $!";
-
- my $base = <$mb>;
- chomp $base;
-
- close($mb);
-
- return $base;
-}
-
-# Construct a set of pseudo parents that are in the same order,
-# and the same quantity as the real parents,
-# but whose SHA1s are as similar to the logical parents
-# as possible.
-sub get_pseudo_parents {
- my ($all, $fake) = @_;
-
- my @all = @$all;
- my @fake = @$fake;
-
- my @pseudo;
-
- my %fake = map {$_ => 1} @fake;
- my %seenfake;
-
- my $fakeidx = 0;
- foreach my $p (@all) {
- if (exists $fake{$p}) {
- if ($fake[$fakeidx] ne $p) {
- die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n",
- $fake[$fakeidx], $p,
- join(", ", @all),
- join(", ", @fake),
- );
- }
-
- push @pseudo, $p;
- $fakeidx++;
- $seenfake{$p}++;
-
- } else {
- my $base = git_merge_base($fake[$fakeidx], $p);
- if ($base ne $fake[$fakeidx]) {
- die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n",
- $fake[$fakeidx], $p, $base);
- }
-
- # The details of how we parse the diffs
- # mean that we cannot have a duplicate
- # revision in the list, so if we've already
- # seen the revision we would normally add, just use
- # the actual revision.
- if ($seenfake{$base}) {
- push @pseudo, $p;
- } else {
- push @pseudo, $base;
- $seenfake{$base}++;
- }
- }
- }
-
- return @pseudo;
-}
-
-
-# Get a diff between the current revision and a parent.
-# Record the commit information that results.
-sub git_diff_parse {
- my ($parents, $rev, %revinfo) = @_;
-
- my @pseudo_parents;
- my @command = ("git-diff-tree");
- my $revision_spec;
-
- if (scalar @$parents == 1) {
-
- $revision_spec = join("..", $parents->[0], $rev);
- @pseudo_parents = @$parents;
- } else {
- my @all_parents = git_find_all_parents($rev);
-
- if (@all_parents != @$parents) {
- @pseudo_parents = get_pseudo_parents(\@all_parents, $parents);
- } else {
- @pseudo_parents = @$parents;
- }
-
- $revision_spec = $rev;
- push @command, "-c";
- }
-
- my @filenames = ( $revs{$rev}{'filename'} );
-
- foreach my $parent (@$parents) {
- push @filenames, $revs{$parent}{'filename'};
- }
-
- push @command, "-p", "-M", $revision_spec, "--", @filenames;
-
-
- my $diff = open_pipe( @command )
- or die "Failed to call git-diff for annotation: $!";
-
- _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo);
-
- close($diff);
-}
-
-sub _git_diff_parse {
- my ($diff, $parents, $rev, %revinfo) = @_;
-
- my $ri = 0;
-
- my $slines = $revs{$rev}{'lines'};
- my (%plines, %pi);
-
- my $gotheader = 0;
- my ($remstart);
- my $parent_count = @$parents;
-
- my $diff_header_regexp = "^@";
- $diff_header_regexp .= "@" x @$parents;
- $diff_header_regexp .= ' -\d+,\d+' x @$parents;
- $diff_header_regexp .= ' \+(\d+),\d+';
- $diff_header_regexp .= " " . ("@" x @$parents);
-
- my %claim_regexps;
- my $allparentplus = '^' . '\\+' x @$parents . '(.*)$';
-
- {
- my $i = 0;
- foreach my $parent (@$parents) {
-
- $pi{$parent} = 0;
- my $r = '^' . '.' x @$parents . '(.*)$';
- my $p = $r;
- substr($p,$i+1, 1) = '\\+';
-
- my $m = $r;
- substr($m,$i+1, 1) = '-';
-
- $claim_regexps{$parent}{plus} = $p;
- $claim_regexps{$parent}{minus} = $m;
-
- $plines{$parent} = [];
-
- $i++;
- }
- }
-
- DIFF:
- while(<$diff>) {
- chomp;
- #printf("%d:%s:\n", $gotheader, $_);
- if (m/$diff_header_regexp/) {
- $remstart = $1 - 1;
- # (0-based arrays)
-
- $gotheader = 1;
-
- foreach my $parent (@$parents) {
- for (my $i = $ri; $i < $remstart; $i++) {
- $plines{$parent}[$pi{$parent}++] = $slines->[$i];
- }
- }
- $ri = $remstart;
-
- next DIFF;
-
- } elsif (!$gotheader) {
- # Skip over the leadin.
- next DIFF;
- }
-
- if (m/^\\/) {
- ;
- # Skip \No newline at end of file.
- # But this can be internationalized, so only look
- # for an initial \
-
- } else {
- my %claims = ();
- my $negclaim = 0;
- my $allclaimed = 0;
- my $line;
-
- if (m/$allparentplus/) {
- claim_line($ri, $rev, $slines, %revinfo);
- $allclaimed = 1;
-
- }
-
- PARENT:
- foreach my $parent (keys %claim_regexps) {
- my $m = $claim_regexps{$parent}{minus};
- my $p = $claim_regexps{$parent}{plus};
-
- if (m/$m/) {
- $line = $1;
- $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ];
- $negclaim++;
-
- } elsif (m/$p/) {
- $line = $1;
- if (get_line($slines, $ri) eq $line) {
- # Found a match, claim
- $claims{$parent}++;
-
- } else {
- die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n",
- $ri, $line,
- get_line($slines, $ri),
- $rev, $parent);
- }
- }
- }
-
- if (%claims) {
- foreach my $parent (@$parents) {
- next if $claims{$parent} || $allclaimed;
- $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
- #[ $line, '', '', '', 0 ];
- }
- $ri++;
-
- } elsif ($negclaim) {
- next DIFF;
-
- } else {
- if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) {
- foreach my $parent (@$parents) {
- printf("parent %s is on line %d\n", $parent, $pi{$parent});
- }
-
- my @context;
- for (my $i = -2; $i < 2; $i++) {
- push @context, get_line($slines, $ri + $i);
- }
- my $context = join("\n", @context);
-
- my $justline = substr($_, scalar @$parents);
- die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n",
- $ri,
- $justline,
- $context);
- }
- foreach my $parent (@$parents) {
- $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
- }
- $ri++;
- }
- }
- }
-
- for (my $i = $ri; $i < @{$slines} ; $i++) {
- foreach my $parent (@$parents) {
- push @{$plines{$parent}}, $slines->[$ri];
- }
- $ri++;
- }
-
- foreach my $parent (@$parents) {
- $revs{$parent}{lines} = $plines{$parent};
- }
-
- return;
-}
-
-sub get_line {
- my ($lines, $index) = @_;
-
- return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index];
-}
-
-sub git_cat_file {
- my ($rev, $filename) = @_;
- return () unless defined $rev && defined $filename;
-
- my $blob = git_ls_tree($rev, $filename);
- die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob;
-
- my $catfile = open_pipe("git","cat-file", "blob", $blob)
- or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
-
- my @lines;
- while(<$catfile>) {
- chomp;
- push @lines, $_;
- }
- close($catfile);
-
- return @lines;
-}
-
-sub git_ls_tree {
- my ($rev, $filename) = @_;
-
- my $lstree = open_pipe("git","ls-tree",$rev,$filename)
- or die "Failed to call git ls-tree: $!";
-
- my ($mode, $type, $blob, $tfilename);
- while(<$lstree>) {
- chomp;
- ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
- last if ($tfilename eq $filename);
- }
- close($lstree);
-
- return $blob if ($tfilename eq $filename);
- die "git-ls-tree failed to find blob for $filename";
-
-}
-
-
-
-sub claim_line {
- my ($floffset, $rev, $lines, %revinfo) = @_;
- my $oline = get_line($lines, $floffset);
- @{$lines->[$floffset]} = ( $oline, $rev,
- $revinfo{'author'}, $revinfo{'author_date'} );
- #printf("Claiming line %d with rev %s: '%s'\n",
- # $floffset, $rev, $oline) if 1;
-}
-
-sub git_commit_info {
- my ($rev) = @_;
- my $commit = open_pipe("git-cat-file", "commit", $rev)
- or die "Failed to call git-cat-file: $!";
-
- my %info;
- while(<$commit>) {
- chomp;
- last if (length $_ == 0);
-
- if (m/^author (.*) <(.*)> (.*)$/) {
- $info{'author'} = $1;
- $info{'author_email'} = $2;
- $info{'author_date'} = $3;
- } elsif (m/^committer (.*) <(.*)> (.*)$/) {
- $info{'committer'} = $1;
- $info{'committer_email'} = $2;
- $info{'committer_date'} = $3;
- }
- }
- close($commit);
-
- return %info;
-}
-
-sub format_date {
- if ($rawtime) {
- return $_[0];
- }
- my ($timestamp, $timezone) = split(' ', $_[0]);
- my $minutes = abs($timezone);
- $minutes = int($minutes / 100) * 60 + ($minutes % 100);
- if ($timezone < 0) {
- $minutes = -$minutes;
- }
- my $t = $timestamp + $minutes * 60;
- return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t));
-}
-
-# Copied from git-send-email.perl - We need a Git.pm module..
-sub gitvar {
- my ($var) = @_;
- my $fh;
- my $pid = open($fh, '-|');
- die "$!" unless defined $pid;
- if (!$pid) {
- exec('git-var', $var) or die "$!";
- }
- my ($val) = <$fh>;
- close $fh or die "$!";
- chomp($val);
- return $val;
-}
-
-sub gitvar_name {
- my ($name) = @_;
- my $val = gitvar($name);
- my @field = split(/\s+/, $val);
- return join(' ', @field[0...(@field-4)]);
-}
-
-sub open_pipe {
- if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
- return open_pipe_activestate(@_);
- } else {
- return open_pipe_normal(@_);
- }
-}
-
-sub open_pipe_activestate {
- tie *fh, "Git::ActiveStatePipe", @_;
- return *fh;
-}
-
-sub open_pipe_normal {
- my (@execlist) = @_;
-
- my $pid = open my $kid, "-|";
- defined $pid or die "Cannot fork: $!";
-
- unless ($pid) {
- exec @execlist;
- die "Cannot exec @execlist: $!";
- }
-
- return $kid;
-}
-
-package Git::ActiveStatePipe;
-use strict;
-
-sub TIEHANDLE {
- my ($class, @params) = @_;
- my $cmdline = join " ", @params;
- my @data = qx{$cmdline};
- bless { i => 0, data => \@data }, $class;
-}
-
-sub READLINE {
- my $self = shift;
- if ($self->{i} >= scalar @{$self->{data}}) {
- return undef;
- }
- return $self->{'data'}->[ $self->{i}++ ];
-}
-
-sub CLOSE {
- my $self = shift;
- delete $self->{data};
- delete $self->{i};
-}
-
-sub EOF {
- my $self = shift;
- return ($self->{i} >= scalar @{$self->{data}});
-}
diff --git a/git.c b/git.c
index e089b53571..235b6fea15 100644
--- a/git.c
+++ b/git.c
@@ -219,6 +219,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
int option;
} commands[] = {
{ "add", cmd_add, RUN_SETUP },
+ { "annotate", cmd_annotate, },
{ "apply", cmd_apply },
{ "archive", cmd_archive },
{ "cat-file", cmd_cat_file, RUN_SETUP },
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index bc8d8eb238..9eb19bb82e 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -41,8 +41,18 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
# replace this with something more descriptive for clearer bookmarks
our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
+# filename of html text to include at top of each page
+our $site_header = "++GITWEB_SITE_HEADER++";
# html text to include at home page
our $home_text = "++GITWEB_HOMETEXT++";
+# filename of html text to include at bottom of each page
+our $site_footer = "++GITWEB_SITE_FOOTER++";
+
+# URI of stylesheets
+our @stylesheets = ("++GITWEB_CSS++");
+our $stylesheet;
+# default is not to define style sheet, but it can be overwritten later
+undef $stylesheet;
# URI of default stylesheet
our $stylesheet = "++GITWEB_CSS++";
@@ -217,6 +227,22 @@ sub feature_pickaxe {
return ($_[0]);
}
+# checking HEAD file with -e is fragile if the repository was
+# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
+# and then pruned.
+sub check_head_link {
+ my ($dir) = @_;
+ my $headfile = "$dir/HEAD";
+ return ((-e $headfile) ||
+ (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
+}
+
+sub check_export_ok {
+ my ($dir) = @_;
+ return (check_head_link($dir) &&
+ (!$export_ok || -e "$dir/$export_ok"));
+}
+
# rename detection options for git-diff and git-diff-tree
# - default is '-M', with the cost proportional to
# (number of removed files) * (number of new files).
@@ -249,7 +275,7 @@ our $project = $cgi->param('p');
if (defined $project) {
if (!validate_pathname($project) ||
!(-d "$projectroot/$project") ||
- !(-e "$projectroot/$project/HEAD") ||
+ !check_head_link("$projectroot/$project") ||
($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
($strict_export && !project_in_list($project))) {
undef $project;
@@ -326,7 +352,7 @@ sub evaluate_path_info {
# find which part of PATH_INFO is project
$project = $path_info;
$project =~ s,/+$,,;
- while ($project && !-e "$projectroot/$project/HEAD") {
+ while ($project && !check_head_link("$projectroot/$project")) {
$project =~ s,/*[^/]*$,,;
}
# validate project
@@ -878,8 +904,7 @@ sub git_get_projects_list {
my $subdir = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- if (-e "$projectroot/$subdir/HEAD" && (!$export_ok ||
- -e "$projectroot/$subdir/$export_ok")) {
+ if (check_export_ok("$projectroot/$subdir")) {
push @list, { path => $subdir };
$File::Find::prune = 1;
}
@@ -900,8 +925,7 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if (-e "$projectroot/$path/HEAD" && (!$export_ok ||
- -e "$projectroot/$path/$export_ok")) {
+ if (check_export_ok("$projectroot/$path")) {
my $pr = {
path => $path,
owner => to_utf8($owner),
@@ -1011,6 +1035,9 @@ sub parse_date {
$date{'hour_local'} = $hour;
$date{'minute_local'} = $min;
$date{'tz_local'} = $tz;
+ $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
+ 1900+$year, $mon+1, $mday,
+ $hour, $min, $sec, $tz);
return %date;
}
@@ -1416,8 +1443,17 @@ sub git_header_html {
<meta name="generator" content="gitweb/$version git/$git_version"/>
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
EOF
+# print out each stylesheet that exist
+ if (defined $stylesheet) {
+#provides backwards capability for those people who define style sheet in a config file
+ print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ } else {
+ foreach my $stylesheet (@stylesheets) {
+ next unless $stylesheet;
+ print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ }
+ }
if (defined $project) {
printf('<link rel="alternate" title="%s log" '.
'href="%s" type="application/rss+xml"/>'."\n",
@@ -1435,8 +1471,15 @@ EOF
}
print "</head>\n" .
- "<body>\n" .
- "<div class=\"page_header\">\n" .
+ "<body>\n";
+
+ if (-f $site_header) {
+ open (my $fd, $site_header);
+ print <$fd>;
+ close $fd;
+ }
+
+ print "<div class=\"page_header\">\n" .
$cgi->a({-href => esc_url($logo_url),
-title => $logo_label},
qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
@@ -1488,8 +1531,15 @@ sub git_footer_html {
print $cgi->a({-href => href(project=>undef, action=>"project_index"),
-class => "rss_logo"}, "TXT") . "\n";
}
- print "</div>\n" .
- "</body>\n" .
+ print "</div>\n" ;
+
+ if (-f $site_footer) {
+ open (my $fd, $site_footer);
+ print <$fd>;
+ close $fd;
+ }
+
+ print "</body>\n" .
"</html>";
}
@@ -2518,7 +2568,8 @@ sub git_blame2 {
if ($ftype !~ "blob") {
die_error("400 Bad Request", "Object is not a blob");
}
- open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base)
+ open ($fd, "-|", git_cmd(), "blame", '-p', '--',
+ $file_name, $hash_base)
or die_error(undef, "Open git-blame failed");
git_header_html();
my $formats_nav =
@@ -2542,25 +2593,52 @@ sub git_blame2 {
<table class="blame">
<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
HTML
- while (<$fd>) {
- /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
- my $full_rev = $1;
+ my %metainfo = ();
+ while (1) {
+ $_ = <$fd>;
+ last unless defined $_;
+ my ($full_rev, $orig_lineno, $lineno, $group_size) =
+ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+ if (!exists $metainfo{$full_rev}) {
+ $metainfo{$full_rev} = {};
+ }
+ my $meta = $metainfo{$full_rev};
+ while (<$fd>) {
+ last if (s/^\t//);
+ if (/^(\S+) (.*)$/) {
+ $meta->{$1} = $2;
+ }
+ }
+ my $data = $_;
my $rev = substr($full_rev, 0, 8);
- my $lineno = $2;
- my $data = $3;
-
- if (!defined $last_rev) {
- $last_rev = $full_rev;
- } elsif ($last_rev ne $full_rev) {
- $last_rev = $full_rev;
+ my $author = $meta->{'author'};
+ my %date = parse_date($meta->{'author-time'},
+ $meta->{'author-tz'});
+ my $date = $date{'iso-tz'};
+ if ($group_size) {
$current_color = ++$current_color % $num_colors;
}
print "<tr class=\"$rev_color[$current_color]\">\n";
- print "<td class=\"sha1\">" .
- $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)},
- esc_html($rev)) . "</td>\n";
- print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" .
- esc_html($lineno) . "</a></td>\n";
+ if ($group_size) {
+ print "<td class=\"sha1\"";
+ print " title=\"$author, $date\"";
+ print " rowspan=\"$group_size\"" if ($group_size > 1);
+ print ">";
+ print $cgi->a({-href => href(action=>"commit",
+ hash=>$full_rev,
+ file_name=>$file_name)},
+ esc_html($rev));
+ print "</td>\n";
+ }
+ my $blamed = href(action => 'blame',
+ file_name => $meta->{'filename'},
+ hash_base => $full_rev);
+ print "<td class=\"linenr\">";
+ print $cgi->a({ -href => "$blamed#l$orig_lineno",
+ -id => "l$lineno",
+ -class => "linenr" },
+ esc_html($lineno));
+ print "</td>";
print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
print "</tr>\n";
}