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
18const 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
31static struct addremove_args the_args;
32
33void
34watch_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
216static int addremove_fileproc (void *callerdat,
217				      struct file_info *finfo);
218
219static int
220addremove_fileproc (void *callerdat, struct file_info *finfo)
221{
222    watch_modify_watchers (finfo->file, &the_args);
223    return 0;
224}
225
226static 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
250static int
251watch_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    getoptreset ();
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
382int
383watch_add (int argc, char **argv)
384{
385    the_args.adding = 1;
386    return watch_addremove (argc, argv);
387}
388
389int
390watch_remove (int argc, char **argv)
391{
392    the_args.adding = 0;
393    return watch_addremove (argc, argv);
394}
395
396int
397watch (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
430static 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
439static int watchers_fileproc (void *callerdat,
440				     struct file_info *finfo);
441
442static int
443watchers_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
492int
493watchers (int argc, char **argv)
494{
495    int local = 0;
496    int c;
497
498    if (argc == -1)
499	usage (watchers_usage);
500
501    getoptreset ();
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