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