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