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 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 220 addremove_fileproc (void *callerdat, struct file_info *finfo) 221 { 222 watch_modify_watchers (finfo->file, &the_args); 223 return 0; 224 } 225 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 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 383 watch_add (int argc, char **argv) 384 { 385 the_args.adding = 1; 386 return watch_addremove (argc, argv); 387 } 388 389 int 390 watch_remove (int argc, char **argv) 391 { 392 the_args.adding = 0; 393 return watch_addremove (argc, argv); 394 } 395 396 int 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 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 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