ident.con commit fmt_ident: refactor strictness checks (59f9295)
   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 "cache.h"
   9
  10static struct strbuf git_default_name = STRBUF_INIT;
  11static struct strbuf git_default_email = STRBUF_INIT;
  12static struct strbuf git_default_date = STRBUF_INIT;
  13static int default_email_is_bogus;
  14static int default_name_is_bogus;
  15
  16#define IDENT_NAME_GIVEN 01
  17#define IDENT_MAIL_GIVEN 02
  18#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
  19static int committer_ident_explicitly_given;
  20static int author_ident_explicitly_given;
  21
  22#ifdef NO_GECOS_IN_PWENT
  23#define get_gecos(ignored) "&"
  24#else
  25#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
  26#endif
  27
  28static struct passwd *xgetpwuid_self(int *is_bogus)
  29{
  30        struct passwd *pw;
  31
  32        errno = 0;
  33        pw = getpwuid(getuid());
  34        if (!pw) {
  35                static struct passwd fallback;
  36                fallback.pw_name = "unknown";
  37#ifndef NO_GECOS_IN_PWENT
  38                fallback.pw_gecos = "Unknown";
  39#endif
  40                pw = &fallback;
  41                if (is_bogus)
  42                        *is_bogus = 1;
  43        }
  44        return pw;
  45}
  46
  47static void copy_gecos(const struct passwd *w, struct strbuf *name)
  48{
  49        char *src;
  50
  51        /* Traditionally GECOS field had office phone numbers etc, separated
  52         * with commas.  Also & stands for capitalized form of the login name.
  53         */
  54
  55        for (src = get_gecos(w); *src && *src != ','; src++) {
  56                int ch = *src;
  57                if (ch != '&')
  58                        strbuf_addch(name, ch);
  59                else {
  60                        /* Sorry, Mr. McDonald... */
  61                        strbuf_addch(name, toupper(*w->pw_name));
  62                        strbuf_addstr(name, w->pw_name + 1);
  63                }
  64        }
  65}
  66
  67static int add_mailname_host(struct strbuf *buf)
  68{
  69        FILE *mailname;
  70        struct strbuf mailnamebuf = STRBUF_INIT;
  71
  72        mailname = fopen("/etc/mailname", "r");
  73        if (!mailname) {
  74                if (errno != ENOENT)
  75                        warning("cannot open /etc/mailname: %s",
  76                                strerror(errno));
  77                return -1;
  78        }
  79        if (strbuf_getline(&mailnamebuf, mailname, '\n') == EOF) {
  80                if (ferror(mailname))
  81                        warning("cannot read /etc/mailname: %s",
  82                                strerror(errno));
  83                strbuf_release(&mailnamebuf);
  84                fclose(mailname);
  85                return -1;
  86        }
  87        /* success! */
  88        strbuf_addbuf(buf, &mailnamebuf);
  89        strbuf_release(&mailnamebuf);
  90        fclose(mailname);
  91        return 0;
  92}
  93
  94static int canonical_name(const char *host, struct strbuf *out)
  95{
  96        int status = -1;
  97
  98#ifndef NO_IPV6
  99        struct addrinfo hints, *ai;
 100        memset (&hints, '\0', sizeof (hints));
 101        hints.ai_flags = AI_CANONNAME;
 102        if (!getaddrinfo(host, NULL, &hints, &ai)) {
 103                if (ai && strchr(ai->ai_canonname, '.')) {
 104                        strbuf_addstr(out, ai->ai_canonname);
 105                        status = 0;
 106                }
 107                freeaddrinfo(ai);
 108        }
 109#else
 110        struct hostent *he = gethostbyname(host);
 111        if (he && strchr(he->h_name, '.')) {
 112                strbuf_addstr(out, he->h_name);
 113                status = 0;
 114        }
 115#endif /* NO_IPV6 */
 116
 117        return status;
 118}
 119
 120static void add_domainname(struct strbuf *out, int *is_bogus)
 121{
 122        char buf[1024];
 123
 124        if (gethostname(buf, sizeof(buf))) {
 125                warning("cannot get host name: %s", strerror(errno));
 126                strbuf_addstr(out, "(none)");
 127                *is_bogus = 1;
 128                return;
 129        }
 130        if (strchr(buf, '.'))
 131                strbuf_addstr(out, buf);
 132        else if (canonical_name(buf, out) < 0) {
 133                strbuf_addf(out, "%s.(none)", buf);
 134                *is_bogus = 1;
 135        }
 136}
 137
 138static void copy_email(const struct passwd *pw, struct strbuf *email,
 139                       int *is_bogus)
 140{
 141        /*
 142         * Make up a fake email address
 143         * (name + '@' + hostname [+ '.' + domainname])
 144         */
 145        strbuf_addstr(email, pw->pw_name);
 146        strbuf_addch(email, '@');
 147
 148        if (!add_mailname_host(email))
 149                return; /* read from "/etc/mailname" (Debian) */
 150        add_domainname(email, is_bogus);
 151}
 152
 153const char *ident_default_name(void)
 154{
 155        if (!git_default_name.len) {
 156                copy_gecos(xgetpwuid_self(&default_name_is_bogus), &git_default_name);
 157                strbuf_trim(&git_default_name);
 158        }
 159        return git_default_name.buf;
 160}
 161
 162const char *ident_default_email(void)
 163{
 164        if (!git_default_email.len) {
 165                const char *email = getenv("EMAIL");
 166
 167                if (email && email[0]) {
 168                        strbuf_addstr(&git_default_email, email);
 169                        committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 170                        author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 171                } else
 172                        copy_email(xgetpwuid_self(&default_email_is_bogus),
 173                                   &git_default_email, &default_email_is_bogus);
 174                strbuf_trim(&git_default_email);
 175        }
 176        return git_default_email.buf;
 177}
 178
 179static const char *ident_default_date(void)
 180{
 181        if (!git_default_date.len)
 182                datestamp(&git_default_date);
 183        return git_default_date.buf;
 184}
 185
 186static int crud(unsigned char c)
 187{
 188        return  c <= 32  ||
 189                c == '.' ||
 190                c == ',' ||
 191                c == ':' ||
 192                c == ';' ||
 193                c == '<' ||
 194                c == '>' ||
 195                c == '"' ||
 196                c == '\\' ||
 197                c == '\'';
 198}
 199
 200/*
 201 * Copy over a string to the destination, but avoid special
 202 * characters ('\n', '<' and '>') and remove crud at the end
 203 */
 204static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)
 205{
 206        size_t i, len;
 207        unsigned char c;
 208
 209        /* Remove crud from the beginning.. */
 210        while ((c = *src) != 0) {
 211                if (!crud(c))
 212                        break;
 213                src++;
 214        }
 215
 216        /* Remove crud from the end.. */
 217        len = strlen(src);
 218        while (len > 0) {
 219                c = src[len-1];
 220                if (!crud(c))
 221                        break;
 222                --len;
 223        }
 224
 225        /*
 226         * Copy the rest to the buffer, but avoid the special
 227         * characters '\n' '<' and '>' that act as delimiters on
 228         * an identification line. We can only remove crud, never add it,
 229         * so 'len' is our maximum.
 230         */
 231        strbuf_grow(sb, len);
 232        for (i = 0; i < len; i++) {
 233                c = *src++;
 234                switch (c) {
 235                case '\n': case '<': case '>':
 236                        continue;
 237                }
 238                sb->buf[sb->len++] = c;
 239        }
 240        sb->buf[sb->len] = '\0';
 241}
 242
 243/*
 244 * Reverse of fmt_ident(); given an ident line, split the fields
 245 * to allow the caller to parse it.
 246 * Signal a success by returning 0, but date/tz fields of the result
 247 * can still be NULL if the input line only has the name/email part
 248 * (e.g. reading from a reflog entry).
 249 */
 250int split_ident_line(struct ident_split *split, const char *line, int len)
 251{
 252        const char *cp;
 253        size_t span;
 254        int status = -1;
 255
 256        memset(split, 0, sizeof(*split));
 257
 258        split->name_begin = line;
 259        for (cp = line; *cp && cp < line + len; cp++)
 260                if (*cp == '<') {
 261                        split->mail_begin = cp + 1;
 262                        break;
 263                }
 264        if (!split->mail_begin)
 265                return status;
 266
 267        for (cp = split->mail_begin - 2; line <= cp; cp--)
 268                if (!isspace(*cp)) {
 269                        split->name_end = cp + 1;
 270                        break;
 271                }
 272        if (!split->name_end) {
 273                /* no human readable name */
 274                split->name_end = split->name_begin;
 275        }
 276
 277        for (cp = split->mail_begin; cp < line + len; cp++)
 278                if (*cp == '>') {
 279                        split->mail_end = cp;
 280                        break;
 281                }
 282        if (!split->mail_end)
 283                return status;
 284
 285        /*
 286         * Look from the end-of-line to find the trailing ">" of the mail
 287         * address, even though we should already know it as split->mail_end.
 288         * This can help in cases of broken idents with an extra ">" somewhere
 289         * in the email address.  Note that we are assuming the timestamp will
 290         * never have a ">" in it.
 291         *
 292         * Note that we will always find some ">" before going off the front of
 293         * the string, because will always hit the split->mail_end closing
 294         * bracket.
 295         */
 296        for (cp = line + len - 1; *cp != '>'; cp--)
 297                ;
 298
 299        for (cp = cp + 1; cp < line + len && isspace(*cp); cp++)
 300                ;
 301        if (line + len <= cp)
 302                goto person_only;
 303        split->date_begin = cp;
 304        span = strspn(cp, "0123456789");
 305        if (!span)
 306                goto person_only;
 307        split->date_end = split->date_begin + span;
 308        for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
 309                ;
 310        if (line + len <= cp || (*cp != '+' && *cp != '-'))
 311                goto person_only;
 312        split->tz_begin = cp;
 313        span = strspn(cp + 1, "0123456789");
 314        if (!span)
 315                goto person_only;
 316        split->tz_end = split->tz_begin + 1 + span;
 317        return 0;
 318
 319person_only:
 320        split->date_begin = NULL;
 321        split->date_end = NULL;
 322        split->tz_begin = NULL;
 323        split->tz_end = NULL;
 324        return 0;
 325}
 326
 327static const char *env_hint =
 328"\n"
 329"*** Please tell me who you are.\n"
 330"\n"
 331"Run\n"
 332"\n"
 333"  git config --global user.email \"you@example.com\"\n"
 334"  git config --global user.name \"Your Name\"\n"
 335"\n"
 336"to set your account\'s default identity.\n"
 337"Omit --global to set the identity only in this repository.\n"
 338"\n";
 339
 340const char *fmt_ident(const char *name, const char *email,
 341                      const char *date_str, int flag)
 342{
 343        static struct strbuf ident = STRBUF_INIT;
 344        int strict = (flag & IDENT_STRICT);
 345        int want_date = !(flag & IDENT_NO_DATE);
 346        int want_name = !(flag & IDENT_NO_NAME);
 347
 348        if (want_name) {
 349                int using_default = 0;
 350                if (!name) {
 351                        name = ident_default_name();
 352                        using_default = 1;
 353                        if (strict && default_name_is_bogus) {
 354                                fputs(env_hint, stderr);
 355                                die("unable to auto-detect name (got '%s')", name);
 356                        }
 357                }
 358                if (!*name) {
 359                        struct passwd *pw;
 360                        if (strict) {
 361                                if (using_default)
 362                                        fputs(env_hint, stderr);
 363                                die("empty ident name (for <%s>) not allowed", email);
 364                        }
 365                        pw = xgetpwuid_self(NULL);
 366                        name = pw->pw_name;
 367                }
 368        }
 369
 370        if (!email) {
 371                email = ident_default_email();
 372                if (strict && default_email_is_bogus) {
 373                        fputs(env_hint, stderr);
 374                        die("unable to auto-detect email address (got '%s')", email);
 375                }
 376        }
 377
 378        strbuf_reset(&ident);
 379        if (want_name) {
 380                strbuf_addstr_without_crud(&ident, name);
 381                strbuf_addstr(&ident, " <");
 382        }
 383        strbuf_addstr_without_crud(&ident, email);
 384        if (want_name)
 385                        strbuf_addch(&ident, '>');
 386        if (want_date) {
 387                strbuf_addch(&ident, ' ');
 388                if (date_str && date_str[0]) {
 389                        if (parse_date(date_str, &ident) < 0)
 390                                die("invalid date format: %s", date_str);
 391                }
 392                else
 393                        strbuf_addstr(&ident, ident_default_date());
 394        }
 395
 396        return ident.buf;
 397}
 398
 399const char *fmt_name(const char *name, const char *email)
 400{
 401        return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE);
 402}
 403
 404const char *git_author_info(int flag)
 405{
 406        if (getenv("GIT_AUTHOR_NAME"))
 407                author_ident_explicitly_given |= IDENT_NAME_GIVEN;
 408        if (getenv("GIT_AUTHOR_EMAIL"))
 409                author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 410        return fmt_ident(getenv("GIT_AUTHOR_NAME"),
 411                         getenv("GIT_AUTHOR_EMAIL"),
 412                         getenv("GIT_AUTHOR_DATE"),
 413                         flag);
 414}
 415
 416const char *git_committer_info(int flag)
 417{
 418        if (getenv("GIT_COMMITTER_NAME"))
 419                committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
 420        if (getenv("GIT_COMMITTER_EMAIL"))
 421                committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 422        return fmt_ident(getenv("GIT_COMMITTER_NAME"),
 423                         getenv("GIT_COMMITTER_EMAIL"),
 424                         getenv("GIT_COMMITTER_DATE"),
 425                         flag);
 426}
 427
 428static int ident_is_sufficient(int user_ident_explicitly_given)
 429{
 430#ifndef WINDOWS
 431        return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
 432#else
 433        return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
 434#endif
 435}
 436
 437int committer_ident_sufficiently_given(void)
 438{
 439        return ident_is_sufficient(committer_ident_explicitly_given);
 440}
 441
 442int author_ident_sufficiently_given(void)
 443{
 444        return ident_is_sufficient(author_ident_explicitly_given);
 445}
 446
 447int git_ident_config(const char *var, const char *value, void *data)
 448{
 449        if (!strcmp(var, "user.name")) {
 450                if (!value)
 451                        return config_error_nonbool(var);
 452                strbuf_reset(&git_default_name);
 453                strbuf_addstr(&git_default_name, value);
 454                committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
 455                author_ident_explicitly_given |= IDENT_NAME_GIVEN;
 456                return 0;
 457        }
 458
 459        if (!strcmp(var, "user.email")) {
 460                if (!value)
 461                        return config_error_nonbool(var);
 462                strbuf_reset(&git_default_email);
 463                strbuf_addstr(&git_default_email, value);
 464                committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 465                author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 466                return 0;
 467        }
 468
 469        return 0;
 470}
 471
 472static int buf_cmp(const char *a_begin, const char *a_end,
 473                   const char *b_begin, const char *b_end)
 474{
 475        int a_len = a_end - a_begin;
 476        int b_len = b_end - b_begin;
 477        int min = a_len < b_len ? a_len : b_len;
 478        int cmp;
 479
 480        cmp = memcmp(a_begin, b_begin, min);
 481        if (cmp)
 482                return cmp;
 483
 484        return a_len - b_len;
 485}
 486
 487int ident_cmp(const struct ident_split *a,
 488              const struct ident_split *b)
 489{
 490        int cmp;
 491
 492        cmp = buf_cmp(a->mail_begin, a->mail_end,
 493                      b->mail_begin, b->mail_end);
 494        if (cmp)
 495                return cmp;
 496
 497        return buf_cmp(a->name_begin, a->name_end,
 498                       b->name_begin, b->name_end);
 499}