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