]> Pileus Git - ~andy/git/blob - ident.c
ident: don't write fallback username into git_default_name
[~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 name_addr_only = (flag & IDENT_NO_DATE);
320
321         if (!name)
322                 name = ident_default_name();
323         if (!email)
324                 email = ident_default_email();
325
326         if (!*name) {
327                 struct passwd *pw;
328
329                 if (error_on_no_name) {
330                         if (name == git_default_name)
331                                 fputs(env_hint, stderr);
332                         die("empty ident %s <%s> not allowed", name, email);
333                 }
334                 pw = getpwuid(getuid());
335                 if (!pw)
336                         die("You don't exist. Go away!");
337                 name = pw->pw_name;
338         }
339
340         strcpy(date, ident_default_date());
341         if (!name_addr_only && date_str && date_str[0]) {
342                 if (parse_date(date_str, date, sizeof(date)) < 0)
343                         die("invalid date format: %s", date_str);
344         }
345
346         i = copy(buffer, sizeof(buffer), 0, name);
347         i = add_raw(buffer, sizeof(buffer), i, " <");
348         i = copy(buffer, sizeof(buffer), i, email);
349         if (!name_addr_only) {
350                 i = add_raw(buffer, sizeof(buffer), i,  "> ");
351                 i = copy(buffer, sizeof(buffer), i, date);
352         } else {
353                 i = add_raw(buffer, sizeof(buffer), i, ">");
354         }
355         if (i >= sizeof(buffer))
356                 die("Impossibly long personal identifier");
357         buffer[i] = 0;
358         return buffer;
359 }
360
361 const char *fmt_name(const char *name, const char *email)
362 {
363         return fmt_ident(name, email, NULL, IDENT_ERROR_ON_NO_NAME | IDENT_NO_DATE);
364 }
365
366 const char *git_author_info(int flag)
367 {
368         return fmt_ident(getenv("GIT_AUTHOR_NAME"),
369                          getenv("GIT_AUTHOR_EMAIL"),
370                          getenv("GIT_AUTHOR_DATE"),
371                          flag);
372 }
373
374 const char *git_committer_info(int flag)
375 {
376         if (getenv("GIT_COMMITTER_NAME"))
377                 user_ident_explicitly_given |= IDENT_NAME_GIVEN;
378         if (getenv("GIT_COMMITTER_EMAIL"))
379                 user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
380         return fmt_ident(getenv("GIT_COMMITTER_NAME"),
381                          getenv("GIT_COMMITTER_EMAIL"),
382                          getenv("GIT_COMMITTER_DATE"),
383                          flag);
384 }
385
386 int user_ident_sufficiently_given(void)
387 {
388 #ifndef WINDOWS
389         return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
390 #else
391         return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
392 #endif
393 }
394
395 int git_ident_config(const char *var, const char *value, void *data)
396 {
397         if (!strcmp(var, "user.name")) {
398                 if (!value)
399                         return config_error_nonbool(var);
400                 strlcpy(git_default_name, value, sizeof(git_default_name));
401                 user_ident_explicitly_given |= IDENT_NAME_GIVEN;
402                 return 0;
403         }
404
405         if (!strcmp(var, "user.email")) {
406                 if (!value)
407                         return config_error_nonbool(var);
408                 strlcpy(git_default_email, value, sizeof(git_default_email));
409                 user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
410                 return 0;
411         }
412
413         return 0;
414 }