1 /* $Id$ */
2 /* Copyright (c) 2011-2015 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Browser */
4 /* Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions and the following disclaimer in the documentation
13  *    and/or other materials provided with the distribution.
14  *
15  * 3. Neither the name of the authors nor the names of the contributors may be
16  *    used to endorse or promote products derived from this software without
17  *    specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
29 
30 
31 
32 #include <System.h>
33 #include <sys/stat.h>
34 #include <sys/wait.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <errno.h>
38 #include "common.c"
39 
40 
41 /* CVS */
42 /* private */
43 /* types */
44 typedef struct _CommonTask CVSTask;
45 
46 typedef struct _BrowserPlugin
47 {
48 	BrowserPluginHelper * helper;
49 
50 	char * filename;
51 
52 	guint source;
53 
54 	/* widgets */
55 	GtkWidget * widget;
56 	GtkWidget * name;
57 	GtkWidget * status;
58 	/* checkout */
59 	GtkWidget * checkout;
60 	/* directory */
61 	GtkWidget * directory;
62 	GtkWidget * d_root;
63 	GtkWidget * d_repository;
64 	GtkWidget * d_tag;
65 	/* file */
66 	GtkWidget * file;
67 	GtkWidget * f_revision;
68 	/* additional actions */
69 	GtkWidget * add;
70 
71 	/* tasks */
72 	CVSTask ** tasks;
73 	size_t tasks_cnt;
74 } CVS;
75 
76 
77 /* constants */
78 #define CVS_CVS		"cvs"
79 
80 
81 /* prototypes */
82 static CVS * _cvs_init(BrowserPluginHelper * helper);
83 static void _cvs_destroy(CVS * cvs);
84 static GtkWidget * _cvs_get_widget(CVS * cvs);
85 static void _cvs_refresh(CVS * cvs, GList * selection);
86 
87 /* accessors */
88 static char * _cvs_get_entries(char const * pathname);
89 static char * _cvs_get_repository(char const * pathname);
90 static char * _cvs_get_root(char const * pathname);
91 static char * _cvs_get_tag(char const * pathname);
92 static gboolean _cvs_is_managed(char const * filename, char ** revision);
93 
94 /* useful */
95 static int _cvs_add_task(CVS * cvs, char const * title, char const * directory,
96 		char * argv[], CommonTaskCallback callback);
97 static int _cvs_confirm_delete(char const * filename);
98 static GtkResponseType _cvs_prompt_checkout(char const * message, char ** path,
99 		char ** module);
100 
101 /* callbacks */
102 static void _cvs_on_add(gpointer data);
103 static void _cvs_on_annotate(gpointer data);
104 static void _cvs_on_checkout(gpointer data);
105 static void _cvs_on_commit(gpointer data);
106 static void _cvs_on_delete(gpointer data);
107 static void _cvs_on_diff(gpointer data);
108 static void _cvs_on_log(gpointer data);
109 static void _cvs_on_status(gpointer data);
110 static void _cvs_on_update(gpointer data);
111 
112 
113 /* public */
114 /* variables */
115 /* plug-in */
116 BrowserPluginDefinition plugin =
117 {
118 	N_("CVS"),
119 	"applications-development",
120 	NULL,
121 	_cvs_init,
122 	_cvs_destroy,
123 	_cvs_get_widget,
124 	_cvs_refresh
125 };
126 
127 
128 /* private */
129 /* functions */
130 /* cvs_init */
131 static GtkWidget * _init_button(GtkSizeGroup * group, char const * icon,
132 		char const * label, GCallback callback, gpointer data);
133 static GtkWidget * _init_label(GtkSizeGroup * group, char const * label,
134 		GtkWidget ** widget);
135 
_cvs_init(BrowserPluginHelper * helper)136 static CVS * _cvs_init(BrowserPluginHelper * helper)
137 {
138 	CVS * cvs;
139 	PangoFontDescription * font;
140 	GtkSizeGroup * group;
141 	GtkSizeGroup * bgroup;
142 	GtkWidget * widget;
143 
144 	if((cvs = object_new(sizeof(*cvs))) == NULL)
145 		return NULL;
146 	cvs->helper = helper;
147 	cvs->filename = NULL;
148 	cvs->source = 0;
149 	/* widgets */
150 	cvs->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
151 	font = pango_font_description_new();
152 	pango_font_description_set_weight(font, PANGO_WEIGHT_BOLD);
153 	group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
154 	bgroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
155 	/* label */
156 	cvs->name = gtk_label_new("");
157 	gtk_label_set_ellipsize(GTK_LABEL(cvs->name), PANGO_ELLIPSIZE_MIDDLE);
158 #if GTK_CHECK_VERSION(3, 0, 0)
159 	gtk_widget_override_font(cvs->name, font);
160 	g_object_set(cvs->name, "halign", GTK_ALIGN_START, NULL);
161 #else
162 	gtk_widget_modify_font(cvs->name, font);
163 	gtk_misc_set_alignment(GTK_MISC(cvs->name), 0.0, 0.5);
164 #endif
165 	gtk_box_pack_start(GTK_BOX(cvs->widget), cvs->name, FALSE, TRUE, 0);
166 	cvs->status = gtk_label_new("");
167 	gtk_label_set_ellipsize(GTK_LABEL(cvs->status), PANGO_ELLIPSIZE_END);
168 #if GTK_CHECK_VERSION(3, 0, 0)
169 	g_object_set(cvs->status, "halign", GTK_ALIGN_START, NULL);
170 #else
171 	gtk_misc_set_alignment(GTK_MISC(cvs->status), 0.0, 0.5);
172 #endif
173 	gtk_box_pack_start(GTK_BOX(cvs->widget), cvs->status, FALSE, TRUE, 0);
174 	/* checkout */
175 	cvs->checkout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
176 	widget = _init_button(bgroup, GTK_STOCK_OK, _("Checkout..."),
177 			G_CALLBACK(_cvs_on_checkout), cvs);
178 	gtk_box_pack_start(GTK_BOX(cvs->checkout), widget, FALSE, TRUE, 0);
179 	gtk_widget_show_all(cvs->checkout);
180 	gtk_widget_set_no_show_all(cvs->checkout, TRUE);
181 	gtk_box_pack_start(GTK_BOX(cvs->widget), cvs->checkout, FALSE, TRUE, 0);
182 	/* directory */
183 	cvs->directory = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
184 	widget = _init_label(group, _("Root:"), &cvs->d_root);
185 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
186 	widget = _init_label(group, _("Repository:"), &cvs->d_repository);
187 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
188 	widget = _init_label(group, _("Tag:"), &cvs->d_tag);
189 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
190 	widget = _init_button(bgroup, GTK_STOCK_FIND_AND_REPLACE,
191 			_("Request diff"), G_CALLBACK(_cvs_on_diff), cvs);
192 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
193 	widget = _init_button(bgroup, GTK_STOCK_INDEX, _("Annotate"),
194 			G_CALLBACK(_cvs_on_annotate), cvs);
195 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
196 	widget = _init_button(bgroup, GTK_STOCK_FIND, _("View log"),
197 			G_CALLBACK(_cvs_on_log), cvs);
198 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
199 	widget = _init_button(bgroup, GTK_STOCK_PROPERTIES, _("Status"),
200 			G_CALLBACK(_cvs_on_status), cvs);
201 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
202 	widget = _init_button(bgroup, GTK_STOCK_REFRESH, _("Update"),
203 			G_CALLBACK(_cvs_on_update), cvs);
204 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
205 	widget = _init_button(bgroup, GTK_STOCK_DELETE, _("Delete"),
206 			G_CALLBACK(_cvs_on_delete), cvs);
207 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
208 	widget = _init_button(bgroup, GTK_STOCK_JUMP_TO, _("Commit"),
209 			G_CALLBACK(_cvs_on_commit), cvs);
210 	gtk_box_pack_start(GTK_BOX(cvs->directory), widget, FALSE, TRUE, 0);
211 	gtk_widget_show_all(cvs->directory);
212 	gtk_widget_set_no_show_all(cvs->directory, TRUE);
213 	gtk_box_pack_start(GTK_BOX(cvs->widget), cvs->directory, FALSE, TRUE,
214 			0);
215 	/* file */
216 	cvs->file = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
217 	widget = _init_label(group, _("Revision:"), &cvs->f_revision);
218 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
219 	widget = _init_button(bgroup, GTK_STOCK_FIND_AND_REPLACE,
220 			_("Request diff"), G_CALLBACK(_cvs_on_diff), cvs);
221 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
222 	widget = _init_button(bgroup, GTK_STOCK_INDEX, _("Annotate"),
223 			G_CALLBACK(_cvs_on_annotate), cvs);
224 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
225 	widget = _init_button(bgroup, GTK_STOCK_FIND, _("View log"),
226 			G_CALLBACK(_cvs_on_log), cvs);
227 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
228 	widget = _init_button(bgroup, GTK_STOCK_PROPERTIES, _("Status"),
229 			G_CALLBACK(_cvs_on_status), cvs);
230 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
231 	widget = _init_button(bgroup, GTK_STOCK_REFRESH, _("Update"),
232 			G_CALLBACK(_cvs_on_update), cvs);
233 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
234 	widget = _init_button(bgroup, GTK_STOCK_DELETE, _("Delete"),
235 			G_CALLBACK(_cvs_on_delete), cvs);
236 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
237 	widget = _init_button(bgroup, GTK_STOCK_JUMP_TO, _("Commit"),
238 			G_CALLBACK(_cvs_on_commit), cvs);
239 	gtk_box_pack_start(GTK_BOX(cvs->file), widget, FALSE, TRUE, 0);
240 	gtk_widget_show_all(cvs->file);
241 	gtk_widget_set_no_show_all(cvs->file, TRUE);
242 	gtk_box_pack_start(GTK_BOX(cvs->widget), cvs->file, FALSE, TRUE, 0);
243 	/* additional actions */
244 	cvs->add = _init_button(bgroup, GTK_STOCK_ADD, _("Add to repository"),
245 			G_CALLBACK(_cvs_on_add), cvs);
246 	gtk_box_pack_start(GTK_BOX(cvs->widget), cvs->add, FALSE, TRUE, 0);
247 	gtk_widget_show_all(cvs->widget);
248 	pango_font_description_free(font);
249 	/* tasks */
250 	cvs->tasks = NULL;
251 	cvs->tasks_cnt = 0;
252 	return cvs;
253 }
254 
_init_button(GtkSizeGroup * group,char const * icon,char const * label,GCallback callback,gpointer data)255 static GtkWidget * _init_button(GtkSizeGroup * group, char const * icon,
256 		char const * label, GCallback callback, gpointer data)
257 {
258 	GtkWidget * hbox;
259 	GtkWidget * image;
260 	GtkWidget * widget;
261 	char const stock[] = "gtk-";
262 
263 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
264 	widget = gtk_button_new_with_label(label);
265 	gtk_size_group_add_widget(group, widget);
266 	if(icon != NULL)
267 	{
268 		if(strncmp(icon, stock, sizeof(stock) - 1) == 0)
269 			image = gtk_image_new_from_stock(icon,
270 					GTK_ICON_SIZE_BUTTON);
271 		else
272 			image = gtk_image_new_from_icon_name(icon,
273 					GTK_ICON_SIZE_BUTTON);
274 		gtk_button_set_image(GTK_BUTTON(widget), image);
275 	}
276 	g_signal_connect_swapped(widget, "clicked", callback, data);
277 	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
278 	return hbox;
279 }
280 
_init_label(GtkSizeGroup * group,char const * label,GtkWidget ** widget)281 static GtkWidget * _init_label(GtkSizeGroup * group, char const * label,
282 		GtkWidget ** widget)
283 {
284 	GtkWidget * hbox;
285 
286 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
287 	*widget = gtk_label_new(label);
288 #if GTK_CHECK_VERSION(3, 0, 0)
289 	g_object_set(*widget, "halign", GTK_ALIGN_START, NULL);
290 #else
291 	gtk_misc_set_alignment(GTK_MISC(*widget), 0.0, 0.5);
292 #endif
293 	gtk_size_group_add_widget(group, *widget);
294 	gtk_box_pack_start(GTK_BOX(hbox), *widget, FALSE, TRUE, 0);
295 	*widget = gtk_label_new("");
296 	gtk_label_set_ellipsize(GTK_LABEL(*widget), PANGO_ELLIPSIZE_MIDDLE);
297 #if GTK_CHECK_VERSION(3, 0, 0)
298 	g_object_set(*widget, "halign", GTK_ALIGN_START, NULL);
299 #else
300 	gtk_misc_set_alignment(GTK_MISC(*widget), 0.0, 0.5);
301 #endif
302 	gtk_box_pack_start(GTK_BOX(hbox), *widget, TRUE, TRUE, 0);
303 	return hbox;
304 }
305 
306 
307 /* cvs_destroy */
_cvs_destroy(CVS * cvs)308 static void _cvs_destroy(CVS * cvs)
309 {
310 	size_t i;
311 
312 	for(i = 0; i < cvs->tasks_cnt; i++)
313 		_common_task_delete(cvs->tasks[i]);
314 	free(cvs->tasks);
315 	if(cvs->source != 0)
316 		g_source_remove(cvs->source);
317 	object_delete(cvs);
318 }
319 
320 
321 /* cvs_get_widget */
_cvs_get_widget(CVS * cvs)322 static GtkWidget * _cvs_get_widget(CVS * cvs)
323 {
324 	return cvs->widget;
325 }
326 
327 
328 /* cvs_refresh */
329 static void _refresh_dir(CVS * cvs);
330 static void _refresh_error(CVS * cvs, char const * message);
331 static void _refresh_file(CVS * cvs);
332 static void _refresh_hide(CVS * cvs, gboolean name);
333 static void _refresh_status(CVS * cvs, char const * status);
334 
_cvs_refresh(CVS * cvs,GList * selection)335 static void _cvs_refresh(CVS * cvs, GList * selection)
336 {
337 	char * path = (selection != NULL) ? selection->data : NULL;
338 	struct stat st;
339 	gchar * p;
340 
341 	if(cvs->source != 0)
342 		g_source_remove(cvs->source);
343 	free(cvs->filename);
344 	cvs->filename = NULL;
345 	if(path == NULL || selection->next != NULL)
346 	{
347 		_refresh_hide(cvs, TRUE);
348 		return;
349 	}
350 	if(lstat(path, &st) != 0
351 			|| (cvs->filename = strdup(path)) == NULL)
352 	{
353 		_refresh_hide(cvs, TRUE);
354 		if(errno != ENOENT)
355 			_refresh_error(cvs, path);
356 		return;
357 	}
358 	p = g_filename_display_basename(path);
359 	gtk_label_set_text(GTK_LABEL(cvs->name), p);
360 	g_free(p);
361 	_refresh_hide(cvs, FALSE);
362 	if(S_ISDIR(st.st_mode))
363 		_refresh_dir(cvs);
364 	else
365 		_refresh_file(cvs);
366 }
367 
_refresh_dir(CVS * cvs)368 static void _refresh_dir(CVS * cvs)
369 {
370 	BrowserPluginHelper * helper = cvs->helper;
371 	char const dir[] = "CVS";
372 	size_t len = strlen(cvs->filename);
373 	char * p = NULL;
374 	struct stat st;
375 	gchar * q;
376 	char const * filename = cvs->filename;
377 
378 	/* reset the interface */
379 	gtk_label_set_text(GTK_LABEL(cvs->d_root), NULL);
380 	gtk_label_set_text(GTK_LABEL(cvs->d_repository), NULL);
381 	gtk_label_set_text(GTK_LABEL(cvs->d_tag), NULL);
382 	/* consider "CVS" folders as managed */
383 	if((len = strlen(filename)) >= 4
384 			&& strcmp(&filename[len - 4], "/CVS") == 0)
385 	{
386 		if((p = strdup(filename)) != NULL)
387 		{
388 			p[len - 4] = '\0';
389 			filename = p;
390 		}
391 	}
392 	/* check if it is a CVS repository */
393 	else
394 	{
395 		len = strlen(filename) + sizeof(dir) + 1;
396 		if((p = malloc(len)) == NULL)
397 		{
398 			helper->error(helper->browser, strerror(errno), 1);
399 			return;
400 		}
401 		snprintf(p, len, "%s/%s", filename, dir);
402 		if(lstat(p, &st) != 0)
403 		{
404 			/* check if the parent folder is managed */
405 			if(_cvs_is_managed(filename, NULL) == FALSE)
406 			{
407 				_refresh_status(cvs, _("Not a CVS repository"));
408 				gtk_widget_show(cvs->checkout);
409 			}
410 			else
411 			{
412 				_refresh_status(cvs, _("Not managed by CVS"));
413 				gtk_widget_show(cvs->add);
414 			}
415 			free(p);
416 			return;
417 		}
418 	}
419 	/* this folder is managed */
420 	gtk_widget_show(cvs->directory);
421 	/* obtain the CVS root */
422 	if((q = _cvs_get_root(filename)) != NULL)
423 	{
424 		gtk_label_set_text(GTK_LABEL(cvs->d_root), q);
425 		free(q);
426 	}
427 	/* obtain the CVS repository */
428 	if((q = _cvs_get_repository(filename)) != NULL)
429 	{
430 		gtk_label_set_text(GTK_LABEL(cvs->d_repository), q);
431 		free(q);
432 	}
433 	/* obtain the default CVS tag (if set) */
434 	if((q = _cvs_get_tag(filename)) != NULL)
435 	{
436 		if(q[0] == 'T' && q[1] != '\0')
437 			gtk_label_set_text(GTK_LABEL(cvs->d_tag), &q[1]);
438 		g_free(q);
439 	}
440 	free(p);
441 }
442 
_refresh_error(CVS * cvs,char const * message)443 static void _refresh_error(CVS * cvs, char const * message)
444 {
445 	BrowserPluginHelper * helper = cvs->helper;
446 
447 	error_set("%s: %s", message, strerror(errno));
448 	helper->error(helper->browser, error_get(NULL), 1);
449 }
450 
_refresh_file(CVS * cvs)451 static void _refresh_file(CVS * cvs)
452 {
453 	char * revision = NULL;
454 
455 	/* reset the interface */
456 	gtk_label_set_text(GTK_LABEL(cvs->f_revision), NULL);
457 	/* check if it is managed */
458 	if(_cvs_is_managed(cvs->filename, &revision) == FALSE)
459 		_refresh_status(cvs, _("Not a CVS repository"));
460 	else if(revision == NULL)
461 	{
462 		gtk_widget_show(cvs->add);
463 		_refresh_status(cvs, _("Not managed by CVS"));
464 	}
465 	else
466 	{
467 		gtk_widget_show(cvs->file);
468 		if(revision != NULL)
469 		{
470 			gtk_label_set_text(GTK_LABEL(cvs->f_revision),
471 					revision);
472 			free(revision);
473 		}
474 	}
475 }
476 
_refresh_hide(CVS * cvs,gboolean name)477 static void _refresh_hide(CVS * cvs, gboolean name)
478 {
479 	name ? gtk_widget_hide(cvs->name) : gtk_widget_show(cvs->name);
480 	_refresh_status(cvs, NULL);
481 	gtk_widget_hide(cvs->checkout);
482 	gtk_widget_hide(cvs->directory);
483 	gtk_widget_hide(cvs->file);
484 	gtk_widget_hide(cvs->add);
485 }
486 
_refresh_status(CVS * cvs,char const * status)487 static void _refresh_status(CVS * cvs, char const * status)
488 {
489 	if(status == NULL)
490 		gtk_widget_hide(cvs->status);
491 	else
492 	{
493 		gtk_label_set_text(GTK_LABEL(cvs->status), status);
494 		gtk_widget_show(cvs->status);
495 	}
496 }
497 
498 
499 /* accessors */
500 /* cvs_get_entries */
_cvs_get_entries(char const * pathname)501 static char * _cvs_get_entries(char const * pathname)
502 {
503 	char * ret = NULL;
504 	char const entries[] = "CVS/Entries";
505 	gchar * dirname;
506 	size_t len;
507 	char * p;
508 
509 	dirname = g_path_get_dirname(pathname);
510 	len = strlen(dirname) + sizeof(entries) + 1;
511 	if((p = malloc(len)) == NULL)
512 		return NULL;
513 	snprintf(p, len, "%s/%s", dirname, entries);
514 	g_file_get_contents(p, &ret, NULL, NULL);
515 	free(p);
516 	g_free(dirname);
517 	return ret;
518 }
519 
520 
521 /* cvs_get_repository */
_cvs_get_repository(char const * pathname)522 static char * _cvs_get_repository(char const * pathname)
523 {
524 	char * ret = NULL;
525 	char const repository[] = "CVS/Repository";
526 	size_t len;
527 	char * p;
528 
529 	len = strlen(pathname) + sizeof(repository) + 1;
530 	if((p = malloc(len)) == NULL)
531 		return NULL;
532 	snprintf(p, len, "%s/%s", pathname, repository);
533 	if(g_file_get_contents(p, &ret, NULL, NULL) == TRUE)
534 		string_rtrim(ret, NULL);
535 	free(p);
536 	return ret;
537 }
538 
539 
540 /* cvs_get_root */
_cvs_get_root(char const * pathname)541 static char * _cvs_get_root(char const * pathname)
542 {
543 	char * ret = NULL;
544 	char const root[] = "CVS/Root";
545 	size_t len;
546 	char * p;
547 
548 	len = strlen(pathname) + sizeof(root) + 1;
549 	if((p = malloc(len)) == NULL)
550 		return NULL;
551 	snprintf(p, len, "%s/%s", pathname, root);
552 	if(g_file_get_contents(p, &ret, NULL, NULL) == TRUE)
553 		string_rtrim(ret, NULL);
554 	free(p);
555 	return ret;
556 }
557 
558 
559 /* cvs_get_tag */
_cvs_get_tag(char const * pathname)560 static char * _cvs_get_tag(char const * pathname)
561 {
562 	char * ret = NULL;
563 	char const tag[] = "CVS/Tag";
564 	char * p;
565 	size_t len;
566 
567 	len = strlen(pathname) + sizeof(tag) + 1;
568 	if((p = malloc(len)) == NULL)
569 		return NULL;
570 	snprintf(p, len, "%s/%s", pathname, tag);
571 	if(g_file_get_contents(p, &ret, NULL, NULL) == TRUE)
572 		string_rtrim(ret, NULL);
573 	free(p);
574 	return ret;
575 }
576 
577 
578 /* cvs_is_managed */
579 /* XXX returns if the directory is managed, set *revision if the file is */
_cvs_is_managed(char const * pathname,char ** revision)580 static gboolean _cvs_is_managed(char const * pathname, char ** revision)
581 {
582 	char * entries;
583 	gchar * basename;
584 	size_t len;
585 	char const * s;
586 	char buf[256];
587 
588 	/* obtain the CVS entries */
589 	if((entries = _cvs_get_entries(pathname)) == NULL)
590 		return FALSE;
591 	/* lookup the filename within the entries */
592 	basename = g_path_get_basename(pathname);
593 	len = strlen(basename);
594 	for(s = entries; s != NULL; s = strchr(s, '\n'))
595 	{
596 		if((s = strchr(s, '/')) == NULL)
597 			break;
598 		if(strncmp(++s, basename, len) != 0 || s[len] != '/')
599 			continue;
600 		s += len;
601 		if(sscanf(s, "/%255[^/]/", buf) != 1)
602 			break;
603 		buf[sizeof(buf) - 1] = '\0';
604 		break;
605 	}
606 	g_free(basename);
607 	g_free(entries);
608 	if(s != NULL && revision != NULL)
609 		*revision = strdup(buf);
610 	return TRUE;
611 }
612 
613 
614 /* useful */
615 /* cvs_add_task */
_cvs_add_task(CVS * cvs,char const * title,char const * directory,char * argv[],CommonTaskCallback callback)616 static int _cvs_add_task(CVS * cvs, char const * title, char const * directory,
617 		char * argv[], CommonTaskCallback callback)
618 {
619 	BrowserPluginHelper * helper = cvs->helper;
620 	CVSTask ** p;
621 	CVSTask * task;
622 
623 	if((p = realloc(cvs->tasks, sizeof(*p) * (cvs->tasks_cnt + 1))) == NULL)
624 		return -helper->error(helper->browser, strerror(errno), 1);
625 	cvs->tasks = p;
626 	if((task = _common_task_new(helper, &plugin, title, directory, argv,
627 					callback, cvs)) == NULL)
628 		return -helper->error(helper->browser, error_get(NULL), 1);
629 	cvs->tasks[cvs->tasks_cnt++] = task;
630 	return 0;
631 }
632 
633 
634 /* cvs_confirm_delete */
_cvs_confirm_delete(char const * filename)635 static int _cvs_confirm_delete(char const * filename)
636 {
637 	GtkWidget * dialog;
638 	int res;
639 
640 	/* XXX move to BrowserPluginHelper */
641 	dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
642 			GTK_BUTTONS_NONE,
643 #if GTK_CHECK_VERSION(2, 6, 0)
644 			"%s", _("Question"));
645 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
646 #endif
647 			_("Do you really want to delete %s?"), filename);
648 	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
649 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
650 			GTK_STOCK_DELETE, GTK_RESPONSE_ACCEPT, NULL);
651 	gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
652 	res = gtk_dialog_run(GTK_DIALOG(dialog));
653 	gtk_widget_destroy(dialog);
654 	return (res == GTK_RESPONSE_ACCEPT) ? 1 : 0;
655 }
656 
657 
658 /* cvs_prompt_checkout */
_cvs_prompt_checkout(char const * message,char ** path,char ** module)659 static GtkResponseType _cvs_prompt_checkout(char const * message, char ** path,
660 		char ** module)
661 {
662 	GtkResponseType ret;
663 	GtkSizeGroup * group;
664 	GtkWidget * dialog;
665 	GtkWidget * vbox;
666 	GtkWidget * hbox;
667 	GtkWidget * label;
668 	GtkWidget * epath;
669 	GtkWidget * emodule;
670 
671 	group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
672 	/* FIXME make it transient */
673 	dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
674 			GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
675 # if GTK_CHECK_VERSION(2, 6, 0)
676 			"%s", _("Question"));
677 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
678 # endif
679 			"%s", message);
680 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
681 	gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
682 # if GTK_CHECK_VERSION(2, 14, 0)
683 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
684 # else
685 	vbox = GTK_DIALOG(dialog)->vbox;
686 # endif
687 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
688 	label = gtk_label_new(_("Path: "));
689 #if GTK_CHECK_VERSION(3, 0, 0)
690 	g_object_set(label, "halign", GTK_ALIGN_START, NULL);
691 #else
692 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
693 #endif
694 	gtk_size_group_add_widget(group, label);
695 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
696 	epath = gtk_entry_new();
697 	gtk_entry_set_activates_default(GTK_ENTRY(epath), TRUE);
698 	gtk_box_pack_start(GTK_BOX(hbox), epath, TRUE, TRUE, 0);
699 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
700 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
701 	label = gtk_label_new(_("Module: "));
702 #if GTK_CHECK_VERSION(3, 0, 0)
703 	g_object_set(label, "halign", GTK_ALIGN_START, NULL);
704 #else
705 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
706 #endif
707 	gtk_size_group_add_widget(group, label);
708 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
709 	emodule = gtk_entry_new();
710 	gtk_entry_set_activates_default(GTK_ENTRY(emodule), TRUE);
711 	gtk_box_pack_start(GTK_BOX(hbox), emodule, TRUE, TRUE, 0);
712 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
713 	gtk_widget_show_all(vbox);
714 	if((ret = gtk_dialog_run(GTK_DIALOG(dialog))) == GTK_RESPONSE_OK)
715 	{
716 		if((*path = g_strdup(gtk_entry_get_text(GTK_ENTRY(epath))))
717 				== NULL
718 				|| (*module = g_strdup(gtk_entry_get_text(
719 							GTK_ENTRY(emodule))))
720 				== NULL)
721 		{
722 			ret = GTK_RESPONSE_NONE;
723 			g_free(*path);
724 		}
725 	}
726 	gtk_widget_destroy(dialog);
727 	return ret;
728 }
729 
730 
731 /* callbacks */
732 /* cvs_on_add */
733 static gboolean _add_is_binary(char const * type);
734 static void _add_on_callback(CVS * cvs, CommonTask * task, int ret);
735 
_cvs_on_add(gpointer data)736 static void _cvs_on_add(gpointer data)
737 {
738 	CVS * cvs = data;
739 	gchar * dirname;
740 	gchar * basename;
741 	char * argv[] = { CVS_CVS, "add", "--", NULL, NULL, NULL };
742 	char const * type;
743 
744 	if(cvs->filename == NULL)
745 		return;
746 	dirname = g_path_get_dirname(cvs->filename);
747 	basename = g_path_get_basename(cvs->filename);
748 	argv[3] = basename;
749 	type = cvs->helper->get_type(cvs->helper->browser, cvs->filename, 0);
750 	if(_add_is_binary(type))
751 	{
752 		argv[4] = argv[3];
753 		argv[3] = argv[2];
754 		argv[2] = "-kb";
755 	}
756 	_cvs_add_task(cvs, "cvs add", dirname, argv, _add_on_callback);
757 	g_free(basename);
758 	g_free(dirname);
759 }
760 
_add_is_binary(char const * type)761 static gboolean _add_is_binary(char const * type)
762 {
763 	char const text[] = "text/";
764 	char const * types[] = { "application/x-perl",
765 		"application/x-shellscript",
766 		"application/xml",
767 		"application/xslt+xml" };
768 	size_t i;
769 
770 	if(type == NULL)
771 		return TRUE;
772 	if(strncmp(text, type, sizeof(text) - 1) == 0)
773 		return FALSE;
774 	for(i = 0; i < sizeof(types) / sizeof(*types); i++)
775 		if(strcmp(types[i], type) == 0)
776 			return FALSE;
777 	return TRUE;
778 }
779 
_add_on_callback(CVS * cvs,CommonTask * task,int ret)780 static void _add_on_callback(CVS * cvs, CommonTask * task, int ret)
781 {
782 	(void) task;
783 
784 	if(ret == 0)
785 		/* refresh upon success */
786 		cvs->helper->refresh(cvs->helper->browser);
787 }
788 
789 
790 /* cvs_on_annotate */
_cvs_on_annotate(gpointer data)791 static void _cvs_on_annotate(gpointer data)
792 {
793 	CVS * cvs = data;
794 	struct stat st;
795 	gchar * dirname;
796 	gchar * basename;
797 	char * argv[] = { CVS_CVS, "annotate", "--", NULL, NULL };
798 
799 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
800 		return;
801 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
802 		: g_path_get_dirname(cvs->filename);
803 	basename = S_ISDIR(st.st_mode) ? NULL
804 		: g_path_get_basename(cvs->filename);
805 	argv[3] = basename;
806 	_cvs_add_task(cvs, "cvs annotate", dirname, argv, NULL);
807 	g_free(basename);
808 	g_free(dirname);
809 }
810 
811 
812 /* cvs_on_checkout */
_cvs_on_checkout(gpointer data)813 static void _cvs_on_checkout(gpointer data)
814 {
815 	CVS * cvs = data;
816 	struct stat st;
817 	char * argv[] = { CVS_CVS, "checkout", "--", NULL, NULL };
818 	char * dirname;
819 	char * path;
820 	char * module;
821 
822 	if(_cvs_prompt_checkout("Checkout repository from:", &path, &module)
823 			!= GTK_RESPONSE_OK)
824 		return;
825 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
826 		: g_path_get_dirname(cvs->filename);
827 	/* FIXME escape arguments as required */
828 	argv[3] = g_strdup_printf("%s%s checkout %s", "-d:", path, module);
829 	_cvs_add_task(cvs, "cvs checkout", dirname, argv, NULL);
830 	g_free(argv[3]);
831 	g_free(dirname);
832 	free(path);
833 	free(module);
834 }
835 
836 
837 /* cvs_on_commit */
838 static void _commit_on_callback(CVS * cvs, CommonTask * task, int ret);
839 
_cvs_on_commit(gpointer data)840 static void _cvs_on_commit(gpointer data)
841 {
842 	CVS * cvs = data;
843 	struct stat st;
844 	gchar * dirname;
845 	gchar * basename;
846 	char * argv[] = { CVS_CVS, "commit", "--", NULL, NULL };
847 
848 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
849 		return;
850 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
851 		: g_path_get_dirname(cvs->filename);
852 	basename = S_ISDIR(st.st_mode) ? NULL
853 		: g_path_get_basename(cvs->filename);
854 	argv[3] = basename;
855 	_cvs_add_task(cvs, "cvs commit", dirname, argv, _commit_on_callback);
856 	g_free(basename);
857 	g_free(dirname);
858 }
859 
_commit_on_callback(CVS * cvs,CommonTask * task,int ret)860 static void _commit_on_callback(CVS * cvs, CommonTask * task, int ret)
861 {
862 	if(ret == 0)
863 		/* refresh upon success */
864 		cvs->helper->refresh(cvs->helper->browser);
865 	else
866 		_common_task_message(task, GTK_MESSAGE_ERROR,
867 				_("Could not commit the file or directory"), 1);
868 }
869 
870 
871 /* cvs_on_delete */
_cvs_on_delete(gpointer data)872 static void _cvs_on_delete(gpointer data)
873 {
874 	CVS * cvs = data;
875 	BrowserPluginHelper * helper = cvs->helper;
876 	struct stat st;
877 	gchar * dirname;
878 	gchar * basename;
879 	char * argv[] = { CVS_CVS, "delete", "--", NULL, NULL };
880 
881 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
882 		return;
883 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
884 		: g_path_get_dirname(cvs->filename);
885 	basename = S_ISDIR(st.st_mode) ? NULL
886 		: g_path_get_basename(cvs->filename);
887 	if((argv[3] = basename) == NULL)
888 		_cvs_add_task(cvs, "cvs delete", dirname, argv, NULL);
889 	else if(_cvs_confirm_delete(basename) == 1)
890 	{
891 		/* delete the file locally before asking CVS to */
892 		if(unlink(cvs->filename) != 0)
893 			helper->error(helper->browser, strerror(errno), 1);
894 		else
895 			_cvs_add_task(cvs, "cvs delete", dirname, argv, NULL);
896 	}
897 	g_free(basename);
898 	g_free(dirname);
899 }
900 
901 
902 /* cvs_on_diff */
_cvs_on_diff(gpointer data)903 static void _cvs_on_diff(gpointer data)
904 {
905 	CVS * cvs = data;
906 	struct stat st;
907 	gchar * dirname;
908 	gchar * basename;
909 	char * argv[] = { CVS_CVS, "diff", "--", NULL, NULL };
910 
911 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
912 		return;
913 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
914 		: g_path_get_dirname(cvs->filename);
915 	basename = S_ISDIR(st.st_mode) ? NULL
916 		: g_path_get_basename(cvs->filename);
917 	argv[3] = basename;
918 	_cvs_add_task(cvs, "cvs diff", dirname, argv, NULL);
919 	g_free(basename);
920 	g_free(dirname);
921 }
922 
923 
924 /* cvs_on_log */
_cvs_on_log(gpointer data)925 static void _cvs_on_log(gpointer data)
926 {
927 	CVS * cvs = data;
928 	struct stat st;
929 	gchar * dirname;
930 	gchar * basename;
931 	char * argv[] = { CVS_CVS, "log", "--", NULL, NULL };
932 
933 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
934 		return;
935 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
936 		: g_path_get_dirname(cvs->filename);
937 	basename = S_ISDIR(st.st_mode) ? NULL
938 		: g_path_get_basename(cvs->filename);
939 	argv[3] = basename;
940 	_cvs_add_task(cvs, "cvs log", dirname, argv, NULL);
941 	g_free(basename);
942 	g_free(dirname);
943 }
944 
945 
946 /* cvs_on_status */
_cvs_on_status(gpointer data)947 static void _cvs_on_status(gpointer data)
948 {
949 	CVS * cvs = data;
950 	struct stat st;
951 	gchar * dirname;
952 	gchar * basename;
953 	char * argv[] = { CVS_CVS, "status", "--", NULL, NULL };
954 
955 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
956 		return;
957 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
958 		: g_path_get_dirname(cvs->filename);
959 	basename = S_ISDIR(st.st_mode) ? NULL
960 		: g_path_get_basename(cvs->filename);
961 	argv[3] = basename;
962 	_cvs_add_task(cvs, "cvs status", dirname, argv, NULL);
963 	g_free(basename);
964 	g_free(dirname);
965 }
966 
967 
968 /* cvs_on_update */
_cvs_on_update(gpointer data)969 static void _cvs_on_update(gpointer data)
970 {
971 	CVS * cvs = data;
972 	struct stat st;
973 	gchar * dirname;
974 	gchar * basename;
975 	char * argv[] = { CVS_CVS, "update", "--", NULL, NULL };
976 
977 	if(cvs->filename == NULL || lstat(cvs->filename, &st) != 0)
978 		return;
979 	dirname = S_ISDIR(st.st_mode) ? g_strdup(cvs->filename)
980 		: g_path_get_dirname(cvs->filename);
981 	basename = S_ISDIR(st.st_mode) ? NULL
982 		: g_path_get_basename(cvs->filename);
983 	argv[3] = basename;
984 	_cvs_add_task(cvs, "cvs update", dirname, argv, NULL);
985 	g_free(basename);
986 	g_free(dirname);
987 }
988