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