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(&timestamp, &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