builtin-log.c 9.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/*
 * Builtin "git log" and related commands (show, whatchanged)
 *
 * (C) Copyright 2006 Linus Torvalds
 *		 2006 Junio Hamano
 */
#include "cache.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
#include "log-tree.h"
12
#include "builtin.h"
13

14 15 16
/* this is in builtin-diff.c */
void add_head(struct rev_info *revs);

17
static void cmd_log_init(int argc, const char **argv, char **envp,
18 19 20 21 22 23
		      struct rev_info *rev)
{
	rev->abbrev = DEFAULT_ABBREV;
	rev->commit_format = CMIT_FMT_DEFAULT;
	rev->verbose_header = 1;
	argc = setup_revisions(argc, argv, rev, "HEAD");
24 25
	if (rev->diffopt.pickaxe || rev->diffopt.filter)
		rev->always_show_header = 0;
26 27
	if (argc > 1)
		die("unrecognized argument: %s", argv[1]);
28 29 30 31 32
}

static int cmd_log_walk(struct rev_info *rev)
{
	struct commit *commit;
33 34 35 36 37 38 39

	prepare_revision_walk(rev);
	setup_pager();
	while ((commit = get_revision(rev)) != NULL) {
		log_tree_commit(rev, commit);
		free(commit->buffer);
		commit->buffer = NULL;
L
Linus Torvalds 已提交
40 41
		free_commit_list(commit->parents);
		commit->parents = NULL;
42 43 44 45 46 47 48 49
	}
	return 0;
}

int cmd_whatchanged(int argc, const char **argv, char **envp)
{
	struct rev_info rev;

50
	git_config(git_diff_config);
51 52 53
	init_revisions(&rev);
	rev.diff = 1;
	rev.diffopt.recursive = 1;
L
Linus Torvalds 已提交
54
	rev.simplify_history = 0;
55 56 57 58
	cmd_log_init(argc, argv, envp, &rev);
	if (!rev.diffopt.output_format)
		rev.diffopt.output_format = DIFF_FORMAT_RAW;
	return cmd_log_walk(&rev);
59 60 61 62 63 64
}

int cmd_show(int argc, const char **argv, char **envp)
{
	struct rev_info rev;

65
	git_config(git_diff_config);
66 67 68 69 70 71 72 73
	init_revisions(&rev);
	rev.diff = 1;
	rev.diffopt.recursive = 1;
	rev.combine_merges = 1;
	rev.dense_combined_merges = 1;
	rev.always_show_header = 1;
	rev.ignore_merges = 0;
	rev.no_walk = 1;
74 75
	cmd_log_init(argc, argv, envp, &rev);
	return cmd_log_walk(&rev);
76 77 78 79 80 81
}

int cmd_log(int argc, const char **argv, char **envp)
{
	struct rev_info rev;

82
	git_config(git_diff_config);
83 84
	init_revisions(&rev);
	rev.always_show_header = 1;
85 86
	cmd_log_init(argc, argv, envp, &rev);
	return cmd_log_walk(&rev);
87
}
88

89 90 91 92 93 94
static int istitlechar(char c)
{
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
		(c >= '0' && c <= '9') || c == '.' || c == '_';
}

95 96 97 98 99 100 101 102 103 104 105 106 107
static char *extra_headers = NULL;
static int extra_headers_size = 0;

static int git_format_config(const char *var, const char *value)
{
	if (!strcmp(var, "format.headers")) {
		int len = strlen(value);
		extra_headers_size += len + 1;
		extra_headers = realloc(extra_headers, extra_headers_size);
		extra_headers[extra_headers_size - len - 1] = 0;
		strcat(extra_headers, value);
		return 0;
	}
108
	return git_diff_config(var, value);
109 110 111
}


112
static FILE *realstdout = NULL;
113
static const char *output_directory = NULL;
114

115
static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
116 117 118
{
	char filename[1024];
	char *sol;
119
	int len = 0;
120

121
	if (output_directory) {
122
		strlcpy(filename, output_directory, 1010);
123 124 125 126
		len = strlen(filename);
		if (filename[len - 1] != '/')
			filename[len++] = '/';
	}
127

128
	sprintf(filename + len, "%04d", nr);
129 130 131 132 133 134 135 136
	len = strlen(filename);

	sol = strstr(commit->buffer, "\n\n");
	if (sol) {
		int j, space = 1;

		sol += 2;
		/* strip [PATCH] or [PATCH blabla] */
137
		if (!keep_subject && !strncmp(sol, "[PATCH", 6)) {
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
			char *eos = strchr(sol + 6, ']');
			if (eos) {
				while (isspace(*eos))
					eos++;
				sol = eos;
			}
		}

		for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) {
			if (istitlechar(sol[j])) {
				if (space) {
					filename[len++] = '-';
					space = 0;
				}
				filename[len++] = sol[j];
				if (sol[j] == '.')
					while (sol[j + 1] == '.')
						j++;
			} else
				space = 1;
		}
		while (filename[len - 1] == '.' || filename[len - 1] == '-')
			len--;
	}
	strcpy(filename + len, ".txt");
163
	fprintf(realstdout, "%s\n", filename);
164 165 166
	freopen(filename, "w", stdout);
}

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
static int get_patch_id(struct commit *commit, struct diff_options *options,
		unsigned char *sha1)
{
	diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
			"", options);
	diffcore_std(options);
	return diff_flush_patch_id(options, sha1);
}

static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
{
	struct rev_info check_rev;
	struct commit *commit;
	struct object *o1, *o2;
	unsigned flags1, flags2;
	unsigned char sha1[20];

	if (rev->pending.nr != 2)
		die("Need exactly one range.");

	o1 = rev->pending.objects[0].item;
	flags1 = o1->flags;
	o2 = rev->pending.objects[1].item;
	flags2 = o2->flags;

	if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
		die("Not a range.");

	diff_setup(options);
	options->recursive = 1;
	if (diff_setup_done(options) < 0)
		die("diff_setup_done failed");

	/* given a range a..b get all patch ids for b..a */
	init_revisions(&check_rev);
	o1->flags ^= UNINTERESTING;
	o2->flags ^= UNINTERESTING;
	add_pending_object(&check_rev, o1, "o1");
	add_pending_object(&check_rev, o2, "o2");
	prepare_revision_walk(&check_rev);

	while ((commit = get_revision(&check_rev)) != NULL) {
		/* ignore merges */
		if (commit->parents && commit->parents->next)
			continue;

		if (!get_patch_id(commit, options, sha1))
			created_object(sha1, xcalloc(1, sizeof(struct object)));
	}

	/* reset for next revision walk */
218 219 220 221
	clear_commit_marks((struct commit *)o1,
			SEEN | UNINTERESTING | SHOWN | ADDED);
	clear_commit_marks((struct commit *)o2,
			SEEN | UNINTERESTING | SHOWN | ADDED);
222 223 224 225
	o1->flags = flags1;
	o2->flags = flags2;
}

226 227 228 229 230
int cmd_format_patch(int argc, const char **argv, char **envp)
{
	struct commit *commit;
	struct commit **list = NULL;
	struct rev_info rev;
231
	int nr = 0, total, i, j;
232
	int use_stdout = 0;
233
	int numbered = 0;
234
	int start_number = -1;
235
	int keep_subject = 0;
236 237
	int ignore_if_in_upstream = 0;
	struct diff_options patch_id_opts;
J
Junio C Hamano 已提交
238
	char *add_signoff = NULL;
239

240
	git_config(git_format_config);
241 242 243 244 245 246
	init_revisions(&rev);
	rev.commit_format = CMIT_FMT_EMAIL;
	rev.verbose_header = 1;
	rev.diff = 1;
	rev.combine_merges = 0;
	rev.ignore_merges = 1;
247 248
	rev.diffopt.msg_sep = "";
	rev.diffopt.recursive = 1;
249

250 251
	rev.extra_headers = extra_headers;

252 253 254 255 256 257 258
	/*
	 * Parse the arguments before setup_revisions(), or something
	 * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is
	 * possibly a valid SHA1.
	 */
	for (i = 1, j = 1; i < argc; i++) {
		if (!strcmp(argv[i], "--stdout"))
259
			use_stdout = 1;
260 261 262
		else if (!strcmp(argv[i], "-n") ||
				!strcmp(argv[i], "--numbered"))
			numbered = 1;
263 264 265 266 267 268 269
		else if (!strncmp(argv[i], "--start-number=", 15))
			start_number = strtol(argv[i] + 15, NULL, 10);
		else if (!strcmp(argv[i], "--start-number")) {
			i++;
			if (i == argc)
				die("Need a number for --start-number");
			start_number = strtol(argv[i], NULL, 10);
J
Junio C Hamano 已提交
270 271
		}
		else if (!strcmp(argv[i], "-k") ||
272 273 274
				!strcmp(argv[i], "--keep-subject")) {
			keep_subject = 1;
			rev.total = -1;
J
Junio C Hamano 已提交
275
		}
276 277
		else if (!strcmp(argv[i], "--output-directory") ||
			 !strcmp(argv[i], "-o")) {
278
			i++;
279 280 281 282 283
			if (argc <= i)
				die("Which directory?");
			if (output_directory)
				die("Two output directories?");
			output_directory = argv[i];
284
		}
J
Junio C Hamano 已提交
285 286
		else if (!strcmp(argv[i], "--signoff") ||
			 !strcmp(argv[i], "-s")) {
E
Eric W. Biederman 已提交
287 288 289 290 291
			const char *committer;
			const char *endpos;
			setup_ident();
			committer = git_committer_info(1);
			endpos = strchr(committer, '>');
J
Junio C Hamano 已提交
292 293 294 295 296 297
			if (!endpos)
				die("bogos committer info %s\n", committer);
			add_signoff = xmalloc(endpos - committer + 2);
			memcpy(add_signoff, committer, endpos - committer + 1);
			add_signoff[endpos - committer + 1] = 0;
		}
298 299 300 301
		else if (!strcmp(argv[i], "--attach"))
			rev.mime_boundary = git_version_string;
		else if (!strncmp(argv[i], "--attach=", 9))
			rev.mime_boundary = argv[i] + 9;
302 303
		else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
			ignore_if_in_upstream = 1;
304
		else
305
			argv[j++] = argv[i];
306
	}
307 308
	argc = j;

309
	if (start_number < 0)
310
		start_number = 1;
311
	if (numbered && keep_subject)
312 313
		die ("-n and -k are mutually exclusive.");

314 315 316
	argc = setup_revisions(argc, argv, &rev, "HEAD");
	if (argc > 1)
		die ("unrecognized argument: %s", argv[1]);
317

318 319 320
	if (!rev.diffopt.output_format)
		rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;

321 322 323 324 325 326 327 328
	if (output_directory) {
		if (use_stdout)
			die("standard output, or directory, which one?");
		if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
			die("Could not create directory %s",
			    output_directory);
	}

329 330
	if (rev.pending.nr == 1) {
		rev.pending.objects[0].item->flags |= UNINTERESTING;
331 332 333
		add_head(&rev);
	}

334 335 336
	if (ignore_if_in_upstream)
		get_patch_ids(&rev, &patch_id_opts);

337 338 339
	if (!use_stdout)
		realstdout = fdopen(dup(1), "w");

340 341
	prepare_revision_walk(&rev);
	while ((commit = get_revision(&rev)) != NULL) {
342 343
		unsigned char sha1[20];

344 345 346
		/* ignore merges */
		if (commit->parents && commit->parents->next)
			continue;
347 348 349 350 351 352

		if (ignore_if_in_upstream &&
				!get_patch_id(commit, &patch_id_opts, sha1) &&
				lookup_object(sha1))
			continue;

353 354 355 356
		nr++;
		list = realloc(list, nr * sizeof(list[0]));
		list[nr - 1] = commit;
	}
357
	total = nr;
358
	if (numbered)
359
		rev.total = total + start_number - 1;
J
Junio C Hamano 已提交
360
	rev.add_signoff = add_signoff;
361 362 363
	while (0 <= --nr) {
		int shown;
		commit = list[nr];
364
		rev.nr = total - nr + (start_number - 1);
365
		if (!use_stdout)
366
			reopen_stdout(commit, rev.nr, keep_subject);
367 368 369
		shown = log_tree_commit(&rev, commit);
		free(commit->buffer);
		commit->buffer = NULL;
370 371 372 373 374 375 376 377 378

		/* We put one extra blank line between formatted
		 * patches and this flag is used by log-tree code
		 * to see if it needs to emit a LF before showing
		 * the log; when using one file per patch, we do
		 * not want the extra blank line.
		 */
		if (!use_stdout)
			rev.shown_one = 0;
379 380 381 382 383 384 385 386
		if (shown) {
			if (rev.mime_boundary)
				printf("\n--%s%s--\n\n\n",
				       mime_boundary_leader,
				       rev.mime_boundary);
			else
				printf("-- \n%s\n\n", git_version_string);
		}
387 388
		if (!use_stdout)
			fclose(stdout);
389 390 391 392 393
	}
	free(list);
	return 0;
}