1 /* Implementation for "cvs edit", "cvs watch on", and related commands
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)
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. */
19 static int watch_onoff PROTO ((int, char **));
21 static int setting_default;
22 static int turning_on;
24 static int setting_tedit;
25 static int setting_tunedit;
26 static int setting_tcommit;
28 static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
31 onoff_fileproc (callerdat, finfo)
33 struct file_info *finfo;
35 char *watched = fileattr_get0 (finfo->file, "_watched");
36 fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
44 static int onoff_filesdoneproc PROTO ((void *, int, const char *, const char *,
48 onoff_filesdoneproc (callerdat, err, repository, update_dir, entries)
51 const char *repository;
52 const char *update_dir;
57 char *watched = fileattr_get0 (NULL, "_watched");
58 fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
66 watch_onoff (argc, argv)
75 while ((c = getopt (argc, argv, "+lR")) != -1)
95 if (current_parsed_root->isremote)
104 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
105 send_file_names (argc, argv, SEND_EXPAND_WILD);
106 send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
107 return get_responses_and_close ();
109 #endif /* CLIENT_SUPPORT */
111 setting_default = (argc <= 0);
113 lock_tree_for_write (argc, argv, local, W_LOCAL, 0);
115 err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
116 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
117 argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
118 (char *) NULL, 0, (char *) NULL);
125 watch_on (argc, argv)
130 return watch_onoff (argc, argv);
134 watch_off (argc, argv)
139 return watch_onoff (argc, argv);
142 static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo));
145 dummy_fileproc (callerdat, finfo)
147 struct file_info *finfo;
149 /* This is a pretty hideous hack, but the gist of it is that recurse.c
150 won't call cvs_notify_check unless there is a fileproc, so we
151 can't just pass NULL for fileproc. */
155 static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo));
157 /* Check for and process notifications. Local only. I think that doing
158 this as a fileproc is the only way to catch all the
159 cases (e.g. foo/bar.c), even though that means checking over and over
160 for the same CVSADM_NOTIFY file which we removed the first time we
161 processed the directory. */
164 ncheck_fileproc (callerdat, finfo)
166 struct file_info *finfo;
178 /* We send notifications even if noexec. I'm not sure which behavior
181 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
184 if (!existence_error (errno))
185 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
189 while (getline (&line, &line_len, fp) > 0)
191 notif_type = line[0];
192 if (notif_type == '\0')
195 cp = strchr (filename, '\t');
200 cp = strchr (val, '\t');
204 cp = strchr (cp, '\t');
208 cp = strchr (cp, '\t');
213 cp = strchr (cp, '\n');
218 notify_do (notif_type, filename, getcaller (), val, watches,
224 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
226 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
228 if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
229 error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
234 static int send_notifications PROTO ((int, char **, int));
236 /* Look through the CVSADM_NOTIFY file and process each item there
239 send_notifications (argc, argv, local)
246 #ifdef CLIENT_SUPPORT
247 /* OK, we've done everything which needs to happen on the client side.
248 Now we can try to contact the server; if we fail, then the
249 notifications stay in CVSADM_NOTIFY to be sent next time. */
250 if (current_parsed_root->isremote)
252 if (strcmp (cvs_cmd_name, "release") != 0)
258 err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
259 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
260 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
263 send_to_server ("noop\012", 0);
264 if (strcmp (cvs_cmd_name, "release") == 0)
265 err += get_server_responses ();
267 err += get_responses_and_close ();
274 lock_tree_for_write (argc, argv, local, W_LOCAL, 0);
275 err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
276 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
277 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
284 static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
287 edit_fileproc (callerdat, finfo)
289 struct file_info *finfo;
299 /* This is a somewhat screwy way to check for this, because it
300 doesn't help errors other than the nonexistence of the file
301 (e.g. permissions problems). It might be better to rearrange
302 the code so that CVSADM_NOTIFY gets written only after the
303 various actions succeed (but what if only some of them
305 if (!isfile (finfo->file))
307 error (0, 0, "no such file %s; ignored", finfo->fullname);
311 fp = open_file (CVSADM_NOTIFY, "a");
314 ascnow = asctime (gmtime (&now));
316 /* Fix non-standard format. */
317 if (ascnow[8] == '0') ascnow[8] = ' ';
318 fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
319 ascnow, hostname, CurDir);
330 if (finfo->update_dir[0] == '\0')
331 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
333 error (0, errno, "cannot close %s/%s", finfo->update_dir,
337 xchmod (finfo->file, 1);
339 /* Now stash the file away in CVSADM so that unedit can revert even if
340 it can't communicate with the server. We stash away a writable
341 copy so that if the user removes the working file, then restores it
342 with "cvs update" (which clears _editors but does not update
343 CVSADM_BASE), then a future "cvs edit" can still win. */
344 /* Could save a system call by only calling mkdir_if_needed if
345 trying to create the output file fails. But copy_file isn't
346 set up to facilitate that. */
347 mkdir_if_needed (CVSADM_BASE);
348 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
349 strcpy (basefilename, CVSADM_BASE);
350 strcat (basefilename, "/");
351 strcat (basefilename, finfo->file);
352 copy_file (finfo->file, basefilename);
358 node = findnode_fn (finfo->entries, finfo->file);
360 base_register (finfo, ((Entnode *) node->data)->version);
366 static const char *const edit_usage[] =
368 "Usage: %s %s [-lR] [-a <action>]... [<file>]...\n",
369 "-l\tLocal directory only, not recursive.\n",
370 "-R\tProcess directories recursively (default).\n",
371 "-a\tSpecify action to register for temporary watch, one of:\n",
372 " \t`edit', `unedit', `commit', `all', or `none' (defaults to `all').\n",
373 "(Specify the --help global option for a list of other help options.)\n",
395 while ((c = getopt (argc, argv, "+lRa:")) != -1)
407 if (strcmp (optarg, "edit") == 0)
409 else if (strcmp (optarg, "unedit") == 0)
411 else if (strcmp (optarg, "commit") == 0)
413 else if (strcmp (optarg, "all") == 0)
419 else if (strcmp (optarg, "none") == 0)
444 if (strpbrk (hostname, "+,>;=\t\n") != NULL)
446 "host name (%s) contains an invalid character (+,>;=\\t\\n)",
448 if (strpbrk (CurDir, "+,>;=\t\n") != NULL)
450 "current directory (%s) contains an invalid character (+,>;=\\t\\n)",
453 /* No need to readlock since we aren't doing anything to the
455 err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
456 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
457 argc, argv, local, W_LOCAL, 0, 0, (char *) NULL,
460 err += send_notifications (argc, argv, local);
465 static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
468 unedit_fileproc (callerdat, finfo)
470 struct file_info *finfo;
480 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
481 strcpy (basefilename, CVSADM_BASE);
482 strcat (basefilename, "/");
483 strcat (basefilename, finfo->file);
484 if (!isfile (basefilename))
486 /* This file apparently was never cvs edit'd (e.g. we are uneditting
487 a directory where only some of the files were cvs edit'd. */
492 if (xcmp (finfo->file, basefilename) != 0)
494 printf ("%s has been modified; revert changes? ", finfo->fullname);
502 rename_file (basefilename, finfo->file);
505 fp = open_file (CVSADM_NOTIFY, "a");
508 ascnow = asctime (gmtime (&now));
510 /* Fix non-standard format. */
511 if (ascnow[8] == '0') ascnow[8] = ' ';
512 fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
513 ascnow, hostname, CurDir);
517 if (finfo->update_dir[0] == '\0')
518 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
520 error (0, errno, "cannot close %s/%s", finfo->update_dir,
524 /* Now update the revision number in CVS/Entries from CVS/Baserev.
525 The basic idea here is that we are reverting to the revision
526 that the user edited. If we wanted "cvs update" to update
527 CVS/Base as we go along (so that an unedit could revert to the
528 current repository revision), we would need:
530 update (or all send_files?) (client) needs to send revision in
531 new Entry-base request. update (server/local) needs to check
532 revision against repository and send new Update-base response
533 (like Update-existing in that the file already exists. While
534 we are at it, might try to clean up the syntax by having the
535 mode only in a "Mode" response, not in the Update-base itself). */
541 baserev = base_get (finfo);
542 node = findnode_fn (finfo->entries, finfo->file);
543 /* The case where node is NULL probably should be an error or
544 something, but I don't want to think about it too hard right
548 entdata = node->data;
551 /* This can only happen if the CVS/Baserev file got
552 corrupted. We suspect it might be possible if the
553 user interrupts CVS, although I haven't verified
555 error (0, 0, "%s not mentioned in %s", finfo->fullname,
558 /* Since we don't know what revision the file derives from,
559 keeping it around would be asking for trouble. */
560 if (unlink_file (finfo->file) < 0)
561 error (0, errno, "cannot remove %s", finfo->fullname);
563 /* This is cheesy, in a sense; why shouldn't we do the
564 update for the user? However, doing that would require
565 contacting the server, so maybe this is OK. */
566 error (0, 0, "run update to complete the unedit");
569 Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
570 entdata->options, entdata->tag, entdata->date,
574 base_deregister (finfo);
577 xchmod (finfo->file, 0);
581 static const char *const unedit_usage[] =
583 "Usage: %s %s [-lR] [<file>]...\n",
584 "-l\tLocal directory only, not recursive.\n",
585 "-R\tProcess directories recursively (default).\n",
586 "(Specify the --help global option for a list of other help options.)\n",
600 usage (unedit_usage);
603 while ((c = getopt (argc, argv, "+lR")) != -1)
615 usage (unedit_usage);
622 /* No need to readlock since we aren't doing anything to the
624 err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
625 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
626 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
629 err += send_notifications (argc, argv, local);
635 mark_up_to_date (file)
640 /* The file is up to date, so we better get rid of an out of
641 date file in CVSADM_BASE. */
642 base = xmalloc (strlen (file) + 80);
643 strcpy (base, CVSADM_BASE);
646 if (unlink_file (base) < 0 && ! existence_error (errno))
647 error (0, errno, "cannot remove %s", file);
654 editor_set (filename, editor, val)
655 const char *filename;
662 edlist = fileattr_get0 (filename, "_editors");
663 newlist = fileattr_modify (edlist, editor, val, '>', ',');
664 /* If the attributes is unchanged, don't rewrite the attribute file. */
665 if (!((edlist == NULL && newlist == NULL)
668 && strcmp (edlist, newlist) == 0)))
669 fileattr_set (filename, "_editors", newlist);
676 struct notify_proc_args {
677 /* What kind of notification, "edit", "tedit", etc. */
679 /* User who is running the command which causes notification. */
681 /* User to be notified. */
682 const char *notifyee;
689 /* Pass as a static until we get around to fixing Parse_Info to pass along
690 a void * where we can stash it. */
691 static struct notify_proc_args *notify_args;
695 static int notify_proc PROTO ((const char *repository, const char *filter));
698 notify_proc (repository, filter)
699 const char *repository;
708 struct notify_proc_args *args = notify_args;
710 srepos = Short_Repository (repository);
711 prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
712 /* Copy FILTER to PROG, replacing the first occurrence of %s with
713 the notifyee. We only allocated enough memory for one %s, and I doubt
714 there is a need for more. */
715 for (p = filter, q = prog; *p != '\0'; ++p)
721 strcpy (q, args->notifyee);
734 /* FIXME: why are we calling expand_proc? Didn't we already
735 expand it in Parse_Info, before passing it to notify_proc? */
736 expanded_prog = expand_path (prog, "notify", 0);
743 pipefp = run_popen (expanded_prog, "w");
746 error (0, errno, "cannot write entry to notify filter: %s", prog);
748 free (expanded_prog);
752 fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
753 fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
754 fprintf (pipefp, "By %s\n", args->who);
756 /* Lots more potentially useful information we could add here; see
757 logfile_write for inspiration. */
760 free (expanded_prog);
761 return (pclose (pipefp));
764 /* FIXME: this function should have a way to report whether there was
765 an error so that server.c can know whether to report Notified back
768 notify_do (type, filename, who, val, watches, repository)
770 const char *filename;
774 const char *repository;
776 static struct addremove_args blank;
777 struct addremove_args args;
783 /* Initialize fields to 0, NULL, or 0.0. */
788 if (strpbrk (val, ",>;=\n") != NULL)
790 error (0, 0, "invalid character in editor value");
793 editor_set (filename, who, val);
797 editor_set (filename, who, NULL);
803 watchers = fileattr_get0 (filename, "_watchers");
812 endp = strchr (p, '>');
815 nextp = strchr (p, ',');
817 if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
819 /* Don't notify user of their own changes. Would perhaps
820 be better to check whether it is the same working
821 directory, not the same user, but that is hairy. */
822 p = nextp == NULL ? nextp : nextp + 1;
826 /* Now we point q at a string which looks like
827 "edit+unedit+commit,"... and walk down it. */
832 endq = strchr (q, '+');
833 if (endq == NULL || (nextp != NULL && endq > nextp))
836 endq = q + strlen (q);
844 /* If there is a temporary and a regular watch, send a single
845 notification, for the regular watch. */
846 if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
851 && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
856 && endq - q == 6 && strncmp ("commit", q, 6) == 0)
861 && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
864 notif = "temporary edit";
867 && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
870 notif = "temporary unedit";
873 && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
876 notif = "temporary commit";
885 struct notify_proc_args args;
886 size_t len = endp - p;
892 args.notifyee = NULL;
893 usersname = xmalloc (strlen (current_parsed_root->directory)
895 + sizeof CVSROOTADM_USERS
897 strcpy (usersname, current_parsed_root->directory);
898 strcat (usersname, "/");
899 strcat (usersname, CVSROOTADM);
900 strcat (usersname, "/");
901 strcat (usersname, CVSROOTADM_USERS);
902 fp = CVS_FOPEN (usersname, "r");
903 if (fp == NULL && !existence_error (errno))
904 error (0, errno, "cannot read %s", usersname);
907 while (getline (&line, &line_len, fp) >= 0)
909 if (strncmp (line, p, len) == 0
913 args.notifyee = xstrdup (line + len + 1);
915 /* There may or may not be more
916 colon-separated fields added to this in the
917 future; in any case, we ignore them right
918 now, and if there are none we make sure to
919 chop off the final newline, if any. */
920 cp = strpbrk (args.notifyee, ":\n");
928 error (0, errno, "cannot read %s", usersname);
930 error (0, errno, "cannot close %s", usersname);
936 if (args.notifyee == NULL)
939 tmp = xmalloc (endp - p + 1);
940 strncpy (tmp, p, endp - p);
941 tmp[endp - p] = '\0';
948 args.file = filename;
950 (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
952 /* It's okay to cast out the const for the free() below since we
953 * just allocated this a few lines above. The const was for
956 free ((char *)args.notifyee);
961 if (watchers != NULL)
974 args.add_tunedit = 1;
979 args.add_tcommit = 1;
981 watch_modify_watchers (filename, &args);
985 args.remove_temp = 1;
986 watch_modify_watchers (filename, &args);
991 #ifdef CLIENT_SUPPORT
992 /* Check and send notifications. This is only for the client. */
994 cvs_notify_check (repository, update_dir)
995 const char *repository;
996 const char *update_dir;
1000 size_t line_len = 0;
1002 if (! server_started)
1003 /* We are in the midst of a command which is not to talk to
1004 the server (e.g. the first phase of a cvs edit). Just chill
1005 out, we'll catch the notifications on the flip side. */
1008 /* We send notifications even if noexec. I'm not sure which behavior
1009 is most sensible. */
1011 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
1014 if (!existence_error (errno))
1015 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
1018 while (getline (&line, &line_len, fp) > 0)
1025 notif_type = line[0];
1026 if (notif_type == '\0')
1028 filename = line + 1;
1029 cp = strchr (filename, '\t');
1035 client_notify (repository, update_dir, filename, notif_type, val);
1040 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
1041 if (fclose (fp) < 0)
1042 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
1044 /* Leave the CVSADM_NOTIFY file there, until the server tells us it
1045 has dealt with it. */
1047 #endif /* CLIENT_SUPPORT */
1050 static const char *const editors_usage[] =
1052 "Usage: %s %s [-lR] [<file>]...\n",
1053 "-l\tProcess this directory only (not recursive).\n",
1054 "-R\tProcess directories recursively (default).\n",
1055 "(Specify the --help global option for a list of other help options.)\n",
1059 static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1062 editors_fileproc (callerdat, finfo)
1064 struct file_info *finfo;
1069 them = fileattr_get0 (finfo->file, "_editors");
1073 cvs_output (finfo->fullname, 0);
1078 cvs_output ("\t", 1);
1079 while (*p != '>' && *p != '\0')
1080 cvs_output (p++, 1);
1083 /* Only happens if attribute is misformed. */
1084 cvs_output ("\n", 1);
1088 cvs_output ("\t", 1);
1091 while (*p != '+' && *p != ',' && *p != '\0')
1092 cvs_output (p++, 1);
1095 cvs_output ("\n", 1);
1104 cvs_output ("\t", 1);
1106 cvs_output ("\n", 1);
1114 editors (argc, argv)
1122 usage (editors_usage);
1125 while ((c = getopt (argc, argv, "+lR")) != -1)
1137 usage (editors_usage);
1144 #ifdef CLIENT_SUPPORT
1145 if (current_parsed_root->isremote)
1153 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
1154 send_file_names (argc, argv, SEND_EXPAND_WILD);
1155 send_to_server ("editors\012", 0);
1156 return get_responses_and_close ();
1158 #endif /* CLIENT_SUPPORT */
1160 return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
1161 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
1162 argc, argv, local, W_LOCAL, 0, 1, (char *) NULL,