1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #include "tig/tig.h"
15 #include "tig/util.h"
16
17 /*
18 * Error handling.
19 */
20
21 static const char *status_messages[] = {
22 "Success",
23 #define STATUS_CODE_MESSAGE(name, msg) msg
24 STATUS_CODE_INFO(STATUS_CODE_MESSAGE)
25 };
26
27 static char status_custom_message[SIZEOF_STR];
28 static bool status_success_message = false;
29
30 const char *
get_status_message(enum status_code code)31 get_status_message(enum status_code code)
32 {
33 if (code == SUCCESS) {
34 const char *message = status_success_message ? status_custom_message : "";
35
36 status_success_message = false;
37 return message;
38 }
39 if (code == ERROR_CUSTOM_MESSAGE)
40 return status_custom_message;
41 return status_messages[code];
42 }
43
44 enum status_code
error(const char * msg,...)45 error(const char *msg, ...)
46 {
47 int retval;
48
49 FORMAT_BUFFER(status_custom_message, sizeof(status_custom_message), msg, retval, true);
50 status_success_message = false;
51
52 return ERROR_CUSTOM_MESSAGE;
53 }
54
55 enum status_code
success(const char * msg,...)56 success(const char *msg, ...)
57 {
58 int retval;
59
60 FORMAT_BUFFER(status_custom_message, sizeof(status_custom_message), msg, retval, true);
61 status_success_message = true;
62
63 return SUCCESS;
64 }
65
66 void
warn(const char * msg,...)67 warn(const char *msg, ...)
68 {
69 va_list args;
70
71 va_start(args, msg);
72 fputs("tig warning: ", stderr);
73 vfprintf(stderr, msg, args);
74 fputs("\n", stderr);
75 va_end(args);
76 }
77
78 die_fn die_callback = NULL;
79 void TIG_NORETURN
die(const char * err,...)80 die(const char *err, ...)
81 {
82 va_list args;
83
84 if (die_callback)
85 die_callback();
86
87 va_start(args, err);
88 fputs("tig: ", stderr);
89 vfprintf(stderr, err, args);
90 fputs("\n", stderr);
91 va_end(args);
92
93 exit(1);
94 }
95
96 /*
97 * Git data formatters and parsers.
98 */
99
100 int
time_now(struct timeval * timeval,struct timezone * tz)101 time_now(struct timeval *timeval, struct timezone *tz)
102 {
103 static bool check_env = true;
104
105 if (check_env) {
106 const char *time;
107
108 if ((time = getenv("TEST_TIME_NOW"))) {
109 memset(timeval, 0, sizeof(*timeval));
110 if (tz)
111 memset(tz, 0, sizeof(*tz));
112 timeval->tv_sec = atoi(time);
113 return 0;
114 }
115
116 check_env = false;
117 }
118
119 return gettimeofday(timeval, tz);
120 }
121
122 int
timecmp(const struct time * t1,const struct time * t2)123 timecmp(const struct time *t1, const struct time *t2)
124 {
125 return t1->sec - t2->sec;
126 }
127
128 struct reldate {
129 const char *name;
130 const char compact_symbol;
131 int in_seconds, interval;
132 };
133
134 static const struct reldate reldate[] = {
135 { "second", 's', 1, 60 * 2 },
136 { "minute", 'm', 60, 60 * 60 * 2 },
137 { "hour", 'h', 60 * 60, 60 * 60 * 24 * 2 },
138 { "day", 'D', 60 * 60 * 24, 60 * 60 * 24 * 7 * 2 },
139 { "week", 'W', 60 * 60 * 24 * 7, 60 * 60 * 24 * 7 * 5 },
140 { "month", 'M', 60 * 60 * 24 * 30, 60 * 60 * 24 * 365 },
141 { "year", 'Y', 60 * 60 * 24 * 365, 0 },
142 };
143
144 static const char *
get_relative_date(const struct time * time,char * buf,size_t buflen,bool compact)145 get_relative_date(const struct time *time, char *buf, size_t buflen, bool compact)
146 {
147 struct timeval now;
148 time_t timestamp = time->sec + time->tz;
149 long long seconds;
150 int i;
151
152 if (time_now(&now, NULL))
153 return "";
154
155 seconds = now.tv_sec < timestamp ? (long long) difftime(timestamp, now.tv_sec) : (long long) difftime(now.tv_sec, timestamp);
156
157 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
158 if (seconds >= reldate[i].interval && reldate[i].interval)
159 continue;
160
161 seconds /= reldate[i].in_seconds;
162 if (compact) {
163 if (!string_nformat(buf, buflen, NULL, "%s%lld%c",
164 now.tv_sec >= timestamp ? "" : "-",
165 seconds, reldate[i].compact_symbol))
166 return "";
167
168 } else if (!string_nformat(buf, buflen, NULL, "%lld %s%s %s",
169 seconds, reldate[i].name,
170 seconds > 1 ? "s" : "",
171 now.tv_sec >= timestamp ? "ago" : "ahead"))
172 return "";
173
174 return buf;
175 }
176
177 return "";
178 }
179
180 const char *
mkdate(const struct time * time,enum date date,bool local,const char * custom_format)181 mkdate(const struct time *time, enum date date, bool local, const char *custom_format)
182 {
183 static char buf[SIZEOF_STR];
184 struct tm tm;
185 const char *format;
186 bool tz_fmt;
187
188 if (!date || !time || !time->sec)
189 return "";
190
191 if (date == DATE_RELATIVE || date == DATE_RELATIVE_COMPACT)
192 return get_relative_date(time, buf, sizeof(buf),
193 date == DATE_RELATIVE_COMPACT);
194
195 format = (date == DATE_CUSTOM && custom_format)
196 ? custom_format
197 : local
198 ? "%Y-%m-%d %H:%M"
199 : "%Y-%m-%d %H:%M %z";
200
201 tz_fmt = strstr(format, "%z") || strstr(format, "%Z");
202
203 if (local) {
204 time_t timestamp = time->sec + time->tz;
205
206 localtime_r(×tamp, &tm);
207 } else {
208 gmtime_r(&time->sec, &tm);
209 }
210
211 if (local || (!tz_fmt))
212 return !strftime(buf, sizeof(buf), format, &tm) ? NULL : buf;
213
214 {
215 char format_buf[SIZEOF_STR];
216 char *format_pos = format_buf;
217 char *buf_pos = buf;
218 size_t buf_size = sizeof(buf);
219 int tz = ABS(time->tz);
220
221 string_ncopy(format_buf, format, strlen(format));
222
223 while (*format_pos) {
224 char *z_pos = strstr(format_pos, "%z");
225 char *Z_pos = strstr(format_pos, "%Z");
226 char *tz_pos = (z_pos && Z_pos) ? MIN(z_pos, Z_pos) : MAX(z_pos, Z_pos);
227 size_t time_len;
228
229 if (tz_pos)
230 *tz_pos = 0;
231
232 time_len = strftime(buf_pos, buf_size, format_pos, &tm);
233 if (!time_len)
234 return NULL;
235
236 buf_pos += time_len;
237 buf_size -= time_len;
238
239 if (!tz_pos)
240 break;
241
242 /* Skip the %z format flag and insert the timezone. */
243 format_pos = tz_pos + 2;
244
245 if (buf_size < 5)
246 return NULL;
247
248 buf_pos[0] = time->tz > 0 ? '-' : '+';
249 buf_pos[1] = '0' + (tz / 60 / 60 / 10);
250 buf_pos[2] = '0' + (tz / 60 / 60 % 10);
251 buf_pos[3] = '0' + (tz / 60 % 60 / 10);
252 buf_pos[4] = '0' + (tz / 60 % 60 % 10);
253 buf_pos[5] = 0;
254
255 buf_pos += 5;
256 buf_size -= 5;
257 }
258
259 #undef buf_size
260 }
261
262 return buf;
263 }
264
265 const char *
mkfilesize(unsigned long size,enum file_size format)266 mkfilesize(unsigned long size, enum file_size format)
267 {
268 static char buf[64 + 1];
269 static const char relsize[] = {
270 'B', 'K', 'M', 'G', 'T', 'P'
271 };
272
273 if (!format)
274 return "";
275
276 if (format == FILE_SIZE_UNITS) {
277 const char *fmt = "%.0f%c";
278 double rsize = size;
279 int i;
280
281 for (i = 0; i < ARRAY_SIZE(relsize); i++) {
282 if (rsize > 1024.0 && i + 1 < ARRAY_SIZE(relsize)) {
283 rsize /= 1024;
284 continue;
285 }
286
287 size = rsize * 10;
288 if (size % 10 > 0)
289 fmt = "%.1f%c";
290
291 return string_format(buf, fmt, rsize, relsize[i])
292 ? buf : NULL;
293 }
294 }
295
296 return string_format(buf, "%lu", size) ? buf : NULL;
297 }
298
299 const struct ident unknown_ident = {
300 "unknown@localhostUnknown", // key
301 "Unknown", // name
302 "unknown@localhost" // email
303 };
304
305 int
ident_compare(const struct ident * i1,const struct ident * i2)306 ident_compare(const struct ident *i1, const struct ident *i2)
307 {
308 if (!i1 || !i2)
309 return (!!i1) - (!!i2);
310 if (!i1->name || !i2->name)
311 return (!!i1->name) - (!!i2->name);
312 return strcmp(i1->name, i2->name);
313 }
314
315 static const char *
get_author_initials(const char * author)316 get_author_initials(const char *author)
317 {
318 static char initials[256];
319 size_t pos = 0;
320 const char *end = strchr(author, '\0');
321
322 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
323
324 memset(initials, 0, sizeof(initials));
325 while (author < end) {
326 unsigned char bytes;
327 size_t i;
328
329 while (author < end && is_initial_sep(*author))
330 author++;
331
332 bytes = utf8_char_length(author);
333 if (bytes >= sizeof(initials) - 1 - pos)
334 break;
335 while (bytes--) {
336 initials[pos++] = *author++;
337 }
338
339 i = pos;
340 while (author < end && !is_initial_sep(*author)) {
341 bytes = utf8_char_length(author);
342 if (bytes >= sizeof(initials) - 1 - i) {
343 while (author < end && !is_initial_sep(*author))
344 author++;
345 break;
346 }
347 while (bytes--) {
348 initials[i++] = *author++;
349 }
350 }
351
352 initials[i++] = 0;
353 }
354
355 return initials;
356 }
357
358 static const char *
get_email_user(const char * email)359 get_email_user(const char *email)
360 {
361 static char user[SIZEOF_STR + 1];
362 const char *end = strchr(email, '@');
363 int length = end ? end - email : strlen(email);
364
365 string_format(user, "%.*s%c", length, email, 0);
366 return user;
367 }
368
369 const char *
mkauthor(const struct ident * ident,int cols,enum author author)370 mkauthor(const struct ident *ident, int cols, enum author author)
371 {
372 bool trim = author_trim(cols);
373 bool abbreviate = author == AUTHOR_ABBREVIATED || !trim;
374
375 if (author == AUTHOR_NO || !ident)
376 return "";
377 if (author == AUTHOR_EMAIL && ident->email)
378 return ident->email;
379 if (author == AUTHOR_EMAIL_USER && ident->email)
380 return get_email_user(ident->email);
381 if (abbreviate && ident->name)
382 return get_author_initials(ident->name);
383 return ident->name;
384 }
385
386 const char *
mkmode(mode_t mode)387 mkmode(mode_t mode)
388 {
389 if (S_ISDIR(mode))
390 return "drwxr-xr-x";
391 else if (S_ISLNK(mode))
392 return "lrwxrwxrwx";
393 else if (S_ISGITLINK(mode))
394 return "m---------";
395 else if (S_ISREG(mode) && mode & S_IXUSR)
396 return "-rwxr-xr-x";
397 else if (S_ISREG(mode))
398 return "-rw-r--r--";
399 else
400 return "----------";
401 }
402
403 const char *
mkstatus(const char status,enum status_label label)404 mkstatus(const char status, enum status_label label)
405 {
406 static char default_label[] = { '?', 0 };
407 static const char *labels[][2] = {
408 { "!", "ignored" },
409 { "?", "untracked" },
410 { "A", "added" },
411 { "C", "copied" },
412 { "D", "deleted" },
413 { "M", "modified" },
414 { "R", "renamed" },
415 { "U", "unmerged" },
416 };
417 int i;
418
419 if (label == STATUS_LABEL_NO)
420 return "";
421
422 for (i = 0; i < ARRAY_SIZE(labels); i++) {
423 if (status == *labels[i][0]) {
424 if (label == STATUS_LABEL_LONG)
425 return labels[i][1];
426 else
427 return labels[i][0];
428 }
429 }
430
431 default_label[0] = status;
432 return default_label;
433 }
434
435 /*
436 * Allocation helper.
437 */
438
439 void *
chunk_allocator(void * mem,size_t type_size,size_t chunk_size,size_t size,size_t increase)440 chunk_allocator(void *mem, size_t type_size, size_t chunk_size, size_t size, size_t increase)
441 {
442 size_t num_chunks = (size + chunk_size - 1) / chunk_size;
443 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;
444
445 if (mem == NULL || num_chunks != num_chunks_new) {
446 size_t newsize = num_chunks_new * chunk_size * type_size;
447 char *tmp = realloc(mem, newsize);
448
449 if (!tmp)
450 return NULL;
451
452 if (num_chunks_new > num_chunks) {
453 size_t oldsize = num_chunks * chunk_size * type_size;
454
455 memset(tmp + oldsize, 0, newsize - oldsize);
456 }
457
458 return tmp;
459 }
460
461 return mem;
462 }
463
464 /* vim: set ts=8 sw=8 noexpandtab: */
465