git
/
ident.c
732 строки · 17.7 Кб
1/*
2* ident.c
3*
4* create git identifier lines of the form "name <email> date"
5*
6* Copyright (C) 2005 Linus Torvalds
7*/
8#include "git-compat-util.h"9#include "ident.h"10#include "config.h"11#include "date.h"12#include "gettext.h"13#include "mailmap.h"14#include "strbuf.h"15
16static struct strbuf git_default_name = STRBUF_INIT;17static struct strbuf git_default_email = STRBUF_INIT;18static struct strbuf git_default_date = STRBUF_INIT;19static struct strbuf git_author_name = STRBUF_INIT;20static struct strbuf git_author_email = STRBUF_INIT;21static struct strbuf git_committer_name = STRBUF_INIT;22static struct strbuf git_committer_email = STRBUF_INIT;23static int default_email_is_bogus;24static int default_name_is_bogus;25
26static int ident_use_config_only;27
28#define IDENT_NAME_GIVEN 0129#define IDENT_MAIL_GIVEN 0230#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)31static int committer_ident_explicitly_given;32static int author_ident_explicitly_given;33static int ident_config_given;34
35#ifdef NO_GECOS_IN_PWENT36#define get_gecos(ignored) "&"37#else38#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)39#endif40
41static struct passwd *xgetpwuid_self(int *is_bogus)42{
43struct passwd *pw;44
45errno = 0;46pw = getpwuid(getuid());47if (!pw) {48static struct passwd fallback;49fallback.pw_name = (char *) "unknown";50#ifndef NO_GECOS_IN_PWENT51fallback.pw_gecos = (char *) "Unknown";52#endif53pw = &fallback;54if (is_bogus)55*is_bogus = 1;56}57return pw;58}
59
60static void copy_gecos(const struct passwd *w, struct strbuf *name)61{
62char *src;63
64/* Traditionally GECOS field had office phone numbers etc, separated65* with commas. Also & stands for capitalized form of the login name.
66*/
67
68for (src = get_gecos(w); *src && *src != ','; src++) {69int ch = *src;70if (ch != '&')71strbuf_addch(name, ch);72else {73/* Sorry, Mr. McDonald... */74strbuf_addch(name, toupper(*w->pw_name));75strbuf_addstr(name, w->pw_name + 1);76}77}78}
79
80static int add_mailname_host(struct strbuf *buf)81{
82FILE *mailname;83struct strbuf mailnamebuf = STRBUF_INIT;84
85mailname = fopen_or_warn("/etc/mailname", "r");86if (!mailname)87return -1;88
89if (strbuf_getline(&mailnamebuf, mailname) == EOF) {90if (ferror(mailname))91warning_errno("cannot read /etc/mailname");92strbuf_release(&mailnamebuf);93fclose(mailname);94return -1;95}96/* success! */97strbuf_addbuf(buf, &mailnamebuf);98strbuf_release(&mailnamebuf);99fclose(mailname);100return 0;101}
102
103static int canonical_name(const char *host, struct strbuf *out)104{
105int status = -1;106
107#ifndef NO_IPV6108struct addrinfo hints, *ai;109memset (&hints, '\0', sizeof (hints));110hints.ai_flags = AI_CANONNAME;111if (!getaddrinfo(host, NULL, &hints, &ai)) {112if (ai && ai->ai_canonname && strchr(ai->ai_canonname, '.')) {113strbuf_addstr(out, ai->ai_canonname);114status = 0;115}116freeaddrinfo(ai);117}118#else119struct hostent *he = gethostbyname(host);120if (he && strchr(he->h_name, '.')) {121strbuf_addstr(out, he->h_name);122status = 0;123}124#endif /* NO_IPV6 */125
126return status;127}
128
129static void add_domainname(struct strbuf *out, int *is_bogus)130{
131char buf[HOST_NAME_MAX + 1];132
133if (xgethostname(buf, sizeof(buf))) {134warning_errno("cannot get host name");135strbuf_addstr(out, "(none)");136*is_bogus = 1;137return;138}139if (strchr(buf, '.'))140strbuf_addstr(out, buf);141else if (canonical_name(buf, out) < 0) {142strbuf_addf(out, "%s.(none)", buf);143*is_bogus = 1;144}145}
146
147static void copy_email(const struct passwd *pw, struct strbuf *email,148int *is_bogus)149{
150/*151* Make up a fake email address
152* (name + '@' + hostname [+ '.' + domainname])
153*/
154strbuf_addstr(email, pw->pw_name);155strbuf_addch(email, '@');156
157if (!add_mailname_host(email))158return; /* read from "/etc/mailname" (Debian) */159add_domainname(email, is_bogus);160}
161
162const char *ident_default_name(void)163{
164if (!(ident_config_given & IDENT_NAME_GIVEN) && !git_default_name.len) {165copy_gecos(xgetpwuid_self(&default_name_is_bogus), &git_default_name);166strbuf_trim(&git_default_name);167}168return git_default_name.buf;169}
170
171const char *ident_default_email(void)172{
173if (!(ident_config_given & IDENT_MAIL_GIVEN) && !git_default_email.len) {174const char *email = getenv("EMAIL");175
176if (email && email[0]) {177strbuf_addstr(&git_default_email, email);178committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;179author_ident_explicitly_given |= IDENT_MAIL_GIVEN;180} else if ((email = query_user_email()) && email[0]) {181strbuf_addstr(&git_default_email, email);182free((char *)email);183} else184copy_email(xgetpwuid_self(&default_email_is_bogus),185&git_default_email, &default_email_is_bogus);186strbuf_trim(&git_default_email);187}188return git_default_email.buf;189}
190
191static const char *ident_default_date(void)192{
193if (!git_default_date.len)194datestamp(&git_default_date);195return git_default_date.buf;196}
197
198void reset_ident_date(void)199{
200strbuf_reset(&git_default_date);201}
202
203static int crud(unsigned char c)204{
205return c <= 32 ||206c == ',' ||207c == ':' ||208c == ';' ||209c == '<' ||210c == '>' ||211c == '"' ||212c == '\\' ||213c == '\'';214}
215
216static int has_non_crud(const char *str)217{
218for (; *str; str++) {219if (!crud(*str))220return 1;221}222return 0;223}
224
225/*
226* Copy over a string to the destination, but avoid special
227* characters ('\n', '<' and '>') and remove crud at the end
228*/
229static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)230{
231size_t i, len;232unsigned char c;233
234/* Remove crud from the beginning.. */235while ((c = *src) != 0) {236if (!crud(c))237break;238src++;239}240
241/* Remove crud from the end.. */242len = strlen(src);243while (len > 0) {244c = src[len-1];245if (!crud(c))246break;247--len;248}249
250/*251* Copy the rest to the buffer, but avoid the special
252* characters '\n' '<' and '>' that act as delimiters on
253* an identification line. We can only remove crud, never add it,
254* so 'len' is our maximum.
255*/
256strbuf_grow(sb, len);257for (i = 0; i < len; i++) {258c = *src++;259switch (c) {260case '\n': case '<': case '>':261continue;262}263sb->buf[sb->len++] = c;264}265sb->buf[sb->len] = '\0';266}
267
268/*
269* Reverse of fmt_ident(); given an ident line, split the fields
270* to allow the caller to parse it.
271* Signal a success by returning 0, but date/tz fields of the result
272* can still be NULL if the input line only has the name/email part
273* (e.g. reading from a reflog entry).
274*/
275int split_ident_line(struct ident_split *split, const char *line, int len)276{
277const char *cp;278size_t span;279int status = -1;280
281memset(split, 0, sizeof(*split));282
283split->name_begin = line;284for (cp = line; *cp && cp < line + len; cp++)285if (*cp == '<') {286split->mail_begin = cp + 1;287break;288}289if (!split->mail_begin)290return status;291
292for (cp = split->mail_begin - 2; line <= cp; cp--)293if (!isspace(*cp)) {294split->name_end = cp + 1;295break;296}297if (!split->name_end) {298/* no human readable name */299split->name_end = split->name_begin;300}301
302for (cp = split->mail_begin; cp < line + len; cp++)303if (*cp == '>') {304split->mail_end = cp;305break;306}307if (!split->mail_end)308return status;309
310/*311* Look from the end-of-line to find the trailing ">" of the mail
312* address, even though we should already know it as split->mail_end.
313* This can help in cases of broken idents with an extra ">" somewhere
314* in the email address. Note that we are assuming the timestamp will
315* never have a ">" in it.
316*
317* Note that we will always find some ">" before going off the front of
318* the string, because will always hit the split->mail_end closing
319* bracket.
320*/
321for (cp = line + len - 1; *cp != '>'; cp--)322;323
324for (cp = cp + 1; cp < line + len && isspace(*cp); cp++)325;326if (line + len <= cp)327goto person_only;328split->date_begin = cp;329span = strspn(cp, "0123456789");330if (!span)331goto person_only;332split->date_end = split->date_begin + span;333for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)334;335if (line + len <= cp || (*cp != '+' && *cp != '-'))336goto person_only;337split->tz_begin = cp;338span = strspn(cp + 1, "0123456789");339if (!span)340goto person_only;341split->tz_end = split->tz_begin + 1 + span;342return 0;343
344person_only:345split->date_begin = NULL;346split->date_end = NULL;347split->tz_begin = NULL;348split->tz_end = NULL;349return 0;350}
351
352/*
353* Returns the difference between the new and old length of the ident line.
354*/
355static ssize_t rewrite_ident_line(const char *person, size_t len,356struct strbuf *buf,357struct string_list *mailmap)358{
359size_t namelen, maillen;360const char *name;361const char *mail;362struct ident_split ident;363
364if (split_ident_line(&ident, person, len))365return 0;366
367mail = ident.mail_begin;368maillen = ident.mail_end - ident.mail_begin;369name = ident.name_begin;370namelen = ident.name_end - ident.name_begin;371
372if (map_user(mailmap, &mail, &maillen, &name, &namelen)) {373struct strbuf namemail = STRBUF_INIT;374size_t newlen;375
376strbuf_addf(&namemail, "%.*s <%.*s>",377(int)namelen, name, (int)maillen, mail);378
379strbuf_splice(buf, ident.name_begin - buf->buf,380ident.mail_end - ident.name_begin + 1,381namemail.buf, namemail.len);382newlen = namemail.len;383
384strbuf_release(&namemail);385
386return newlen - (ident.mail_end - ident.name_begin);387}388
389return 0;390}
391
392void apply_mailmap_to_header(struct strbuf *buf, const char **header,393struct string_list *mailmap)394{
395size_t buf_offset = 0;396
397if (!mailmap)398return;399
400for (;;) {401const char *person, *line;402size_t i;403int found_header = 0;404
405line = buf->buf + buf_offset;406if (!*line || *line == '\n')407return; /* End of headers */408
409for (i = 0; header[i]; i++)410if (skip_prefix(line, header[i], &person)) {411const char *endp = strchrnul(person, '\n');412found_header = 1;413buf_offset += endp - line;414buf_offset += rewrite_ident_line(person, endp - person, buf, mailmap);415break;416}417
418if (!found_header) {419buf_offset = strchrnul(line, '\n') - buf->buf;420if (buf->buf[buf_offset] == '\n')421buf_offset++;422}423}424}
425
426static void ident_env_hint(enum want_ident whose_ident)427{
428switch (whose_ident) {429case WANT_AUTHOR_IDENT:430fputs(_("Author identity unknown\n"), stderr);431break;432case WANT_COMMITTER_IDENT:433fputs(_("Committer identity unknown\n"), stderr);434break;435default:436break;437}438
439fputs(_("\n"440"*** Please tell me who you are.\n"441"\n"442"Run\n"443"\n"444" git config --global user.email \"you@example.com\"\n"445" git config --global user.name \"Your Name\"\n"446"\n"447"to set your account\'s default identity.\n"448"Omit --global to set the identity only in this repository.\n"449"\n"), stderr);450}
451
452const char *fmt_ident(const char *name, const char *email,453enum want_ident whose_ident, const char *date_str, int flag)454{
455static int index;456static struct strbuf ident_pool[2] = { STRBUF_INIT, STRBUF_INIT };457int strict = (flag & IDENT_STRICT);458int want_date = !(flag & IDENT_NO_DATE);459int want_name = !(flag & IDENT_NO_NAME);460
461struct strbuf *ident = &ident_pool[index];462index = (index + 1) % ARRAY_SIZE(ident_pool);463
464if (!email) {465if (whose_ident == WANT_AUTHOR_IDENT && git_author_email.len)466email = git_author_email.buf;467else if (whose_ident == WANT_COMMITTER_IDENT && git_committer_email.len)468email = git_committer_email.buf;469}470if (!email) {471if (strict && ident_use_config_only472&& !(ident_config_given & IDENT_MAIL_GIVEN)) {473ident_env_hint(whose_ident);474die(_("no email was given and auto-detection is disabled"));475}476email = ident_default_email();477if (strict && default_email_is_bogus) {478ident_env_hint(whose_ident);479die(_("unable to auto-detect email address (got '%s')"), email);480}481}482
483if (want_name) {484int using_default = 0;485if (!name) {486if (whose_ident == WANT_AUTHOR_IDENT && git_author_name.len)487name = git_author_name.buf;488else if (whose_ident == WANT_COMMITTER_IDENT &&489git_committer_name.len)490name = git_committer_name.buf;491}492if (!name) {493if (strict && ident_use_config_only494&& !(ident_config_given & IDENT_NAME_GIVEN)) {495ident_env_hint(whose_ident);496die(_("no name was given and auto-detection is disabled"));497}498name = ident_default_name();499using_default = 1;500if (strict && default_name_is_bogus) {501ident_env_hint(whose_ident);502die(_("unable to auto-detect name (got '%s')"), name);503}504}505if (!*name) {506struct passwd *pw;507if (strict) {508if (using_default)509ident_env_hint(whose_ident);510die(_("empty ident name (for <%s>) not allowed"), email);511}512pw = xgetpwuid_self(NULL);513name = pw->pw_name;514}515if (strict && !has_non_crud(name))516die(_("name consists only of disallowed characters: %s"), name);517}518
519strbuf_reset(ident);520if (want_name) {521strbuf_addstr_without_crud(ident, name);522strbuf_addstr(ident, " <");523}524strbuf_addstr_without_crud(ident, email);525if (want_name)526strbuf_addch(ident, '>');527if (want_date) {528strbuf_addch(ident, ' ');529if (date_str && date_str[0]) {530if (parse_date(date_str, ident) < 0)531die(_("invalid date format: %s"), date_str);532}533else534strbuf_addstr(ident, ident_default_date());535}536
537return ident->buf;538}
539
540const char *fmt_name(enum want_ident whose_ident)541{
542char *name = NULL;543char *email = NULL;544
545switch (whose_ident) {546case WANT_BLANK_IDENT:547break;548case WANT_AUTHOR_IDENT:549name = getenv("GIT_AUTHOR_NAME");550email = getenv("GIT_AUTHOR_EMAIL");551break;552case WANT_COMMITTER_IDENT:553name = getenv("GIT_COMMITTER_NAME");554email = getenv("GIT_COMMITTER_EMAIL");555break;556}557return fmt_ident(name, email, whose_ident, NULL,558IDENT_STRICT | IDENT_NO_DATE);559}
560
561const char *git_author_info(int flag)562{
563if (getenv("GIT_AUTHOR_NAME"))564author_ident_explicitly_given |= IDENT_NAME_GIVEN;565if (getenv("GIT_AUTHOR_EMAIL"))566author_ident_explicitly_given |= IDENT_MAIL_GIVEN;567return fmt_ident(getenv("GIT_AUTHOR_NAME"),568getenv("GIT_AUTHOR_EMAIL"),569WANT_AUTHOR_IDENT,570getenv("GIT_AUTHOR_DATE"),571flag);572}
573
574const char *git_committer_info(int flag)575{
576if (getenv("GIT_COMMITTER_NAME"))577committer_ident_explicitly_given |= IDENT_NAME_GIVEN;578if (getenv("GIT_COMMITTER_EMAIL"))579committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;580return fmt_ident(getenv("GIT_COMMITTER_NAME"),581getenv("GIT_COMMITTER_EMAIL"),582WANT_COMMITTER_IDENT,583getenv("GIT_COMMITTER_DATE"),584flag);585}
586
587static int ident_is_sufficient(int user_ident_explicitly_given)588{
589#ifndef WINDOWS590return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);591#else592return (user_ident_explicitly_given == IDENT_ALL_GIVEN);593#endif594}
595
596int committer_ident_sufficiently_given(void)597{
598return ident_is_sufficient(committer_ident_explicitly_given);599}
600
601int author_ident_sufficiently_given(void)602{
603return ident_is_sufficient(author_ident_explicitly_given);604}
605
606static int set_ident(const char *var, const char *value)607{
608if (!strcmp(var, "author.name")) {609if (!value)610return config_error_nonbool(var);611strbuf_reset(&git_author_name);612strbuf_addstr(&git_author_name, value);613author_ident_explicitly_given |= IDENT_NAME_GIVEN;614ident_config_given |= IDENT_NAME_GIVEN;615return 0;616}617
618if (!strcmp(var, "author.email")) {619if (!value)620return config_error_nonbool(var);621strbuf_reset(&git_author_email);622strbuf_addstr(&git_author_email, value);623author_ident_explicitly_given |= IDENT_MAIL_GIVEN;624ident_config_given |= IDENT_MAIL_GIVEN;625return 0;626}627
628if (!strcmp(var, "committer.name")) {629if (!value)630return config_error_nonbool(var);631strbuf_reset(&git_committer_name);632strbuf_addstr(&git_committer_name, value);633committer_ident_explicitly_given |= IDENT_NAME_GIVEN;634ident_config_given |= IDENT_NAME_GIVEN;635return 0;636}637
638if (!strcmp(var, "committer.email")) {639if (!value)640return config_error_nonbool(var);641strbuf_reset(&git_committer_email);642strbuf_addstr(&git_committer_email, value);643committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;644ident_config_given |= IDENT_MAIL_GIVEN;645return 0;646}647
648if (!strcmp(var, "user.name")) {649if (!value)650return config_error_nonbool(var);651strbuf_reset(&git_default_name);652strbuf_addstr(&git_default_name, value);653committer_ident_explicitly_given |= IDENT_NAME_GIVEN;654author_ident_explicitly_given |= IDENT_NAME_GIVEN;655ident_config_given |= IDENT_NAME_GIVEN;656return 0;657}658
659if (!strcmp(var, "user.email")) {660if (!value)661return config_error_nonbool(var);662strbuf_reset(&git_default_email);663strbuf_addstr(&git_default_email, value);664committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;665author_ident_explicitly_given |= IDENT_MAIL_GIVEN;666ident_config_given |= IDENT_MAIL_GIVEN;667return 0;668}669
670return 0;671}
672
673int git_ident_config(const char *var, const char *value,674const struct config_context *ctx UNUSED,675void *data UNUSED)676{
677if (!strcmp(var, "user.useconfigonly")) {678ident_use_config_only = git_config_bool(var, value);679return 0;680}681
682return set_ident(var, value);683}
684
685static void set_env_if(const char *key, const char *value, int *given, int bit)686{
687if ((*given & bit) || getenv(key))688return; /* nothing to do */689setenv(key, value, 0);690*given |= bit;691}
692
693void prepare_fallback_ident(const char *name, const char *email)694{
695set_env_if("GIT_AUTHOR_NAME", name,696&author_ident_explicitly_given, IDENT_NAME_GIVEN);697set_env_if("GIT_AUTHOR_EMAIL", email,698&author_ident_explicitly_given, IDENT_MAIL_GIVEN);699set_env_if("GIT_COMMITTER_NAME", name,700&committer_ident_explicitly_given, IDENT_NAME_GIVEN);701set_env_if("GIT_COMMITTER_EMAIL", email,702&committer_ident_explicitly_given, IDENT_MAIL_GIVEN);703}
704
705static int buf_cmp(const char *a_begin, const char *a_end,706const char *b_begin, const char *b_end)707{
708int a_len = a_end - a_begin;709int b_len = b_end - b_begin;710int min = a_len < b_len ? a_len : b_len;711int cmp;712
713cmp = memcmp(a_begin, b_begin, min);714if (cmp)715return cmp;716
717return a_len - b_len;718}
719
720int ident_cmp(const struct ident_split *a,721const struct ident_split *b)722{
723int cmp;724
725cmp = buf_cmp(a->mail_begin, a->mail_end,726b->mail_begin, b->mail_end);727if (cmp)728return cmp;729
730return buf_cmp(a->name_begin, a->name_end,731b->name_begin, b->name_end);732}
733