builtin-log.c 9.4 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 18 19 20 21 22 23 24 25
static int cmd_log_wc(int argc, const char **argv, char **envp,
		      struct rev_info *rev)
{
	struct commit *commit;

	rev->abbrev = DEFAULT_ABBREV;
	rev->commit_format = CMIT_FMT_DEFAULT;
	rev->verbose_header = 1;
	argc = setup_revisions(argc, argv, rev, "HEAD");
26 27 28 29 30 31 32
	if (rev->always_show_header) {
		if (rev->diffopt.pickaxe || rev->diffopt.filter) {
			rev->always_show_header = 0;
			if (rev->diffopt.output_format == DIFF_FORMAT_RAW)
				rev->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
		}
	}
33 34 35 36 37 38 39 40 41 42

	if (argc > 1)
		die("unrecognized argument: %s", argv[1]);

	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 已提交
43 44
		free_commit_list(commit->parents);
		commit->parents = NULL;
45 46 47 48 49 50 51 52 53 54 55
	}
	return 0;
}

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

	init_revisions(&rev);
	rev.diff = 1;
	rev.diffopt.recursive = 1;
L
Linus Torvalds 已提交
56
	rev.simplify_history = 0;
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
	return cmd_log_wc(argc, argv, envp, &rev);
}

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

	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;
	return cmd_log_wc(argc, argv, envp, &rev);
}

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

	init_revisions(&rev);
	rev.always_show_header = 1;
	rev.diffopt.recursive = 1;
	return cmd_log_wc(argc, argv, envp, &rev);
}
84

85 86 87 88 89 90
static int istitlechar(char c)
{
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
		(c >= '0' && c <= '9') || c == '.' || c == '_';
}

91 92 93 94 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;
	}
	return git_default_config(var, value);
}


108
static FILE *realstdout = NULL;
109
static const char *output_directory = NULL;
110

111
static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
112 113 114
{
	char filename[1024];
	char *sol;
115
	int len = 0;
116

117
	if (output_directory) {
118
		strlcpy(filename, output_directory, 1010);
119 120 121 122
		len = strlen(filename);
		if (filename[len - 1] != '/')
			filename[len++] = '/';
	}
123

124
	sprintf(filename + len, "%04d", nr);
125 126 127 128 129 130 131 132
	len = strlen(filename);

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

		sol += 2;
		/* strip [PATCH] or [PATCH blabla] */
133
		if (!keep_subject && !strncmp(sol, "[PATCH", 6)) {
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
			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");
159
	fprintf(realstdout, "%s\n", filename);
160 161 162
	freopen(filename, "w", stdout);
}

163 164 165 166 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 218 219 220 221 222 223 224 225 226 227
static void reset_all_objects_flags()
{
	int i;

	for (i = 0; i < obj_allocs; i++)
		if (objs[i])
			objs[i]->flags = 0;
}

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 */
	reset_all_objects_flags();
	o1->flags = flags1;
	o2->flags = flags2;
}

228 229 230 231 232
int cmd_format_patch(int argc, const char **argv, char **envp)
{
	struct commit *commit;
	struct commit **list = NULL;
	struct rev_info rev;
233
	int nr = 0, total, i, j;
234
	int use_stdout = 0;
235
	int numbered = 0;
236
	int start_number = -1;
237
	int keep_subject = 0;
238 239
	int ignore_if_in_upstream = 0;
	struct diff_options patch_id_opts;
J
Junio C Hamano 已提交
240
	char *add_signoff = NULL;
241 242 243 244 245 246 247 248 249 250 251

	init_revisions(&rev);
	rev.commit_format = CMIT_FMT_EMAIL;
	rev.verbose_header = 1;
	rev.diff = 1;
	rev.diffopt.with_raw = 0;
	rev.diffopt.with_stat = 1;
	rev.combine_merges = 0;
	rev.ignore_merges = 1;
	rev.diffopt.output_format = DIFF_FORMAT_PATCH;

252 253 254
	git_config(git_format_config);
	rev.extra_headers = extra_headers;

255 256 257 258 259 260 261
	/*
	 * 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"))
262
			use_stdout = 1;
263 264 265
		else if (!strcmp(argv[i], "-n") ||
				!strcmp(argv[i], "--numbered"))
			numbered = 1;
266 267 268 269 270 271 272
		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 已提交
273 274
		}
		else if (!strcmp(argv[i], "-k") ||
275 276 277
				!strcmp(argv[i], "--keep-subject")) {
			keep_subject = 1;
			rev.total = -1;
J
Junio C Hamano 已提交
278
		}
279 280
		else if (!strcmp(argv[i], "--output-directory") ||
			 !strcmp(argv[i], "-o")) {
281
			i++;
282 283 284 285 286
			if (argc <= i)
				die("Which directory?");
			if (output_directory)
				die("Two output directories?");
			output_directory = argv[i];
287
		}
J
Junio C Hamano 已提交
288 289
		else if (!strcmp(argv[i], "--signoff") ||
			 !strcmp(argv[i], "-s")) {
E
Eric W. Biederman 已提交
290 291 292 293 294
			const char *committer;
			const char *endpos;
			setup_ident();
			committer = git_committer_info(1);
			endpos = strchr(committer, '>');
J
Junio C Hamano 已提交
295 296 297 298 299 300
			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;
		}
301 302 303 304
		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;
305 306
		else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
			ignore_if_in_upstream = 1;
307
		else
308
			argv[j++] = argv[i];
309
	}
310 311
	argc = j;

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

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

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;
}