733d69d92d9a2d46f5e8f64947507a87420638b1
   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 char git_default_date[50];
  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 user_ident_explicitly_given;
  18
  19#ifdef NO_GECOS_IN_PWENT
  20#define get_gecos(ignored) "&"
  21#else
  22#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
  23#endif
  24
  25static void copy_gecos(const struct passwd *w, struct strbuf *name)
  26{
  27        char *src;
  28
  29        /* Traditionally GECOS field had office phone numbers etc, separated
  30         * with commas.  Also & stands for capitalized form of the login name.
  31         */
  32
  33        for (src = get_gecos(w); *src && *src != ','; src++) {
  34                int ch = *src;
  35                if (ch != '&')
  36                        strbuf_addch(name, ch);
  37                else {
  38                        /* Sorry, Mr. McDonald... */
  39                        strbuf_addch(name, toupper(*w->pw_name));
  40                        strbuf_addstr(name, w->pw_name + 1);
  41                }
  42        }
  43}
  44
  45static int add_mailname_host(struct strbuf *buf)
  46{
  47        FILE *mailname;
  48
  49        mailname = fopen("/etc/mailname", "r");
  50        if (!mailname) {
  51                if (errno != ENOENT)
  52                        warning("cannot open /etc/mailname: %s",
  53                                strerror(errno));
  54                return -1;
  55        }
  56        if (strbuf_getline(buf, mailname, '\n') == EOF) {
  57                if (ferror(mailname))
  58                        warning("cannot read /etc/mailname: %s",
  59                                strerror(errno));
  60                fclose(mailname);
  61                return -1;
  62        }
  63        /* success! */
  64        fclose(mailname);
  65        return 0;
  66}
  67
  68static void add_domainname(struct strbuf *out)
  69{
  70        char buf[1024];
  71        struct hostent *he;
  72
  73        if (gethostname(buf, sizeof(buf))) {
  74                warning("cannot get host name: %s", strerror(errno));
  75                strbuf_addstr(out, "(none)");
  76                return;
  77        }
  78        if (strchr(buf, '.'))
  79                strbuf_addstr(out, buf);
  80        else if ((he = gethostbyname(buf)) && strchr(he->h_name, '.'))
  81                strbuf_addstr(out, he->h_name);
  82        else
  83                strbuf_addf(out, "%s.(none)", buf);
  84}
  85
  86static void copy_email(const struct passwd *pw, struct strbuf *email)
  87{
  88        /*
  89         * Make up a fake email address
  90         * (name + '@' + hostname [+ '.' + domainname])
  91         */
  92        strbuf_addstr(email, pw->pw_name);
  93        strbuf_addch(email, '@');
  94
  95        if (!add_mailname_host(email))
  96                return; /* read from "/etc/mailname" (Debian) */
  97        add_domainname(email);
  98}
  99
 100static const char *ident_default_name(void)
 101{
 102        if (!git_default_name.len) {
 103                copy_gecos(xgetpwuid_self(), &git_default_name);
 104                strbuf_trim(&git_default_name);
 105        }
 106        return git_default_name.buf;
 107}
 108
 109const char *ident_default_email(void)
 110{
 111        if (!git_default_email.len) {
 112                const char *email = getenv("EMAIL");
 113
 114                if (email && email[0]) {
 115                        strbuf_addstr(&git_default_email, email);
 116                        user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 117                } else
 118                        copy_email(xgetpwuid_self(), &git_default_email);
 119                strbuf_trim(&git_default_email);
 120        }
 121        return git_default_email.buf;
 122}
 123
 124static const char *ident_default_date(void)
 125{
 126        if (!git_default_date[0])
 127                datestamp(git_default_date, sizeof(git_default_date));
 128        return git_default_date;
 129}
 130
 131static int crud(unsigned char c)
 132{
 133        return  c <= 32  ||
 134                c == '.' ||
 135                c == ',' ||
 136                c == ':' ||
 137                c == ';' ||
 138                c == '<' ||
 139                c == '>' ||
 140                c == '"' ||
 141                c == '\\' ||
 142                c == '\'';
 143}
 144
 145/*
 146 * Copy over a string to the destination, but avoid special
 147 * characters ('\n', '<' and '>') and remove crud at the end
 148 */
 149static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)
 150{
 151        size_t i, len;
 152        unsigned char c;
 153
 154        /* Remove crud from the beginning.. */
 155        while ((c = *src) != 0) {
 156                if (!crud(c))
 157                        break;
 158                src++;
 159        }
 160
 161        /* Remove crud from the end.. */
 162        len = strlen(src);
 163        while (len > 0) {
 164                c = src[len-1];
 165                if (!crud(c))
 166                        break;
 167                --len;
 168        }
 169
 170        /*
 171         * Copy the rest to the buffer, but avoid the special
 172         * characters '\n' '<' and '>' that act as delimiters on
 173         * an identification line. We can only remove crud, never add it,
 174         * so 'len' is our maximum.
 175         */
 176        strbuf_grow(sb, len);
 177        for (i = 0; i < len; i++) {
 178                c = *src++;
 179                switch (c) {
 180                case '\n': case '<': case '>':
 181                        continue;
 182                }
 183                sb->buf[sb->len++] = c;
 184        }
 185        sb->buf[sb->len] = '\0';
 186}
 187
 188/*
 189 * Reverse of fmt_ident(); given an ident line, split the fields
 190 * to allow the caller to parse it.
 191 * Signal a success by returning 0, but date/tz fields of the result
 192 * can still be NULL if the input line only has the name/email part
 193 * (e.g. reading from a reflog entry).
 194 */
 195int split_ident_line(struct ident_split *split, const char *line, int len)
 196{
 197        const char *cp;
 198        size_t span;
 199        int status = -1;
 200
 201        memset(split, 0, sizeof(*split));
 202
 203        split->name_begin = line;
 204        for (cp = line; *cp && cp < line + len; cp++)
 205                if (*cp == '<') {
 206                        split->mail_begin = cp + 1;
 207                        break;
 208                }
 209        if (!split->mail_begin)
 210                return status;
 211
 212        for (cp = split->mail_begin - 2; line <= cp; cp--)
 213                if (!isspace(*cp)) {
 214                        split->name_end = cp + 1;
 215                        break;
 216                }
 217        if (!split->name_end) {
 218                /* no human readable name */
 219                split->name_end = split->name_begin;
 220        }
 221
 222        for (cp = split->mail_begin; cp < line + len; cp++)
 223                if (*cp == '>') {
 224                        split->mail_end = cp;
 225                        break;
 226                }
 227        if (!split->mail_end)
 228                return status;
 229
 230        for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++)
 231                ;
 232        if (line + len <= cp)
 233                goto person_only;
 234        split->date_begin = cp;
 235        span = strspn(cp, "0123456789");
 236        if (!span)
 237                goto person_only;
 238        split->date_end = split->date_begin + span;
 239        for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
 240                ;
 241        if (line + len <= cp || (*cp != '+' && *cp != '-'))
 242                goto person_only;
 243        split->tz_begin = cp;
 244        span = strspn(cp + 1, "0123456789");
 245        if (!span)
 246                goto person_only;
 247        split->tz_end = split->tz_begin + 1 + span;
 248        return 0;
 249
 250person_only:
 251        split->date_begin = NULL;
 252        split->date_end = NULL;
 253        split->tz_begin = NULL;
 254        split->tz_end = NULL;
 255        return 0;
 256}
 257
 258static const char *env_hint =
 259"\n"
 260"*** Please tell me who you are.\n"
 261"\n"
 262"Run\n"
 263"\n"
 264"  git config --global user.email \"you@example.com\"\n"
 265"  git config --global user.name \"Your Name\"\n"
 266"\n"
 267"to set your account\'s default identity.\n"
 268"Omit --global to set the identity only in this repository.\n"
 269"\n";
 270
 271const char *fmt_ident(const char *name, const char *email,
 272                      const char *date_str, int flag)
 273{
 274        static struct strbuf ident = STRBUF_INIT;
 275        char date[50];
 276        int strict = (flag & IDENT_STRICT);
 277        int want_date = !(flag & IDENT_NO_DATE);
 278        int want_name = !(flag & IDENT_NO_NAME);
 279
 280        if (want_name && !name)
 281                name = ident_default_name();
 282        if (!email)
 283                email = ident_default_email();
 284
 285        if (want_name && !*name) {
 286                struct passwd *pw;
 287
 288                if (strict) {
 289                        if (name == git_default_name.buf)
 290                                fputs(env_hint, stderr);
 291                        die("empty ident name (for <%s>) not allowed", email);
 292                }
 293                pw = xgetpwuid_self();
 294                name = pw->pw_name;
 295        }
 296
 297        if (strict && email == git_default_email.buf &&
 298            strstr(email, "(none)")) {
 299                fputs(env_hint, stderr);
 300                die("unable to auto-detect email address (got '%s')", email);
 301        }
 302
 303        if (want_date) {
 304                if (date_str && date_str[0]) {
 305                        if (parse_date(date_str, date, sizeof(date)) < 0)
 306                                die("invalid date format: %s", date_str);
 307                }
 308                else
 309                        strcpy(date, ident_default_date());
 310        }
 311
 312        strbuf_reset(&ident);
 313        if (want_name) {
 314                strbuf_addstr_without_crud(&ident, name);
 315                strbuf_addstr(&ident, " <");
 316        }
 317        strbuf_addstr_without_crud(&ident, email);
 318        if (want_name)
 319                        strbuf_addch(&ident, '>');
 320        if (want_date) {
 321                strbuf_addch(&ident, ' ');
 322                strbuf_addstr_without_crud(&ident, date);
 323        }
 324        return ident.buf;
 325}
 326
 327const char *fmt_name(const char *name, const char *email)
 328{
 329        return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE);
 330}
 331
 332const char *git_author_info(int flag)
 333{
 334        return fmt_ident(getenv("GIT_AUTHOR_NAME"),
 335                         getenv("GIT_AUTHOR_EMAIL"),
 336                         getenv("GIT_AUTHOR_DATE"),
 337                         flag);
 338}
 339
 340const char *git_committer_info(int flag)
 341{
 342        if (getenv("GIT_COMMITTER_NAME"))
 343                user_ident_explicitly_given |= IDENT_NAME_GIVEN;
 344        if (getenv("GIT_COMMITTER_EMAIL"))
 345                user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 346        return fmt_ident(getenv("GIT_COMMITTER_NAME"),
 347                         getenv("GIT_COMMITTER_EMAIL"),
 348                         getenv("GIT_COMMITTER_DATE"),
 349                         flag);
 350}
 351
 352int user_ident_sufficiently_given(void)
 353{
 354#ifndef WINDOWS
 355        return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
 356#else
 357        return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
 358#endif
 359}
 360
 361int git_ident_config(const char *var, const char *value, void *data)
 362{
 363        if (!strcmp(var, "user.name")) {
 364                if (!value)
 365                        return config_error_nonbool(var);
 366                strbuf_reset(&git_default_name);
 367                strbuf_addstr(&git_default_name, value);
 368                user_ident_explicitly_given |= IDENT_NAME_GIVEN;
 369                return 0;
 370        }
 371
 372        if (!strcmp(var, "user.email")) {
 373                if (!value)
 374                        return config_error_nonbool(var);
 375                strbuf_reset(&git_default_email);
 376                strbuf_addstr(&git_default_email, value);
 377                user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
 378                return 0;
 379        }
 380
 381        return 0;
 382}