ident.c 15.5 KB
Newer Older
1 2 3 4 5 6 7 8
/*
 * ident.c
 *
 * create git identifier lines of the form "name <email> date"
 *
 * Copyright (C) 2005 Linus Torvalds
 */
#include "cache.h"
9
#include "config.h"
10

11 12
static struct strbuf git_default_name = STRBUF_INIT;
static struct strbuf git_default_email = STRBUF_INIT;
13
static struct strbuf git_default_date = STRBUF_INIT;
14 15 16 17
static struct strbuf git_author_name = STRBUF_INIT;
static struct strbuf git_author_email = STRBUF_INIT;
static struct strbuf git_committer_name = STRBUF_INIT;
static struct strbuf git_committer_email = STRBUF_INIT;
18
static int default_email_is_bogus;
19
static int default_name_is_bogus;
20

21 22
static int ident_use_config_only;

23 24 25
#define IDENT_NAME_GIVEN 01
#define IDENT_MAIL_GIVEN 02
#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
26 27
static int committer_ident_explicitly_given;
static int author_ident_explicitly_given;
28
static int ident_config_given;
29

30 31 32 33 34 35
#ifdef NO_GECOS_IN_PWENT
#define get_gecos(ignored) "&"
#else
#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
#endif

36
static struct passwd *xgetpwuid_self(int *is_bogus)
37 38 39 40 41
{
	struct passwd *pw;

	errno = 0;
	pw = getpwuid(getuid());
42 43 44 45 46 47 48 49 50 51
	if (!pw) {
		static struct passwd fallback;
		fallback.pw_name = "unknown";
#ifndef NO_GECOS_IN_PWENT
		fallback.pw_gecos = "Unknown";
#endif
		pw = &fallback;
		if (is_bogus)
			*is_bogus = 1;
	}
52 53 54
	return pw;
}

55
static void copy_gecos(const struct passwd *w, struct strbuf *name)
56
{
57
	char *src;
58 59 60 61 62

	/* Traditionally GECOS field had office phone numbers etc, separated
	 * with commas.  Also & stands for capitalized form of the login name.
	 */

63
	for (src = get_gecos(w); *src && *src != ','; src++) {
64
		int ch = *src;
65 66 67
		if (ch != '&')
			strbuf_addch(name, ch);
		else {
68
			/* Sorry, Mr. McDonald... */
69 70
			strbuf_addch(name, toupper(*w->pw_name));
			strbuf_addstr(name, w->pw_name + 1);
71 72 73 74
		}
	}
}

75
static int add_mailname_host(struct strbuf *buf)
76 77
{
	FILE *mailname;
78
	struct strbuf mailnamebuf = STRBUF_INIT;
79

80 81
	mailname = fopen_or_warn("/etc/mailname", "r");
	if (!mailname)
82
		return -1;
83

84
	if (strbuf_getline(&mailnamebuf, mailname) == EOF) {
85
		if (ferror(mailname))
86
			warning_errno("cannot read /etc/mailname");
87
		strbuf_release(&mailnamebuf);
88 89 90 91
		fclose(mailname);
		return -1;
	}
	/* success! */
92 93
	strbuf_addbuf(buf, &mailnamebuf);
	strbuf_release(&mailnamebuf);
94 95 96 97
	fclose(mailname);
	return 0;
}

98 99 100 101 102 103 104 105 106
static int canonical_name(const char *host, struct strbuf *out)
{
	int status = -1;

#ifndef NO_IPV6
	struct addrinfo hints, *ai;
	memset (&hints, '\0', sizeof (hints));
	hints.ai_flags = AI_CANONNAME;
	if (!getaddrinfo(host, NULL, &hints, &ai)) {
107
		if (ai && ai->ai_canonname && strchr(ai->ai_canonname, '.')) {
108 109 110 111 112 113
			strbuf_addstr(out, ai->ai_canonname);
			status = 0;
		}
		freeaddrinfo(ai);
	}
#else
114
	struct hostent *he = gethostbyname(host);
115 116 117 118 119 120 121 122 123
	if (he && strchr(he->h_name, '.')) {
		strbuf_addstr(out, he->h_name);
		status = 0;
	}
#endif /* NO_IPV6 */

	return status;
}

124
static void add_domainname(struct strbuf *out, int *is_bogus)
125
{
126
	char buf[HOST_NAME_MAX + 1];
127

128
	if (xgethostname(buf, sizeof(buf))) {
129
		warning_errno("cannot get host name");
130
		strbuf_addstr(out, "(none)");
131
		*is_bogus = 1;
132 133
		return;
	}
134
	if (strchr(buf, '.'))
135
		strbuf_addstr(out, buf);
136
	else if (canonical_name(buf, out) < 0) {
137
		strbuf_addf(out, "%s.(none)", buf);
138 139
		*is_bogus = 1;
	}
140 141
}

142 143
static void copy_email(const struct passwd *pw, struct strbuf *email,
		       int *is_bogus)
144
{
145 146 147 148
	/*
	 * Make up a fake email address
	 * (name + '@' + hostname [+ '.' + domainname])
	 */
149 150 151 152
	strbuf_addstr(email, pw->pw_name);
	strbuf_addch(email, '@');

	if (!add_mailname_host(email))
153
		return;	/* read from "/etc/mailname" (Debian) */
154
	add_domainname(email, is_bogus);
155 156
}

157
const char *ident_default_name(void)
158
{
159
	if (!(ident_config_given & IDENT_NAME_GIVEN) && !git_default_name.len) {
160
		copy_gecos(xgetpwuid_self(&default_name_is_bogus), &git_default_name);
161
		strbuf_trim(&git_default_name);
162
	}
163
	return git_default_name.buf;
164
}
165

166 167
const char *ident_default_email(void)
{
168
	if (!(ident_config_given & IDENT_MAIL_GIVEN) && !git_default_email.len) {
169 170
		const char *email = getenv("EMAIL");

171
		if (email && email[0]) {
172
			strbuf_addstr(&git_default_email, email);
173 174
			committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
			author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
175 176 177
		} else if ((email = query_user_email()) && email[0]) {
			strbuf_addstr(&git_default_email, email);
			free((char *)email);
178
		} else
179 180
			copy_email(xgetpwuid_self(&default_email_is_bogus),
				   &git_default_email, &default_email_is_bogus);
181
		strbuf_trim(&git_default_email);
182
	}
183
	return git_default_email.buf;
184 185
}

186
static const char *ident_default_date(void)
187
{
188 189 190
	if (!git_default_date.len)
		datestamp(&git_default_date);
	return git_default_date.buf;
191 192
}

193 194 195 196 197
void reset_ident_date(void)
{
	strbuf_reset(&git_default_date);
}

198 199
static int crud(unsigned char c)
{
Alex Riesen's avatar
Alex Riesen committed
200 201 202 203 204 205 206 207
	return  c <= 32  ||
		c == '.' ||
		c == ',' ||
		c == ':' ||
		c == ';' ||
		c == '<' ||
		c == '>' ||
		c == '"' ||
208
		c == '\\' ||
Alex Riesen's avatar
Alex Riesen committed
209
		c == '\'';
210 211
}

212 213 214 215 216 217 218 219 220
static int has_non_crud(const char *str)
{
	for (; *str; str++) {
		if (!crud(*str))
			return 1;
	}
	return 0;
}

221 222 223 224
/*
 * Copy over a string to the destination, but avoid special
 * characters ('\n', '<' and '>') and remove crud at the end
 */
225
static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)
226
{
227
	size_t i, len;
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
	unsigned char c;

	/* Remove crud from the beginning.. */
	while ((c = *src) != 0) {
		if (!crud(c))
			break;
		src++;
	}

	/* Remove crud from the end.. */
	len = strlen(src);
	while (len > 0) {
		c = src[len-1];
		if (!crud(c))
			break;
		--len;
	}

	/*
	 * Copy the rest to the buffer, but avoid the special
Junio C Hamano's avatar
Junio C Hamano committed
248
	 * characters '\n' '<' and '>' that act as delimiters on
249 250
	 * an identification line. We can only remove crud, never add it,
	 * so 'len' is our maximum.
251
	 */
252
	strbuf_grow(sb, len);
253 254 255 256 257 258
	for (i = 0; i < len; i++) {
		c = *src++;
		switch (c) {
		case '\n': case '<': case '>':
			continue;
		}
259
		sb->buf[sb->len++] = c;
260
	}
261
	sb->buf[sb->len] = '\0';
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
/*
 * Reverse of fmt_ident(); given an ident line, split the fields
 * to allow the caller to parse it.
 * Signal a success by returning 0, but date/tz fields of the result
 * can still be NULL if the input line only has the name/email part
 * (e.g. reading from a reflog entry).
 */
int split_ident_line(struct ident_split *split, const char *line, int len)
{
	const char *cp;
	size_t span;
	int status = -1;

	memset(split, 0, sizeof(*split));

	split->name_begin = line;
	for (cp = line; *cp && cp < line + len; cp++)
		if (*cp == '<') {
			split->mail_begin = cp + 1;
			break;
		}
	if (!split->mail_begin)
		return status;

288
	for (cp = split->mail_begin - 2; line <= cp; cp--)
289 290 291 292
		if (!isspace(*cp)) {
			split->name_end = cp + 1;
			break;
		}
293 294 295 296
	if (!split->name_end) {
		/* no human readable name */
		split->name_end = split->name_begin;
	}
297 298 299 300 301 302 303 304 305

	for (cp = split->mail_begin; cp < line + len; cp++)
		if (*cp == '>') {
			split->mail_end = cp;
			break;
		}
	if (!split->mail_end)
		return status;

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	/*
	 * Look from the end-of-line to find the trailing ">" of the mail
	 * address, even though we should already know it as split->mail_end.
	 * This can help in cases of broken idents with an extra ">" somewhere
	 * in the email address.  Note that we are assuming the timestamp will
	 * never have a ">" in it.
	 *
	 * Note that we will always find some ">" before going off the front of
	 * the string, because will always hit the split->mail_end closing
	 * bracket.
	 */
	for (cp = line + len - 1; *cp != '>'; cp--)
		;

	for (cp = cp + 1; cp < line + len && isspace(*cp); cp++)
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
		;
	if (line + len <= cp)
		goto person_only;
	split->date_begin = cp;
	span = strspn(cp, "0123456789");
	if (!span)
		goto person_only;
	split->date_end = split->date_begin + span;
	for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
		;
	if (line + len <= cp || (*cp != '+' && *cp != '-'))
		goto person_only;
	split->tz_begin = cp;
	span = strspn(cp + 1, "0123456789");
	if (!span)
		goto person_only;
	split->tz_end = split->tz_begin + 1 + span;
	return 0;

person_only:
	split->date_begin = NULL;
	split->date_end = NULL;
	split->tz_begin = NULL;
	split->tz_end = NULL;
	return 0;
}

348
static const char *env_hint =
349 350 351 352 353 354 355 356 357 358 359
N_("\n"
   "*** Please tell me who you are.\n"
   "\n"
   "Run\n"
   "\n"
   "  git config --global user.email \"you@example.com\"\n"
   "  git config --global user.name \"Your Name\"\n"
   "\n"
   "to set your account\'s default identity.\n"
   "Omit --global to set the identity only in this repository.\n"
   "\n");
360

361
const char *fmt_ident(const char *name, const char *email,
362
		      enum want_ident whose_ident, const char *date_str, int flag)
363
{
364
	static struct strbuf ident = STRBUF_INIT;
365
	int strict = (flag & IDENT_STRICT);
366
	int want_date = !(flag & IDENT_NO_DATE);
367
	int want_name = !(flag & IDENT_NO_NAME);
368

369 370 371 372 373 374
	if (!email) {
		if (whose_ident == WANT_AUTHOR_IDENT && git_author_email.len)
			email = git_author_email.buf;
		else if (whose_ident == WANT_COMMITTER_IDENT && git_committer_email.len)
			email = git_committer_email.buf;
	}
375 376 377 378 379 380 381 382 383 384 385 386 387
	if (!email) {
		if (strict && ident_use_config_only
		    && !(ident_config_given & IDENT_MAIL_GIVEN)) {
			fputs(_(env_hint), stderr);
			die(_("no email was given and auto-detection is disabled"));
		}
		email = ident_default_email();
		if (strict && default_email_is_bogus) {
			fputs(_(env_hint), stderr);
			die(_("unable to auto-detect email address (got '%s')"), email);
		}
	}

388 389
	if (want_name) {
		int using_default = 0;
390 391 392 393 394 395 396
		if (!name) {
			if (whose_ident == WANT_AUTHOR_IDENT && git_author_name.len)
				name = git_author_name.buf;
			else if (whose_ident == WANT_COMMITTER_IDENT &&
					git_committer_name.len)
				name = git_committer_name.buf;
		}
397
		if (!name) {
398
			if (strict && ident_use_config_only
399
			    && !(ident_config_given & IDENT_NAME_GIVEN)) {
400
				fputs(_(env_hint), stderr);
401
				die(_("no name was given and auto-detection is disabled"));
402
			}
403 404 405
			name = ident_default_name();
			using_default = 1;
			if (strict && default_name_is_bogus) {
406
				fputs(_(env_hint), stderr);
407
				die(_("unable to auto-detect name (got '%s')"), name);
408 409 410 411 412 413
			}
		}
		if (!*name) {
			struct passwd *pw;
			if (strict) {
				if (using_default)
414
					fputs(_(env_hint), stderr);
415
				die(_("empty ident name (for <%s>) not allowed"), email);
416 417 418
			}
			pw = xgetpwuid_self(NULL);
			name = pw->pw_name;
419
		}
420 421
		if (strict && !has_non_crud(name))
			die(_("name consists only of disallowed characters: %s"), name);
422 423
	}

424
	strbuf_reset(&ident);
425 426 427
	if (want_name) {
		strbuf_addstr_without_crud(&ident, name);
		strbuf_addstr(&ident, " <");
428
	}
429
	strbuf_addstr_without_crud(&ident, email);
430 431
	if (want_name)
			strbuf_addch(&ident, '>');
432
	if (want_date) {
433
		strbuf_addch(&ident, ' ');
434 435
		if (date_str && date_str[0]) {
			if (parse_date(date_str, &ident) < 0)
436
				die(_("invalid date format: %s"), date_str);
437 438 439
		}
		else
			strbuf_addstr(&ident, ident_default_date());
440
	}
441

442
	return ident.buf;
443
}
444

445
const char *fmt_name(enum want_ident whose_ident)
446
{
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
	char *name = NULL;
	char *email = NULL;

	switch (whose_ident) {
	case WANT_BLANK_IDENT:
		break;
	case WANT_AUTHOR_IDENT:
		name = getenv("GIT_AUTHOR_NAME");
		email = getenv("GIT_AUTHOR_EMAIL");
		break;
	case WANT_COMMITTER_IDENT:
		name = getenv("GIT_COMMITTER_NAME");
		email = getenv("GIT_COMMITTER_EMAIL");
		break;
	}
	return fmt_ident(name, email, whose_ident, NULL,
			IDENT_STRICT | IDENT_NO_DATE);
464 465
}

466
const char *git_author_info(int flag)
467
{
468 469 470 471
	if (getenv("GIT_AUTHOR_NAME"))
		author_ident_explicitly_given |= IDENT_NAME_GIVEN;
	if (getenv("GIT_AUTHOR_EMAIL"))
		author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
472
	return fmt_ident(getenv("GIT_AUTHOR_NAME"),
473
			 getenv("GIT_AUTHOR_EMAIL"),
474
			 WANT_AUTHOR_IDENT,
475
			 getenv("GIT_AUTHOR_DATE"),
476
			 flag);
477 478
}

479
const char *git_committer_info(int flag)
480
{
481
	if (getenv("GIT_COMMITTER_NAME"))
482
		committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
483
	if (getenv("GIT_COMMITTER_EMAIL"))
484
		committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
485
	return fmt_ident(getenv("GIT_COMMITTER_NAME"),
486
			 getenv("GIT_COMMITTER_EMAIL"),
487
			 WANT_COMMITTER_IDENT,
488
			 getenv("GIT_COMMITTER_DATE"),
489
			 flag);
490
}
491

492
static int ident_is_sufficient(int user_ident_explicitly_given)
493 494 495 496 497 498 499
{
#ifndef WINDOWS
	return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
#else
	return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
#endif
}
500

501 502 503 504 505 506 507 508 509 510
int committer_ident_sufficiently_given(void)
{
	return ident_is_sufficient(committer_ident_explicitly_given);
}

int author_ident_sufficiently_given(void)
{
	return ident_is_sufficient(author_ident_explicitly_given);
}

511
static int set_ident(const char *var, const char *value)
512
{
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
	if (!strcmp(var, "author.name")) {
		if (!value)
			return config_error_nonbool(var);
		strbuf_reset(&git_author_name);
		strbuf_addstr(&git_author_name, value);
		author_ident_explicitly_given |= IDENT_NAME_GIVEN;
		ident_config_given |= IDENT_NAME_GIVEN;
		return 0;
	}

	if (!strcmp(var, "author.email")) {
		if (!value)
			return config_error_nonbool(var);
		strbuf_reset(&git_author_email);
		strbuf_addstr(&git_author_email, value);
		author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
		ident_config_given |= IDENT_MAIL_GIVEN;
		return 0;
	}

	if (!strcmp(var, "committer.name")) {
		if (!value)
			return config_error_nonbool(var);
		strbuf_reset(&git_committer_name);
		strbuf_addstr(&git_committer_name, value);
		committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
		ident_config_given |= IDENT_NAME_GIVEN;
		return 0;
	}

	if (!strcmp(var, "committer.email")) {
		if (!value)
			return config_error_nonbool(var);
		strbuf_reset(&git_committer_email);
		strbuf_addstr(&git_committer_email, value);
		committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
		ident_config_given |= IDENT_MAIL_GIVEN;
550 551 552
		return 0;
	}

553 554 555
	if (!strcmp(var, "user.name")) {
		if (!value)
			return config_error_nonbool(var);
556 557
		strbuf_reset(&git_default_name);
		strbuf_addstr(&git_default_name, value);
558 559
		committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
		author_ident_explicitly_given |= IDENT_NAME_GIVEN;
560
		ident_config_given |= IDENT_NAME_GIVEN;
561 562 563 564 565 566
		return 0;
	}

	if (!strcmp(var, "user.email")) {
		if (!value)
			return config_error_nonbool(var);
567 568
		strbuf_reset(&git_default_email);
		strbuf_addstr(&git_default_email, value);
569 570
		committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
		author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
571
		ident_config_given |= IDENT_MAIL_GIVEN;
572 573 574 575 576
		return 0;
	}

	return 0;
}
577

578 579 580 581 582 583 584 585 586 587
int git_ident_config(const char *var, const char *value, void *data)
{
	if (!strcmp(var, "user.useconfigonly")) {
		ident_use_config_only = git_config_bool(var, value);
		return 0;
	}

	return set_ident(var, value);
}

588 589
static void set_env_if(const char *key, const char *value, int *given, int bit)
{
590
	if ((*given & bit) || getenv(key))
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
		return; /* nothing to do */
	setenv(key, value, 0);
	*given |= bit;
}

void prepare_fallback_ident(const char *name, const char *email)
{
	set_env_if("GIT_AUTHOR_NAME", name,
		   &author_ident_explicitly_given, IDENT_NAME_GIVEN);
	set_env_if("GIT_AUTHOR_EMAIL", email,
		   &author_ident_explicitly_given, IDENT_MAIL_GIVEN);
	set_env_if("GIT_COMMITTER_NAME", name,
		   &committer_ident_explicitly_given, IDENT_NAME_GIVEN);
	set_env_if("GIT_COMMITTER_EMAIL", email,
		   &committer_ident_explicitly_given, IDENT_MAIL_GIVEN);
}

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
static int buf_cmp(const char *a_begin, const char *a_end,
		   const char *b_begin, const char *b_end)
{
	int a_len = a_end - a_begin;
	int b_len = b_end - b_begin;
	int min = a_len < b_len ? a_len : b_len;
	int cmp;

	cmp = memcmp(a_begin, b_begin, min);
	if (cmp)
		return cmp;

	return a_len - b_len;
}

int ident_cmp(const struct ident_split *a,
	      const struct ident_split *b)
{
	int cmp;

	cmp = buf_cmp(a->mail_begin, a->mail_end,
		      b->mail_begin, b->mail_end);
	if (cmp)
		return cmp;

	return buf_cmp(a->name_begin, a->name_end,
		       b->name_begin, b->name_end);
}