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