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