1 /* Implementation for "cvs watch add", "cvs watchers", and related commands
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 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 #include "cvs.h"
14 #include "edit.h"
15 #include "fileattr.h"
16 #include "watch.h"
17
18 const char *const watch_usage[] =
19 {
20 "Usage: %s %s {on|off|add|remove} [-lR] [-a <action>]... [<path>]...\n",
21 "on/off: Turn on/off read-only checkouts of files.\n",
22 "add/remove: Add or remove notification on actions.\n",
23 "-l (on/off/add/remove): Local directory only, not recursive.\n",
24 "-R (on/off/add/remove): Process directories recursively (default).\n",
25 "-a (add/remove): Specify what actions, one of: `edit', `unedit',\n",
26 " `commit', `all', or `none' (defaults to `all').\n",
27 "(Specify the --help global option for a list of other help options.)\n",
28 NULL
29 };
30
31 static struct addremove_args the_args;
32
33 void
watch_modify_watchers(const char * file,struct addremove_args * what)34 watch_modify_watchers (const char *file, struct addremove_args *what)
35 {
36 char *curattr = fileattr_get0 (file, "_watchers");
37 char *p;
38 char *pend;
39 char *nextp;
40 char *who;
41 int who_len;
42 char *mycurattr;
43 char *mynewattr;
44 size_t mynewattr_size;
45
46 int add_edit_pending;
47 int add_unedit_pending;
48 int add_commit_pending;
49 int remove_edit_pending;
50 int remove_unedit_pending;
51 int remove_commit_pending;
52 int add_tedit_pending;
53 int add_tunedit_pending;
54 int add_tcommit_pending;
55
56 TRACE( TRACE_FUNCTION, "modify_watchers ( %s )", file );
57
58 who = getcaller ();
59 who_len = strlen (who);
60
61 /* Look for current watcher types for this user. */
62 mycurattr = NULL;
63 if (curattr != NULL)
64 {
65 p = curattr;
66 while (1) {
67 if (strncmp (who, p, who_len) == 0
68 && p[who_len] == '>')
69 {
70 /* Found this user. */
71 mycurattr = p + who_len + 1;
72 }
73 p = strchr (p, ',');
74 if (p == NULL)
75 break;
76 ++p;
77 }
78 }
79 if (mycurattr != NULL)
80 {
81 mycurattr = xstrdup (mycurattr);
82 p = strchr (mycurattr, ',');
83 if (p != NULL)
84 *p = '\0';
85 }
86
87 /* Now copy mycurattr to mynewattr, making the requisite modifications.
88 Note that we add a dummy '+' to the start of mynewattr, to reduce
89 special cases (but then we strip it off when we are done). */
90
91 mynewattr_size = sizeof "+edit+unedit+commit+tedit+tunedit+tcommit";
92 if (mycurattr != NULL)
93 mynewattr_size += strlen (mycurattr);
94 mynewattr = xmalloc (mynewattr_size);
95 mynewattr[0] = '\0';
96
97 add_edit_pending = what->adding && what->edit;
98 add_unedit_pending = what->adding && what->unedit;
99 add_commit_pending = what->adding && what->commit;
100 remove_edit_pending = !what->adding && what->edit;
101 remove_unedit_pending = !what->adding && what->unedit;
102 remove_commit_pending = !what->adding && what->commit;
103 add_tedit_pending = what->add_tedit;
104 add_tunedit_pending = what->add_tunedit;
105 add_tcommit_pending = what->add_tcommit;
106
107 /* Copy over existing watch types, except those to be removed. */
108 p = mycurattr;
109 while (p != NULL)
110 {
111 pend = strchr (p, '+');
112 if (pend == NULL)
113 {
114 pend = p + strlen (p);
115 nextp = NULL;
116 }
117 else
118 nextp = pend + 1;
119
120 /* Process this item. */
121 if (pend - p == 4 && strncmp ("edit", p, 4) == 0)
122 {
123 if (!remove_edit_pending)
124 strcat (mynewattr, "+edit");
125 add_edit_pending = 0;
126 }
127 else if (pend - p == 6 && strncmp ("unedit", p, 6) == 0)
128 {
129 if (!remove_unedit_pending)
130 strcat (mynewattr, "+unedit");
131 add_unedit_pending = 0;
132 }
133 else if (pend - p == 6 && strncmp ("commit", p, 6) == 0)
134 {
135 if (!remove_commit_pending)
136 strcat (mynewattr, "+commit");
137 add_commit_pending = 0;
138 }
139 else if (pend - p == 5 && strncmp ("tedit", p, 5) == 0)
140 {
141 if (!what->remove_temp)
142 strcat (mynewattr, "+tedit");
143 add_tedit_pending = 0;
144 }
145 else if (pend - p == 7 && strncmp ("tunedit", p, 7) == 0)
146 {
147 if (!what->remove_temp)
148 strcat (mynewattr, "+tunedit");
149 add_tunedit_pending = 0;
150 }
151 else if (pend - p == 7 && strncmp ("tcommit", p, 7) == 0)
152 {
153 if (!what->remove_temp)
154 strcat (mynewattr, "+tcommit");
155 add_tcommit_pending = 0;
156 }
157 else
158 {
159 char *mp;
160
161 /* Copy over any unrecognized watch types, for future
162 expansion. */
163 mp = mynewattr + strlen (mynewattr);
164 *mp++ = '+';
165 strncpy (mp, p, pend - p);
166 *(mp + (pend - p)) = '\0';
167 }
168
169 /* Set up for next item. */
170 p = nextp;
171 }
172
173 /* Add in new watch types. */
174 if (add_edit_pending)
175 strcat (mynewattr, "+edit");
176 if (add_unedit_pending)
177 strcat (mynewattr, "+unedit");
178 if (add_commit_pending)
179 strcat (mynewattr, "+commit");
180 if (add_tedit_pending)
181 strcat (mynewattr, "+tedit");
182 if (add_tunedit_pending)
183 strcat (mynewattr, "+tunedit");
184 if (add_tcommit_pending)
185 strcat (mynewattr, "+tcommit");
186
187 {
188 char *curattr_new;
189
190 curattr_new =
191 fileattr_modify (curattr,
192 who,
193 mynewattr[0] == '\0' ? NULL : mynewattr + 1,
194 '>',
195 ',');
196 /* If the attribute is unchanged, don't rewrite the attribute file. */
197 if (!((curattr_new == NULL && curattr == NULL)
198 || (curattr_new != NULL
199 && curattr != NULL
200 && strcmp (curattr_new, curattr) == 0)))
201 fileattr_set (file,
202 "_watchers",
203 curattr_new);
204 if (curattr_new != NULL)
205 free (curattr_new);
206 }
207
208 if (curattr != NULL)
209 free (curattr);
210 if (mycurattr != NULL)
211 free (mycurattr);
212 if (mynewattr != NULL)
213 free (mynewattr);
214 }
215
216 static int addremove_fileproc (void *callerdat,
217 struct file_info *finfo);
218
219 static int
addremove_fileproc(void * callerdat,struct file_info * finfo)220 addremove_fileproc (void *callerdat, struct file_info *finfo)
221 {
222 watch_modify_watchers (finfo->file, &the_args);
223 return 0;
224 }
225
addremove_filesdoneproc(void * callerdat,int err,const char * repository,const char * update_dir,List * entries)226 static int addremove_filesdoneproc (void * callerdat, int err, const char * repository,
227 const char *update_dir, List * entries)
228 {
229 int set_default = the_args.setting_default;
230 int dir_check = 0;
231
232 while ( !set_default && dir_check < the_args.num_dirs )
233 {
234 /* If we are recursing, then just see if the first part of update_dir
235 matches any of the specified directories. Otherwise, it must be an exact
236 match. */
237 if ( the_args.local )
238 set_default = strcmp( update_dir, the_args.dirs[ dir_check ] )==0;
239 else
240 set_default = strncmp( update_dir, the_args.dirs[ dir_check ], strlen( the_args.dirs[ dir_check ] ) ) == 0;
241 dir_check++;
242 }
243
244 if (set_default)
245 watch_modify_watchers (NULL, &the_args);
246 return err;
247 }
248
249
250 static int
watch_addremove(int argc,char ** argv)251 watch_addremove (int argc, char **argv)
252 {
253 int c;
254 int err;
255 int a_omitted;
256 int arg_index;
257 int max_dirs;
258
259 a_omitted = 1;
260 the_args.commit = 0;
261 the_args.edit = 0;
262 the_args.unedit = 0;
263 the_args.num_dirs = 0;
264 the_args.dirs = NULL;
265 the_args.local = 0;
266
267 optind = 0;
268 while ((c = getopt (argc, argv, "+lRa:")) != -1)
269 {
270 switch (c)
271 {
272 case 'l':
273 the_args.local = 1;
274 break;
275 case 'R':
276 the_args.local = 0;
277 break;
278 case 'a':
279 a_omitted = 0;
280 if (strcmp (optarg, "edit") == 0)
281 the_args.edit = 1;
282 else if (strcmp (optarg, "unedit") == 0)
283 the_args.unedit = 1;
284 else if (strcmp (optarg, "commit") == 0)
285 the_args.commit = 1;
286 else if (strcmp (optarg, "all") == 0)
287 {
288 the_args.edit = 1;
289 the_args.unedit = 1;
290 the_args.commit = 1;
291 }
292 else if (strcmp (optarg, "none") == 0)
293 {
294 the_args.edit = 0;
295 the_args.unedit = 0;
296 the_args.commit = 0;
297 }
298 else
299 usage (watch_usage);
300 break;
301 case '?':
302 default:
303 usage (watch_usage);
304 break;
305 }
306 }
307 argc -= optind;
308 argv += optind;
309
310 the_args.num_dirs = 0;
311 max_dirs = 4; /* Arbitrary choice. */
312 the_args.dirs = xmalloc( sizeof( const char * ) * max_dirs );
313
314 TRACE (TRACE_FUNCTION, "watch_addremove (%d)", argc);
315 for ( arg_index=0; arg_index<argc; ++arg_index )
316 {
317 TRACE( TRACE_FUNCTION, "\t%s", argv[ arg_index ]);
318 if ( isdir( argv[ arg_index ] ) )
319 {
320 if ( the_args.num_dirs >= max_dirs )
321 {
322 max_dirs *= 2;
323 the_args.dirs = (const char ** )xrealloc( (void *)the_args.dirs, max_dirs );
324 }
325 the_args.dirs[ the_args.num_dirs++ ] = argv[ arg_index ];
326 }
327 }
328
329 if (a_omitted)
330 {
331 the_args.edit = 1;
332 the_args.unedit = 1;
333 the_args.commit = 1;
334 }
335
336 #ifdef CLIENT_SUPPORT
337 if (current_parsed_root->isremote)
338 {
339 start_server ();
340 ign_setup ();
341
342 if (the_args.local)
343 send_arg ("-l");
344 /* FIXME: copes poorly with "all" if server is extended to have
345 new watch types and client is still running an old version. */
346 if (the_args.edit)
347 option_with_arg ("-a", "edit");
348 if (the_args.unedit)
349 option_with_arg ("-a", "unedit");
350 if (the_args.commit)
351 option_with_arg ("-a", "commit");
352 if (!the_args.edit && !the_args.unedit && !the_args.commit)
353 option_with_arg ("-a", "none");
354 send_arg ("--");
355 send_files (argc, argv, the_args.local, 0, SEND_NO_CONTENTS);
356 send_file_names (argc, argv, SEND_EXPAND_WILD);
357 send_to_server (the_args.adding ?
358 "watch-add\012" : "watch-remove\012",
359 0);
360 return get_responses_and_close ();
361 }
362 #endif /* CLIENT_SUPPORT */
363
364 the_args.setting_default = (argc <= 0);
365
366 lock_tree_promotably (argc, argv, the_args.local, W_LOCAL, 0);
367
368 err = start_recursion
369 (addremove_fileproc, addremove_filesdoneproc, NULL, NULL, NULL,
370 argc, argv, the_args.local, W_LOCAL, 0, CVS_LOCK_WRITE,
371 NULL, 1, NULL);
372
373 Lock_Cleanup ();
374 free( (void *)the_args.dirs );
375 the_args.dirs = NULL;
376
377 return err;
378 }
379
380
381
382 int
watch_add(int argc,char ** argv)383 watch_add (int argc, char **argv)
384 {
385 the_args.adding = 1;
386 return watch_addremove (argc, argv);
387 }
388
389 int
watch_remove(int argc,char ** argv)390 watch_remove (int argc, char **argv)
391 {
392 the_args.adding = 0;
393 return watch_addremove (argc, argv);
394 }
395
396 int
watch(int argc,char ** argv)397 watch (int argc, char **argv)
398 {
399 if (argc <= 1)
400 usage (watch_usage);
401 if (strcmp (argv[1], "on") == 0)
402 {
403 --argc;
404 ++argv;
405 return watch_on (argc, argv);
406 }
407 else if (strcmp (argv[1], "off") == 0)
408 {
409 --argc;
410 ++argv;
411 return watch_off (argc, argv);
412 }
413 else if (strcmp (argv[1], "add") == 0)
414 {
415 --argc;
416 ++argv;
417 return watch_add (argc, argv);
418 }
419 else if (strcmp (argv[1], "remove") == 0)
420 {
421 --argc;
422 ++argv;
423 return watch_remove (argc, argv);
424 }
425 else
426 usage (watch_usage);
427 return 0;
428 }
429
430 static const char *const watchers_usage[] =
431 {
432 "Usage: %s %s [-lR] [<file>]...\n",
433 "-l\tProcess this directory only (not recursive).\n",
434 "-R\tProcess directories recursively (default).\n",
435 "(Specify the --help global option for a list of other help options.)\n",
436 NULL
437 };
438
439 static int watchers_fileproc (void *callerdat,
440 struct file_info *finfo);
441
442 static int
watchers_fileproc(void * callerdat,struct file_info * finfo)443 watchers_fileproc (void *callerdat, struct file_info *finfo)
444 {
445 char *them;
446 char *p;
447
448 them = fileattr_get0 (finfo->file, "_watchers");
449 if (them == NULL)
450 return 0;
451
452 cvs_output (finfo->fullname, 0);
453
454 p = them;
455 while (1)
456 {
457 cvs_output ("\t", 1);
458 while (*p != '>' && *p != '\0')
459 cvs_output (p++, 1);
460 if (*p == '\0')
461 {
462 /* Only happens if attribute is misformed. */
463 cvs_output ("\n", 1);
464 break;
465 }
466 ++p;
467 cvs_output ("\t", 1);
468 while (1)
469 {
470 while (*p != '+' && *p != ',' && *p != '\0')
471 cvs_output (p++, 1);
472 if (*p == '\0')
473 {
474 cvs_output ("\n", 1);
475 goto out;
476 }
477 if (*p == ',')
478 {
479 ++p;
480 break;
481 }
482 ++p;
483 cvs_output ("\t", 1);
484 }
485 cvs_output ("\n", 1);
486 }
487 out:;
488 free (them);
489 return 0;
490 }
491
492 int
watchers(int argc,char ** argv)493 watchers (int argc, char **argv)
494 {
495 int local = 0;
496 int c;
497
498 if (argc == -1)
499 usage (watchers_usage);
500
501 optind = 0;
502 while ((c = getopt (argc, argv, "+lR")) != -1)
503 {
504 switch (c)
505 {
506 case 'l':
507 local = 1;
508 break;
509 case 'R':
510 local = 0;
511 break;
512 case '?':
513 default:
514 usage (watchers_usage);
515 break;
516 }
517 }
518 argc -= optind;
519 argv += optind;
520
521 #ifdef CLIENT_SUPPORT
522 if (current_parsed_root->isremote)
523 {
524 start_server ();
525 ign_setup ();
526
527 if (local)
528 send_arg ("-l");
529 send_arg ("--");
530 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
531 send_file_names (argc, argv, SEND_EXPAND_WILD);
532 send_to_server ("watchers\012", 0);
533 return get_responses_and_close ();
534 }
535 #endif /* CLIENT_SUPPORT */
536
537 return start_recursion (watchers_fileproc, NULL, NULL,
538 NULL, NULL, argc, argv, local, W_LOCAL, 0,
539 CVS_LOCK_READ, NULL, 1, NULL);
540 }
541