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