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