]> Pileus Git - ~andy/git/blob - ident.c
ident: use a dynamic strbuf in fmt_ident
[~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
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
82 static 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
96 const char *ident_default_name(void)
97 {
98         if (!git_default_name.len)
99                 copy_gecos(xgetpwuid_self(), &git_default_name);
100         return git_default_name.buf;
101 }
102
103 const char *ident_default_email(void)
104 {
105         if (!git_default_email.len) {
106                 const char *email = getenv("EMAIL");
107
108                 if (email && email[0]) {
109                         strbuf_addstr(&git_default_email, email);
110                         user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
111                 } else
112                         copy_email(xgetpwuid_self(), &git_default_email);
113         }
114         return git_default_email.buf;
115 }
116
117 const char *ident_default_date(void)
118 {
119         if (!git_default_date[0])
120                 datestamp(git_default_date, sizeof(git_default_date));
121         return git_default_date;
122 }
123
124 static int crud(unsigned char c)
125 {
126         return  c <= 32  ||
127                 c == '.' ||
128                 c == ',' ||
129                 c == ':' ||
130                 c == ';' ||
131                 c == '<' ||
132                 c == '>' ||
133                 c == '"' ||
134                 c == '\\' ||
135                 c == '\'';
136 }
137
138 /*
139  * Copy over a string to the destination, but avoid special
140  * characters ('\n', '<' and '>') and remove crud at the end
141  */
142 static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src)
143 {
144         size_t i, len;
145         unsigned char c;
146
147         /* Remove crud from the beginning.. */
148         while ((c = *src) != 0) {
149                 if (!crud(c))
150                         break;
151                 src++;
152         }
153
154         /* Remove crud from the end.. */
155         len = strlen(src);
156         while (len > 0) {
157                 c = src[len-1];
158                 if (!crud(c))
159                         break;
160                 --len;
161         }
162
163         /*
164          * Copy the rest to the buffer, but avoid the special
165          * characters '\n' '<' and '>' that act as delimiters on
166          * an identification line. We can only remove crud, never add it,
167          * so 'len' is our maximum.
168          */
169         strbuf_grow(sb, len);
170         for (i = 0; i < len; i++) {
171                 c = *src++;
172                 switch (c) {
173                 case '\n': case '<': case '>':
174                         continue;
175                 }
176                 sb->buf[sb->len++] = c;
177         }
178         sb->buf[sb->len] = '\0';
179 }
180
181 /*
182  * Reverse of fmt_ident(); given an ident line, split the fields
183  * to allow the caller to parse it.
184  * Signal a success by returning 0, but date/tz fields of the result
185  * can still be NULL if the input line only has the name/email part
186  * (e.g. reading from a reflog entry).
187  */
188 int split_ident_line(struct ident_split *split, const char *line, int len)
189 {
190         const char *cp;
191         size_t span;
192         int status = -1;
193
194         memset(split, 0, sizeof(*split));
195
196         split->name_begin = line;
197         for (cp = line; *cp && cp < line + len; cp++)
198                 if (*cp == '<') {
199                         split->mail_begin = cp + 1;
200                         break;
201                 }
202         if (!split->mail_begin)
203                 return status;
204
205         for (cp = split->mail_begin - 2; line < cp; cp--)
206                 if (!isspace(*cp)) {
207                         split->name_end = cp + 1;
208                         break;
209                 }
210         if (!split->name_end)
211                 return status;
212
213         for (cp = split->mail_begin; cp < line + len; cp++)
214                 if (*cp == '>') {
215                         split->mail_end = cp;
216                         break;
217                 }
218         if (!split->mail_end)
219                 return status;
220
221         for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++)
222                 ;
223         if (line + len <= cp)
224                 goto person_only;
225         split->date_begin = cp;
226         span = strspn(cp, "0123456789");
227         if (!span)
228                 goto person_only;
229         split->date_end = split->date_begin + span;
230         for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
231                 ;
232         if (line + len <= cp || (*cp != '+' && *cp != '-'))
233                 goto person_only;
234         split->tz_begin = cp;
235         span = strspn(cp + 1, "0123456789");
236         if (!span)
237                 goto person_only;
238         split->tz_end = split->tz_begin + 1 + span;
239         return 0;
240
241 person_only:
242         split->date_begin = NULL;
243         split->date_end = NULL;
244         split->tz_begin = NULL;
245         split->tz_end = NULL;
246         return 0;
247 }
248
249 static const char *env_hint =
250 "\n"
251 "*** Please tell me who you are.\n"
252 "\n"
253 "Run\n"
254 "\n"
255 "  git config --global user.email \"you@example.com\"\n"
256 "  git config --global user.name \"Your Name\"\n"
257 "\n"
258 "to set your account\'s default identity.\n"
259 "Omit --global to set the identity only in this repository.\n"
260 "\n";
261
262 const char *fmt_ident(const char *name, const char *email,
263                       const char *date_str, int flag)
264 {
265         static struct strbuf ident = STRBUF_INIT;
266         char date[50];
267         int error_on_no_name = (flag & IDENT_ERROR_ON_NO_NAME);
268         int name_addr_only = (flag & IDENT_NO_DATE);
269
270         if (!name)
271                 name = ident_default_name();
272         if (!email)
273                 email = ident_default_email();
274
275         if (!*name) {
276                 struct passwd *pw;
277
278                 if (error_on_no_name) {
279                         if (name == git_default_name.buf)
280                                 fputs(env_hint, stderr);
281                         die("empty ident %s <%s> not allowed", name, email);
282                 }
283                 pw = xgetpwuid_self();
284                 name = pw->pw_name;
285         }
286
287         strcpy(date, ident_default_date());
288         if (!name_addr_only && date_str && date_str[0]) {
289                 if (parse_date(date_str, date, sizeof(date)) < 0)
290                         die("invalid date format: %s", date_str);
291         }
292
293         strbuf_reset(&ident);
294         strbuf_addstr_without_crud(&ident, name);
295         strbuf_addstr(&ident, " <");
296         strbuf_addstr_without_crud(&ident, email);
297         strbuf_addch(&ident, '>');
298         if (!name_addr_only) {
299                 strbuf_addch(&ident, ' ');
300                 strbuf_addstr_without_crud(&ident, date);
301         }
302         return ident.buf;
303 }
304
305 const char *fmt_name(const char *name, const char *email)
306 {
307         return fmt_ident(name, email, NULL, IDENT_ERROR_ON_NO_NAME | IDENT_NO_DATE);
308 }
309
310 const char *git_author_info(int flag)
311 {
312         return fmt_ident(getenv("GIT_AUTHOR_NAME"),
313                          getenv("GIT_AUTHOR_EMAIL"),
314                          getenv("GIT_AUTHOR_DATE"),
315                          flag);
316 }
317
318 const char *git_committer_info(int flag)
319 {
320         if (getenv("GIT_COMMITTER_NAME"))
321                 user_ident_explicitly_given |= IDENT_NAME_GIVEN;
322         if (getenv("GIT_COMMITTER_EMAIL"))
323                 user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
324         return fmt_ident(getenv("GIT_COMMITTER_NAME"),
325                          getenv("GIT_COMMITTER_EMAIL"),
326                          getenv("GIT_COMMITTER_DATE"),
327                          flag);
328 }
329
330 int user_ident_sufficiently_given(void)
331 {
332 #ifndef WINDOWS
333         return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
334 #else
335         return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
336 #endif
337 }
338
339 int git_ident_config(const char *var, const char *value, void *data)
340 {
341         if (!strcmp(var, "user.name")) {
342                 if (!value)
343                         return config_error_nonbool(var);
344                 strbuf_reset(&git_default_name);
345                 strbuf_addstr(&git_default_name, value);
346                 user_ident_explicitly_given |= IDENT_NAME_GIVEN;
347                 return 0;
348         }
349
350         if (!strcmp(var, "user.email")) {
351                 if (!value)
352                         return config_error_nonbool(var);
353                 strbuf_reset(&git_default_email);
354                 strbuf_addstr(&git_default_email, value);
355                 user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
356                 return 0;
357         }
358
359         return 0;
360 }