ident.con commit refs: document transaction semantics (4938686)
   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 && !name)
 349                name = ident_default_name();
 350        if (!email)
 351                email = ident_default_email();
 352
 353        if (want_name && !*name) {
 354                struct passwd *pw;
 355
 356                if (strict) {
 357                        if (name == git_default_name.buf)
 358                                fputs(env_hint, stderr);
 359                        die("empty ident name (for <%s>) not allowed", email);
 360                }
 361                pw = xgetpwuid_self(NULL);
 362                name = pw->pw_name;
 363        }
 364
 365        if (want_name && strict &&
 366            name == git_default_name.buf && default_name_is_bogus) {
 367                fputs(env_hint, stderr);
 368                die("unable to auto-detect name (got '%s')", name);
 369        }
 370
 371        if (strict && email == git_default_email.buf && default_email_is_bogus) {
 372                fputs(env_hint, stderr);
 373                die("unable to auto-detect email address (got '%s')", email);
 374        }
 375
 376        strbuf_reset(&ident);
 377        if (want_name) {
 378                strbuf_addstr_without_crud(&ident, name);
 379                strbuf_addstr(&ident, " <");
 380        }
 381        strbuf_addstr_without_crud(&ident, email);
 382        if (want_name)
 383                        strbuf_addch(&ident, '>');
 384        if (want_date) {
 385                strbuf_addch(&ident, ' ');
 386                if (date_str && date_str[0]) {
 387                        if (parse_date(date_str, &ident) < 0)
 388                                die("invalid date format: %s", date_str);
 389                }
 390                else
 391                        strbuf_addstr(&ident, ident_default_date());
 392        }
 393
 394        return ident.buf;
 395}
 396
 397const char *fmt_name(const char *name, const char *email)
 398{
 399        return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE);
 400}
 401
 402const char *git_author_info(int flag)
 403{
 404        if (getenv("GIT_AUTHOR_NAME"))
 405                author_ident_explicitly_given |= IDENT_NAME_GIVEN;
 406        if (getenv("GIT_AUTHOR_EMAIL"))
 407                author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 408        return fmt_ident(getenv("GIT_AUTHOR_NAME"),
 409                         getenv("GIT_AUTHOR_EMAIL"),
 410                         getenv("GIT_AUTHOR_DATE"),
 411                         flag);
 412}
 413
 414const char *git_committer_info(int flag)
 415{
 416        if (getenv("GIT_COMMITTER_NAME"))
 417                committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
 418        if (getenv("GIT_COMMITTER_EMAIL"))
 419                committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 420        return fmt_ident(getenv("GIT_COMMITTER_NAME"),
 421                         getenv("GIT_COMMITTER_EMAIL"),
 422                         getenv("GIT_COMMITTER_DATE"),
 423                         flag);
 424}
 425
 426static int ident_is_sufficient(int user_ident_explicitly_given)
 427{
 428#ifndef WINDOWS
 429        return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
 430#else
 431        return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
 432#endif
 433}
 434
 435int committer_ident_sufficiently_given(void)
 436{
 437        return ident_is_sufficient(committer_ident_explicitly_given);
 438}
 439
 440int author_ident_sufficiently_given(void)
 441{
 442        return ident_is_sufficient(author_ident_explicitly_given);
 443}
 444
 445int git_ident_config(const char *var, const char *value, void *data)
 446{
 447        if (!strcmp(var, "user.name")) {
 448                if (!value)
 449                        return config_error_nonbool(var);
 450                strbuf_reset(&git_default_name);
 451                strbuf_addstr(&git_default_name, value);
 452                committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
 453                author_ident_explicitly_given |= IDENT_NAME_GIVEN;
 454                return 0;
 455        }
 456
 457        if (!strcmp(var, "user.email")) {
 458                if (!value)
 459                        return config_error_nonbool(var);
 460                strbuf_reset(&git_default_email);
 461                strbuf_addstr(&git_default_email, value);
 462                committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 463                author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 464                return 0;
 465        }
 466
 467        return 0;
 468}
 469
 470static int buf_cmp(const char *a_begin, const char *a_end,
 471                   const char *b_begin, const char *b_end)
 472{
 473        int a_len = a_end - a_begin;
 474        int b_len = b_end - b_begin;
 475        int min = a_len < b_len ? a_len : b_len;
 476        int cmp;
 477
 478        cmp = memcmp(a_begin, b_begin, min);
 479        if (cmp)
 480                return cmp;
 481
 482        return a_len - b_len;
 483}
 484
 485int ident_cmp(const struct ident_split *a,
 486              const struct ident_split *b)
 487{
 488        int cmp;
 489
 490        cmp = buf_cmp(a->mail_begin, a->mail_end,
 491                      b->mail_begin, b->mail_end);
 492        if (cmp)
 493                return cmp;
 494
 495        return buf_cmp(a->name_begin, a->name_end,
 496                       b->name_begin, b->name_end);
 497}