blame.c 20.0 KB
Newer Older
F
Fredrik Kuivinen 已提交
1 2 3 4
/*
 * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
 */

5
#include <assert.h>
F
Fredrik Kuivinen 已提交
6 7
#include <time.h>
#include <sys/time.h>
8
#include <math.h>
9 10 11 12 13 14 15 16

#include "cache.h"
#include "refs.h"
#include "tag.h"
#include "commit.h"
#include "tree.h"
#include "blob.h"
#include "diff.h"
17
#include "diffcore.h"
F
Fredrik Kuivinen 已提交
18
#include "revision.h"
J
Junio C Hamano 已提交
19
#include "xdiff-interface.h"
20 21 22

#define DEBUG 0

23 24 25 26 27 28 29 30 31
static const char blame_usage[] = "[-c] [-l] [--] file [commit]\n"
	"  -c, --compability Use the same output mode as git-annotate (Default: off)\n"
	"  -l, --long        Show long commit SHA1 (Default: off)\n"
	"  -h, --help        This message";

static struct commit **blame_lines;
static int num_blame_lines;
static char* blame_contents;
static int blame_len;
32

F
Fredrik Kuivinen 已提交
33 34 35 36 37 38
struct util_info {
	int *line_map;
	unsigned char sha1[20];	/* blob sha, not commit! */
	char *buf;
	unsigned long size;
	int num_lines;
39 40 41
	const char* pathname;

	void* topo_data;
42 43
};

F
Fredrik Kuivinen 已提交
44 45 46
struct chunk {
	int off1, len1;	// ---
	int off2, len2;	// +++
47 48
};

F
Fredrik Kuivinen 已提交
49 50 51
struct patch {
	struct chunk *chunks;
	int num;
52 53
};

F
Fredrik Kuivinen 已提交
54
static void get_blob(struct commit *commit);
55

F
Fredrik Kuivinen 已提交
56 57 58 59
/* Only used for statistics */
static int num_get_patch = 0;
static int num_commits = 0;
static int patch_time = 0;
60

J
Junio C Hamano 已提交
61 62
struct blame_diff_state {
	struct xdiff_emit_state xm;
F
Fredrik Kuivinen 已提交
63
	struct patch *ret;
J
Junio C Hamano 已提交
64
};
F
Fredrik Kuivinen 已提交
65

J
Junio C Hamano 已提交
66 67 68 69
static void process_u0_diff(void *state_, char *line, unsigned long len)
{
	struct blame_diff_state *state = state_;
	struct chunk *chunk;
F
Fredrik Kuivinen 已提交
70

J
Junio C Hamano 已提交
71 72
	if (len < 4 || line[0] != '@' || line[1] != '@')
		return;
F
Fredrik Kuivinen 已提交
73

J
Junio C Hamano 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
	if (DEBUG)
		printf("chunk line: %.*s", (int)len, line);
	state->ret->num++;
	state->ret->chunks = xrealloc(state->ret->chunks,
				      sizeof(struct chunk) * state->ret->num);
	chunk = &state->ret->chunks[state->ret->num - 1];

	assert(!strncmp(line, "@@ -", 4));

	if (parse_hunk_header(line, len,
			      &chunk->off1, &chunk->len1,
			      &chunk->off2, &chunk->len2)) {
		state->ret->num--;
		return;
	}
F
Fredrik Kuivinen 已提交
89

J
Junio C Hamano 已提交
90 91 92 93
	if (chunk->len1 == 0)
		chunk->off1++;
	if (chunk->len2 == 0)
		chunk->off2++;
F
Fredrik Kuivinen 已提交
94

J
Junio C Hamano 已提交
95 96 97 98
	if (chunk->off1 > 0)
		chunk->off1--;
	if (chunk->off2 > 0)
		chunk->off2--;
F
Fredrik Kuivinen 已提交
99

J
Junio C Hamano 已提交
100 101 102
	assert(chunk->off1 >= 0);
	assert(chunk->off2 >= 0);
}
F
Fredrik Kuivinen 已提交
103

J
Junio C Hamano 已提交
104 105 106 107 108 109 110 111 112 113
static struct patch *get_patch(struct commit *commit, struct commit *other)
{
	struct blame_diff_state state;
	xpparam_t xpp;
	xdemitconf_t xecfg;
	mmfile_t file_c, file_o;
	xdemitcb_t ecb;
	struct util_info *info_c = (struct util_info *)commit->object.util;
	struct util_info *info_o = (struct util_info *)other->object.util;
	struct timeval tv_start, tv_end;
F
Fredrik Kuivinen 已提交
114

J
Junio C Hamano 已提交
115 116 117
	get_blob(commit);
	file_c.ptr = info_c->buf;
	file_c.size = info_c->size;
F
Fredrik Kuivinen 已提交
118

J
Junio C Hamano 已提交
119 120 121
	get_blob(other);
	file_o.ptr = info_o->buf;
	file_o.size = info_o->size;
F
Fredrik Kuivinen 已提交
122

J
Junio C Hamano 已提交
123
	gettimeofday(&tv_start, NULL);
F
Fredrik Kuivinen 已提交
124

J
Junio C Hamano 已提交
125 126 127 128 129 130 131 132 133 134
	xpp.flags = XDF_NEED_MINIMAL;
	xecfg.ctxlen = 0;
	xecfg.flags = 0;
	ecb.outf = xdiff_outf;
	ecb.priv = &state;
	memset(&state, 0, sizeof(state));
	state.xm.consume = process_u0_diff;
	state.ret = xmalloc(sizeof(struct patch));
	state.ret->chunks = NULL;
	state.ret->num = 0;
F
Fredrik Kuivinen 已提交
135

J
Junio C Hamano 已提交
136
	xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb);
F
Fredrik Kuivinen 已提交
137 138 139 140 141 142

	gettimeofday(&tv_end, NULL);
	patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
		tv_end.tv_usec - tv_start.tv_usec;

	num_get_patch++;
J
Junio C Hamano 已提交
143
	return state.ret;
144 145
}

F
Fredrik Kuivinen 已提交
146
static void free_patch(struct patch *p)
147
{
F
Fredrik Kuivinen 已提交
148 149
	free(p->chunks);
	free(p);
150 151
}

F
Fredrik Kuivinen 已提交
152 153 154
static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
				  int baselen, const char *pathname,
				  unsigned mode, int stage);
155 156

static unsigned char blob_sha1[20];
157
static const char* blame_file;
F
Fredrik Kuivinen 已提交
158 159
static int get_blob_sha1(struct tree *t, const char *pathname,
			 unsigned char *sha1)
160
{
F
Fredrik Kuivinen 已提交
161 162
	int i;
	const char *pathspec[2];
163
	blame_file = pathname;
F
Fredrik Kuivinen 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
	pathspec[0] = pathname;
	pathspec[1] = NULL;
	memset(blob_sha1, 0, sizeof(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)
		return -1;

	memcpy(sha1, blob_sha1, 20);
	return 0;
179 180
}

F
Fredrik Kuivinen 已提交
181 182 183
static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
				  int baselen, const char *pathname,
				  unsigned mode, int stage)
184
{
F
Fredrik Kuivinen 已提交
185 186
	if (S_ISDIR(mode))
		return READ_TREE_RECURSIVE;
187

188 189 190 191
	if (strncmp(blame_file, base, baselen) ||
	    strcmp(blame_file + baselen, pathname))
		return -1;

F
Fredrik Kuivinen 已提交
192 193
	memcpy(blob_sha1, sha1, 20);
	return -1;
194 195
}

F
Fredrik Kuivinen 已提交
196
static void get_blob(struct commit *commit)
197
{
F
Fredrik Kuivinen 已提交
198 199
	struct util_info *info = commit->object.util;
	char type[20];
200

F
Fredrik Kuivinen 已提交
201 202
	if (info->buf)
		return;
203

F
Fredrik Kuivinen 已提交
204 205
	info->buf = read_sha1_file(info->sha1, type, &info->size);

206
	assert(!strcmp(type, blob_type));
207 208
}

F
Fredrik Kuivinen 已提交
209 210
/* For debugging only */
static void print_patch(struct patch *p)
211
{
F
Fredrik Kuivinen 已提交
212 213 214 215 216 217
	int i;
	printf("Num chunks: %d\n", p->num);
	for (i = 0; i < p->num; i++) {
		printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
		       p->chunks[i].off2, p->chunks[i].len2);
	}
218 219
}

J
Junio C Hamano 已提交
220
#if DEBUG
F
Fredrik Kuivinen 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
/* For debugging only */
static void print_map(struct commit *cmit, struct commit *other)
{
	struct util_info *util = cmit->object.util;
	struct util_info *util2 = other->object.util;

	int i;
	int max =
	    util->num_lines >
	    util2->num_lines ? util->num_lines : util2->num_lines;
	int num;

	for (i = 0; i < max; i++) {
		printf("i: %d ", i);
		num = -1;

		if (i < util->num_lines) {
			num = util->line_map[i];
			printf("%d\t", num);
		} else
			printf("\t");

		if (i < util2->num_lines) {
			int num2 = util2->line_map[i];
			printf("%d\t", num2);
			if (num != -1 && num2 != num)
				printf("---");
		} else
			printf("\t");

		printf("\n");
	}
}
J
Junio C Hamano 已提交
254
#endif
255 256

// p is a patch from commit to other.
F
Fredrik Kuivinen 已提交
257 258
static void fill_line_map(struct commit *commit, struct commit *other,
			  struct patch *p)
259
{
F
Fredrik Kuivinen 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
	struct util_info *util = commit->object.util;
	struct util_info *util2 = other->object.util;
	int *map = util->line_map;
	int *map2 = util2->line_map;
	int cur_chunk = 0;
	int i1, i2;

	if (p->num && DEBUG)
		print_patch(p);

	if (DEBUG)
		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;
		if (cur_chunk < p->num)
			chunk = &p->chunks[cur_chunk];

		if (chunk && chunk->off1 == i1) {
			if (DEBUG && i2 != chunk->off2)
				printf("i2: %d off2: %d\n", i2, chunk->off2);

			assert(i2 == chunk->off2);

			i1--;
			i2--;
			if (chunk->len1 > 0)
				i1 += chunk->len1;

			if (chunk->len2 > 0)
				i2 += chunk->len2;

			cur_chunk++;
		} else {
			if (i2 >= util2->num_lines)
				break;

			if (map[i1] != map2[i2] && map[i1] != -1) {
				if (DEBUG)
					printf("map: i1: %d %d %p i2: %d %d %p\n",
					       i1, map[i1],
					       i1 != -1 ? blame_lines[map[i1]] : NULL,
					       i2, map2[i2],
					       i2 != -1 ? blame_lines[map2[i2]] : NULL);
				if (map2[i2] != -1 &&
				    blame_lines[map[i1]] &&
				    !blame_lines[map2[i2]])
					map[i1] = map2[i2];
			}

			if (map[i1] == -1 && map2[i2] != -1)
				map[i1] = map2[i2];
		}

		if (DEBUG > 1)
			printf("l1: %d l2: %d i1: %d i2: %d\n",
			       map[i1], map2[i2], i1, i2);
	}
319 320
}

F
Fredrik Kuivinen 已提交
321
static int map_line(struct commit *commit, int line)
322
{
F
Fredrik Kuivinen 已提交
323 324 325
	struct util_info *info = commit->object.util;
	assert(line >= 0 && line < info->num_lines);
	return info->line_map[line];
326 327
}

328
static struct util_info* get_util(struct commit *commit)
329
{
330 331 332 333
	struct util_info *util = commit->object.util;

	if (util)
		return util;
F
Fredrik Kuivinen 已提交
334 335

	util = xmalloc(sizeof(struct util_info));
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
	util->buf = NULL;
	util->size = 0;
	util->line_map = NULL;
	util->num_lines = -1;
	util->pathname = NULL;
	commit->object.util = util;
	return util;
}

static int fill_util_info(struct commit *commit)
{
	struct util_info *util = commit->object.util;

	assert(util);
	assert(util->pathname);
F
Fredrik Kuivinen 已提交
351

352
	if (get_blob_sha1(commit->tree, util->pathname, util->sha1))
F
Fredrik Kuivinen 已提交
353
		return 1;
354
	else
F
Fredrik Kuivinen 已提交
355
		return 0;
356 357
}

F
Fredrik Kuivinen 已提交
358
static void alloc_line_map(struct commit *commit)
359
{
F
Fredrik Kuivinen 已提交
360 361
	struct util_info *util = commit->object.util;
	int i;
362

F
Fredrik Kuivinen 已提交
363 364
	if (util->line_map)
		return;
365

F
Fredrik Kuivinen 已提交
366
	get_blob(commit);
367

F
Fredrik Kuivinen 已提交
368 369 370 371 372 373 374
	util->num_lines = 0;
	for (i = 0; i < util->size; i++) {
		if (util->buf[i] == '\n')
			util->num_lines++;
	}
	if(util->buf[util->size - 1] != '\n')
		util->num_lines++;
375

F
Fredrik Kuivinen 已提交
376
	util->line_map = xmalloc(sizeof(int) * util->num_lines);
377

F
Fredrik Kuivinen 已提交
378 379
	for (i = 0; i < util->num_lines; i++)
		util->line_map[i] = -1;
380 381
}

F
Fredrik Kuivinen 已提交
382
static void init_first_commit(struct commit* commit, const char* filename)
383
{
384
	struct util_info* util = commit->object.util;
F
Fredrik Kuivinen 已提交
385
	int i;
386

387 388
	util->pathname = filename;
	if (fill_util_info(commit))
F
Fredrik Kuivinen 已提交
389
		die("fill_util_info failed");
390

F
Fredrik Kuivinen 已提交
391
	alloc_line_map(commit);
392

F
Fredrik Kuivinen 已提交
393
	util = commit->object.util;
394

395
	for (i = 0; i < util->num_lines; i++)
F
Fredrik Kuivinen 已提交
396 397
		util->line_map[i] = i;
}
398 399


F
Fredrik Kuivinen 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
static void process_commits(struct rev_info *rev, const char *path,
			    struct commit** initial)
{
	int i;
	struct util_info* util;
	int lines_left;
	int *blame_p;
	int *new_lines;
	int new_lines_len;

	struct commit* commit = get_revision(rev);
	assert(commit);
	init_first_commit(commit, path);

	util = commit->object.util;
	num_blame_lines = util->num_lines;
	blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
417 418 419
	blame_contents = util->buf;
	blame_len = util->size;

F
Fredrik Kuivinen 已提交
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
	for (i = 0; i < num_blame_lines; i++)
		blame_lines[i] = NULL;

	lines_left = num_blame_lines;
	blame_p = xmalloc(sizeof(int) * num_blame_lines);
	new_lines = xmalloc(sizeof(int) * num_blame_lines);
	do {
		struct commit_list *parents;
		int num_parents;
		struct util_info *util;

		if (DEBUG)
			printf("\nProcessing commit: %d %s\n", num_commits,
			       sha1_to_hex(commit->object.sha1));

		if (lines_left == 0)
			return;

		num_commits++;
		memset(blame_p, 0, sizeof(int) * num_blame_lines);
		new_lines_len = 0;
		num_parents = 0;
		for (parents = commit->parents;
		     parents != NULL; parents = parents->next)
			num_parents++;

		if(num_parents == 0)
			*initial = commit;

449
		if (fill_util_info(commit))
F
Fredrik Kuivinen 已提交
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
			continue;

		alloc_line_map(commit);
		util = commit->object.util;

		for (parents = commit->parents;
		     parents != NULL; parents = parents->next) {
			struct commit *parent = parents->item;
			struct patch *patch;

			if (parse_commit(parent) < 0)
				die("parse_commit error");

			if (DEBUG)
				printf("parent: %s\n",
				       sha1_to_hex(parent->object.sha1));

467
			if (fill_util_info(parent)) {
F
Fredrik Kuivinen 已提交
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
				num_parents--;
				continue;
			}

			patch = get_patch(parent, commit);
                        alloc_line_map(parent);
                        fill_line_map(parent, commit, patch);

                        for (i = 0; i < patch->num; i++) {
                            int l;
                            for (l = 0; l < patch->chunks[i].len2; l++) {
                                int mapped_line =
                                    map_line(commit, patch->chunks[i].off2 + l);
                                if (mapped_line != -1) {
                                    blame_p[mapped_line]++;
                                    if (blame_p[mapped_line] == num_parents)
                                        new_lines[new_lines_len++] = mapped_line;
                                }
                            }
			}
                        free_patch(patch);
		}

		if (DEBUG)
			printf("parents: %d\n", num_parents);

		for (i = 0; i < new_lines_len; i++) {
			int mapped_line = new_lines[i];
			if (blame_lines[mapped_line] == NULL) {
				blame_lines[mapped_line] = commit;
				lines_left--;
				if (DEBUG)
					printf("blame: mapped: %d i: %d\n",
					       mapped_line, i);
			}
		}
	} while ((commit = get_revision(rev)) != NULL);
505 506
}

507 508 509 510

static int compare_tree_path(struct rev_info* revs,
			     struct commit* c1, struct commit* c2)
{
511
	int ret;
512 513 514 515 516
	const char* paths[2];
	struct util_info* util = c2->object.util;
	paths[0] = util->pathname;
	paths[1] = NULL;

517
	diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
F
Fredrik Kuivinen 已提交
518
			      &revs->pruning);
519
	ret = rev_compare_tree(revs, c1->tree, c2->tree);
F
Fredrik Kuivinen 已提交
520
	diff_tree_release_paths(&revs->pruning);
521
	return ret;
522 523 524 525 526 527
}


static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
				   const char* path)
{
528
	int ret;
529 530 531 532
	const char* paths[2];
	paths[0] = path;
	paths[1] = NULL;

533
	diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
F
Fredrik Kuivinen 已提交
534
			      &revs->pruning);
535
	ret = rev_same_tree_as_empty(revs, t1);
F
Fredrik Kuivinen 已提交
536
	diff_tree_release_paths(&revs->pruning);
537
	return ret;
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
}

static const char* find_rename(struct commit* commit, struct commit* parent)
{
	struct util_info* cutil = commit->object.util;
	struct diff_options diff_opts;
	const char *paths[1];
	int i;

	if (DEBUG) {
		printf("find_rename commit: %s ",
		       sha1_to_hex(commit->object.sha1));
		puts(sha1_to_hex(parent->object.sha1));
	}

	diff_setup(&diff_opts);
	diff_opts.recursive = 1;
	diff_opts.detect_rename = DIFF_DETECT_RENAME;
	paths[0] = NULL;
557
	diff_tree_setup_paths(paths, &diff_opts);
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
	if (diff_setup_done(&diff_opts) < 0)
		die("diff_setup_done failed");

	diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,
		       "", &diff_opts);
	diffcore_std(&diff_opts);

	for (i = 0; i < diff_queued_diff.nr; i++) {
		struct diff_filepair *p = diff_queued_diff.queue[i];

		if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
			if (DEBUG)
				printf("rename %s -> %s\n", p->one->path, p->two->path);
			return p->two->path;
		}
	}

	return 0;
}

static void simplify_commit(struct rev_info *revs, struct commit *commit)
{
	struct commit_list **pp, *parent;

	if (!commit->tree)
		return;

	if (!commit->parents) {
		struct util_info* util = commit->object.util;
		if (!same_tree_as_empty_path(revs, commit->tree,
					     util->pathname))
			commit->object.flags |= TREECHANGE;
		return;
	}

	pp = &commit->parents;
	while ((parent = *pp) != NULL) {
		struct commit *p = parent->item;

		if (p->object.flags & UNINTERESTING) {
			pp = &parent->next;
			continue;
		}

		parse_commit(p);
		switch (compare_tree_path(revs, p, commit)) {
		case REV_TREE_SAME:
			parent->next = NULL;
			commit->parents = parent;
			get_util(p)->pathname = get_util(commit)->pathname;
			return;

		case REV_TREE_NEW:
		{

			struct util_info* util = commit->object.util;
			if (revs->remove_empty_trees &&
			    same_tree_as_empty_path(revs, p->tree,
						    util->pathname)) {
				const char* new_name = find_rename(commit, p);
				if (new_name) {
					struct util_info* putil = get_util(p);
					if (!putil->pathname)
						putil->pathname = strdup(new_name);
				} else {
					*pp = parent->next;
					continue;
				}
			}
		}

		/* fallthrough */
		case REV_TREE_DIFFERENT:
			pp = &parent->next;
			if (!get_util(p)->pathname)
				get_util(p)->pathname =
					get_util(commit)->pathname;
			continue;
		}
		die("bad tree compare for commit %s",
		    sha1_to_hex(commit->object.sha1));
	}
	commit->object.flags |= TREECHANGE;
}


644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
struct commit_info
{
	char* author;
	char* author_mail;
	unsigned long author_time;
	char* author_tz;
};

static void get_commit_info(struct commit* commit, struct commit_info* ret)
{
	int len;
	char* tmp;
	static char author_buf[1024];

	tmp = strstr(commit->buffer, "\nauthor ") + 8;
J
Junio C Hamano 已提交
659
	len = strchr(tmp, '\n') - tmp;
660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
	ret->author = author_buf;
	memcpy(ret->author, tmp, len);

	tmp = ret->author;
	tmp += len;
	*tmp = 0;
	while(*tmp != ' ')
		tmp--;
	ret->author_tz = tmp+1;

	*tmp = 0;
	while(*tmp != ' ')
		tmp--;
	ret->author_time = strtoul(tmp, NULL, 10);

	*tmp = 0;
	while(*tmp != ' ')
		tmp--;
	ret->author_mail = tmp + 1;

	*tmp = 0;
}

683
static const char* format_time(unsigned long time, const char* tz_str)
684 685 686
{
	static char time_buf[128];
	time_t t = time;
687 688 689 690 691 692 693 694 695 696 697 698
	int minutes, tz;
	struct tm *tm;

	tz = atoi(tz_str);
	minutes = tz < 0 ? -tz : tz;
	minutes = (minutes / 100)*60 + (minutes % 100);
	minutes = tz < 0 ? -minutes : minutes;
	t = time + minutes * 60;
	tm = gmtime(&t);

	strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
	strcat(time_buf, tz_str);
699 700 701
	return time_buf;
}

702 703 704 705 706 707 708 709 710 711 712 713
static void topo_setter(struct commit* c, void* data)
{
	struct util_info* util = c->object.util;
	util->topo_data = data;
}

static void* topo_getter(struct commit* c)
{
	struct util_info* util = c->object.util;
	return util->topo_data;
}

J
Junio C Hamano 已提交
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
static int read_ancestry(const char *graft_file,
			 unsigned char **start_sha1)
{
	FILE *fp = fopen(graft_file, "r");
	char buf[1024];
	if (!fp)
		return -1;
	while (fgets(buf, sizeof(buf), fp)) {
		/* The format is just "Commit Parent1 Parent2 ...\n" */
		int len = strlen(buf);
		struct commit_graft *graft = read_graft_line(buf, len);
		register_commit_graft(graft, 0);
		if (!*start_sha1)
			*start_sha1 = graft->sha1;
	}
	fclose(fp);
	return 0;
}

733 734
int main(int argc, const char **argv)
{
F
Fredrik Kuivinen 已提交
735 736
	int i;
	struct commit *initial = NULL;
J
Junio C Hamano 已提交
737
	unsigned char sha1[20], *sha1_p = NULL;
738 739 740 741 742 743

	const char *filename = NULL, *commit = NULL;
	char filename_buf[256];
	int sha1_len = 8;
	int compability = 0;
	int options = 1;
744
	struct commit* start_commit;
745

F
Fredrik Kuivinen 已提交
746 747 748
	const char* args[10];
	struct rev_info rev;

749 750 751
	struct commit_info ci;
	const char *buf;
	int max_digits;
752
	int longest_file, longest_author;
F
Fredrik Kuivinen 已提交
753
	int found_rename;
F
Fredrik Kuivinen 已提交
754

755
	const char* prefix = setup_git_directory();
756
	git_config(git_default_config);
F
Fredrik Kuivinen 已提交
757

758 759 760 761 762 763 764
	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")) {
765
				sha1_len = 40;
766 767 768 769 770
				continue;
			} else if(!strcmp(argv[i], "-c") ||
				  !strcmp(argv[i], "--compability")) {
				compability = 1;
				continue;
J
Junio C Hamano 已提交
771 772 773 774 775 776 777 778
			} else if(!strcmp(argv[i], "-S")) {
				if (i + 1 < argc &&
				    !read_ancestry(argv[i + 1], &sha1_p)) {
					compability = 1;
					i++;
					continue;
				}
				usage(blame_usage);
779 780 781 782 783 784 785 786
			} else if(!strcmp(argv[i], "--")) {
				options = 0;
				continue;
			} else if(argv[i][0] == '-')
				usage(blame_usage);
			else
				options = 0;
		}
F
Fredrik Kuivinen 已提交
787

788 789 790 791 792 793 794 795 796 797 798 799
		if(!options) {
			if(!filename)
				filename = argv[i];
			else if(!commit)
				commit = argv[i];
			else
				usage(blame_usage);
		}
	}

	if(!filename)
		usage(blame_usage);
J
Junio C Hamano 已提交
800 801 802
	if (commit && sha1_p)
		usage(blame_usage);
	else if(!commit)
803 804 805 806 807 808 809
		commit = "HEAD";

	if(prefix)
		sprintf(filename_buf, "%s%s", prefix, filename);
	else
		strcpy(filename_buf, filename);
	filename = filename_buf;
F
Fredrik Kuivinen 已提交
810

J
Junio C Hamano 已提交
811 812 813 814 815 816
	if (!sha1_p) {
		if (get_sha1(commit, sha1))
			die("get_sha1 failed, commit '%s' not found", commit);
		sha1_p = sha1;
	}
	start_commit = lookup_commit_reference(sha1_p);
817 818 819 820
	get_util(start_commit)->pathname = filename;
	if (fill_util_info(start_commit)) {
		printf("%s not found in %s\n", filename, commit);
		return 1;
F
Fredrik Kuivinen 已提交
821 822 823
	}


824 825 826 827 828 829
	init_revisions(&rev);
	rev.remove_empty_trees = 1;
	rev.topo_order = 1;
	rev.prune_fn = simplify_commit;
	rev.topo_setter = topo_setter;
	rev.topo_getter = topo_getter;
830
	rev.parents = 1;
831 832 833 834 835 836
	rev.limited = 1;

	commit_list_insert(start_commit, &rev.commits);

	args[0] = filename;
	args[1] = NULL;
F
Fredrik Kuivinen 已提交
837
	diff_tree_setup_paths(args, &rev.pruning);
F
Fredrik Kuivinen 已提交
838 839 840
	prepare_revision_walk(&rev);
	process_commits(&rev, filename, &initial);

841
	buf = blame_contents;
842 843 844
	for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
		i *= 10;

F
Fredrik Kuivinen 已提交
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
	longest_file = 0;
	longest_author = 0;
	found_rename = 0;
	for (i = 0; i < num_blame_lines; i++) {
		struct commit *c = blame_lines[i];
		struct util_info* u;
		if (!c)
			c = initial;
		u = c->object.util;

		if (!found_rename && strcmp(filename, u->pathname))
			found_rename = 1;
		if (longest_file < strlen(u->pathname))
			longest_file = strlen(u->pathname);
		get_commit_info(c, &ci);
		if (longest_author < strlen(ci.author))
			longest_author = strlen(ci.author);
	}

F
Fredrik Kuivinen 已提交
864 865
	for (i = 0; i < num_blame_lines; i++) {
		struct commit *c = blame_lines[i];
866 867
		struct util_info* u;

F
Fredrik Kuivinen 已提交
868 869 870
		if (!c)
			c = initial;

871
		u = c->object.util;
872 873
		get_commit_info(c, &ci);
		fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
F
Fredrik Kuivinen 已提交
874
		if(compability) {
875 876
			printf("\t(%10s\t%10s\t%d)", ci.author,
			       format_time(ci.author_time, ci.author_tz), i+1);
F
Fredrik Kuivinen 已提交
877 878 879 880 881 882 883
		} 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),
884
			       max_digits, i+1);
F
Fredrik Kuivinen 已提交
885
		}
886 887 888 889 890 891 892

		if(i == num_blame_lines - 1) {
			fwrite(buf, blame_len - (buf - blame_contents),
			       1, stdout);
			if(blame_contents[blame_len-1] != '\n')
				putc('\n', stdout);
		} else {
J
Junio C Hamano 已提交
893
			char* next_buf = strchr(buf, '\n') + 1;
894 895 896
			fwrite(buf, next_buf - buf, 1, stdout);
			buf = next_buf;
		}
F
Fredrik Kuivinen 已提交
897 898 899 900 901 902 903 904 905 906
	}

	if (DEBUG) {
		printf("num get patch: %d\n", num_get_patch);
		printf("num commits: %d\n", num_commits);
		printf("patch time: %f\n", patch_time / 1000000.0);
		printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
	}

	return 0;
907
}