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 fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
41 static int onoff_filesdoneproc PROTO ((void *, int, const char *, const char *,
45 onoff_filesdoneproc (callerdat, err, repository, update_dir, entries)
48 const char *repository;
49 const char *update_dir;
53 fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
58 watch_onoff (argc, argv)
67 while ((c = getopt (argc, argv, "+lR")) != -1)
87 if (current_parsed_root->isremote)
96 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
97 send_file_names (argc, argv, SEND_EXPAND_WILD);
98 send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
99 return get_responses_and_close ();
101 #endif /* CLIENT_SUPPORT */
103 setting_default = (argc <= 0);
105 lock_tree_for_write (argc, argv, local, W_LOCAL, 0);
107 err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
108 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
109 argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
110 (char *) NULL, 0, (char *) NULL);
117 watch_on (argc, argv)
122 return watch_onoff (argc, argv);
126 watch_off (argc, argv)
131 return watch_onoff (argc, argv);
134 static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo));
137 dummy_fileproc (callerdat, finfo)
139 struct file_info *finfo;
141 /* This is a pretty hideous hack, but the gist of it is that recurse.c
142 won't call notify_check unless there is a fileproc, so we can't just
143 pass NULL for fileproc. */
147 static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo));
149 /* Check for and process notifications. Local only. I think that doing
150 this as a fileproc is the only way to catch all the
151 cases (e.g. foo/bar.c), even though that means checking over and over
152 for the same CVSADM_NOTIFY file which we removed the first time we
153 processed the directory. */
156 ncheck_fileproc (callerdat, finfo)
158 struct file_info *finfo;
170 /* We send notifications even if noexec. I'm not sure which behavior
173 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
176 if (!existence_error (errno))
177 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
181 while (getline (&line, &line_len, fp) > 0)
183 notif_type = line[0];
184 if (notif_type == '\0')
187 cp = strchr (filename, '\t');
192 cp = strchr (val, '\t');
196 cp = strchr (cp, '\t');
200 cp = strchr (cp, '\t');
205 cp = strchr (cp, '\n');
210 notify_do (notif_type, filename, getcaller (), val, watches,
216 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
218 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
220 if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
221 error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
226 static int send_notifications PROTO ((int, char **, int));
228 /* Look through the CVSADM_NOTIFY file and process each item there
231 send_notifications (argc, argv, local)
238 #ifdef CLIENT_SUPPORT
239 /* OK, we've done everything which needs to happen on the client side.
240 Now we can try to contact the server; if we fail, then the
241 notifications stay in CVSADM_NOTIFY to be sent next time. */
242 if (current_parsed_root->isremote)
244 if (strcmp (cvs_cmd_name, "release") != 0)
250 err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
251 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
252 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
255 send_to_server ("noop\012", 0);
256 if (strcmp (cvs_cmd_name, "release") == 0)
257 err += get_server_responses ();
259 err += get_responses_and_close ();
266 lock_tree_for_write (argc, argv, local, W_LOCAL, 0);
267 err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
268 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
269 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
276 static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
279 edit_fileproc (callerdat, finfo)
281 struct file_info *finfo;
291 /* This is a somewhat screwy way to check for this, because it
292 doesn't help errors other than the nonexistence of the file
293 (e.g. permissions problems). It might be better to rearrange
294 the code so that CVSADM_NOTIFY gets written only after the
295 various actions succeed (but what if only some of them
297 if (!isfile (finfo->file))
299 error (0, 0, "no such file %s; ignored", finfo->fullname);
303 fp = open_file (CVSADM_NOTIFY, "a");
306 ascnow = asctime (gmtime (&now));
308 /* Fix non-standard format. */
309 if (ascnow[8] == '0') ascnow[8] = ' ';
310 fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
311 ascnow, hostname, CurDir);
322 if (finfo->update_dir[0] == '\0')
323 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
325 error (0, errno, "cannot close %s/%s", finfo->update_dir,
329 xchmod (finfo->file, 1);
331 /* Now stash the file away in CVSADM so that unedit can revert even if
332 it can't communicate with the server. We stash away a writable
333 copy so that if the user removes the working file, then restores it
334 with "cvs update" (which clears _editors but does not update
335 CVSADM_BASE), then a future "cvs edit" can still win. */
336 /* Could save a system call by only calling mkdir_if_needed if
337 trying to create the output file fails. But copy_file isn't
338 set up to facilitate that. */
339 mkdir_if_needed (CVSADM_BASE);
340 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
341 strcpy (basefilename, CVSADM_BASE);
342 strcat (basefilename, "/");
343 strcat (basefilename, finfo->file);
344 copy_file (finfo->file, basefilename);
350 node = findnode_fn (finfo->entries, finfo->file);
352 base_register (finfo, ((Entnode *) node->data)->version);
358 static const char *const edit_usage[] =
360 "Usage: %s %s [-lR] [files...]\n",
361 "-l: Local directory only, not recursive\n",
362 "-R: Process directories recursively\n",
363 "-a: Specify what actions for temporary watch, one of\n",
364 " edit,unedit,commit,all,none\n",
365 "(Specify the --help global option for a list of other help options)\n",
387 while ((c = getopt (argc, argv, "+lRa:")) != -1)
399 if (strcmp (optarg, "edit") == 0)
401 else if (strcmp (optarg, "unedit") == 0)
403 else if (strcmp (optarg, "commit") == 0)
405 else if (strcmp (optarg, "all") == 0)
411 else if (strcmp (optarg, "none") == 0)
436 if (strpbrk (hostname, "+,>;=\t\n") != NULL)
438 "host name (%s) contains an invalid character (+,>;=\\t\\n)",
440 if (strpbrk (CurDir, "+,>;=\t\n") != NULL)
442 "current directory (%s) contains an invalid character (+,>;=\\t\\n)",
445 /* No need to readlock since we aren't doing anything to the
447 err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
448 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
449 argc, argv, local, W_LOCAL, 0, 0, (char *) NULL,
452 err += send_notifications (argc, argv, local);
457 static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
460 unedit_fileproc (callerdat, finfo)
462 struct file_info *finfo;
472 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
473 strcpy (basefilename, CVSADM_BASE);
474 strcat (basefilename, "/");
475 strcat (basefilename, finfo->file);
476 if (!isfile (basefilename))
478 /* This file apparently was never cvs edit'd (e.g. we are uneditting
479 a directory where only some of the files were cvs edit'd. */
484 if (xcmp (finfo->file, basefilename) != 0)
486 printf ("%s has been modified; revert changes? ", finfo->fullname);
494 rename_file (basefilename, finfo->file);
497 fp = open_file (CVSADM_NOTIFY, "a");
500 ascnow = asctime (gmtime (&now));
502 /* Fix non-standard format. */
503 if (ascnow[8] == '0') ascnow[8] = ' ';
504 fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
505 ascnow, hostname, CurDir);
509 if (finfo->update_dir[0] == '\0')
510 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
512 error (0, errno, "cannot close %s/%s", finfo->update_dir,
516 /* Now update the revision number in CVS/Entries from CVS/Baserev.
517 The basic idea here is that we are reverting to the revision
518 that the user edited. If we wanted "cvs update" to update
519 CVS/Base as we go along (so that an unedit could revert to the
520 current repository revision), we would need:
522 update (or all send_files?) (client) needs to send revision in
523 new Entry-base request. update (server/local) needs to check
524 revision against repository and send new Update-base response
525 (like Update-existing in that the file already exists. While
526 we are at it, might try to clean up the syntax by having the
527 mode only in a "Mode" response, not in the Update-base itself). */
533 baserev = base_get (finfo);
534 node = findnode_fn (finfo->entries, finfo->file);
535 /* The case where node is NULL probably should be an error or
536 something, but I don't want to think about it too hard right
540 entdata = node->data;
543 /* This can only happen if the CVS/Baserev file got
544 corrupted. We suspect it might be possible if the
545 user interrupts CVS, although I haven't verified
547 error (0, 0, "%s not mentioned in %s", finfo->fullname,
550 /* Since we don't know what revision the file derives from,
551 keeping it around would be asking for trouble. */
552 if (unlink_file (finfo->file) < 0)
553 error (0, errno, "cannot remove %s", finfo->fullname);
555 /* This is cheesy, in a sense; why shouldn't we do the
556 update for the user? However, doing that would require
557 contacting the server, so maybe this is OK. */
558 error (0, 0, "run update to complete the unedit");
561 Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
562 entdata->options, entdata->tag, entdata->date,
566 base_deregister (finfo);
569 xchmod (finfo->file, 0);
573 static const char *const unedit_usage[] =
575 "Usage: %s %s [-lR] [files...]\n",
576 "-l: Local directory only, not recursive\n",
577 "-R: Process directories recursively\n",
578 "(Specify the --help global option for a list of other help options)\n",
592 usage (unedit_usage);
595 while ((c = getopt (argc, argv, "+lR")) != -1)
607 usage (unedit_usage);
614 /* No need to readlock since we aren't doing anything to the
616 err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
617 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
618 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
621 err += send_notifications (argc, argv, local);
627 mark_up_to_date (file)
632 /* The file is up to date, so we better get rid of an out of
633 date file in CVSADM_BASE. */
634 base = xmalloc (strlen (file) + 80);
635 strcpy (base, CVSADM_BASE);
638 if (unlink_file (base) < 0 && ! existence_error (errno))
639 error (0, errno, "cannot remove %s", file);
646 editor_set (filename, editor, val)
647 const char *filename;
654 edlist = fileattr_get0 (filename, "_editors");
655 newlist = fileattr_modify (edlist, editor, val, '>', ',');
656 /* If the attributes is unchanged, don't rewrite the attribute file. */
657 if (!((edlist == NULL && newlist == NULL)
660 && strcmp (edlist, newlist) == 0)))
661 fileattr_set (filename, "_editors", newlist);
668 struct notify_proc_args {
669 /* What kind of notification, "edit", "tedit", etc. */
671 /* User who is running the command which causes notification. */
673 /* User to be notified. */
674 const char *notifyee;
681 /* Pass as a static until we get around to fixing Parse_Info to pass along
682 a void * where we can stash it. */
683 static struct notify_proc_args *notify_args;
687 static int notify_proc PROTO ((const char *repository, const char *filter));
690 notify_proc (repository, filter)
691 const char *repository;
700 struct notify_proc_args *args = notify_args;
702 srepos = Short_Repository (repository);
703 prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
704 /* Copy FILTER to PROG, replacing the first occurrence of %s with
705 the notifyee. We only allocated enough memory for one %s, and I doubt
706 there is a need for more. */
707 for (p = filter, q = prog; *p != '\0'; ++p)
713 strcpy (q, args->notifyee);
726 /* FIXME: why are we calling expand_proc? Didn't we already
727 expand it in Parse_Info, before passing it to notify_proc? */
728 expanded_prog = expand_path (prog, "notify", 0);
735 pipefp = run_popen (expanded_prog, "w");
738 error (0, errno, "cannot write entry to notify filter: %s", prog);
740 free (expanded_prog);
744 fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
745 fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
746 fprintf (pipefp, "By %s\n", args->who);
748 /* Lots more potentially useful information we could add here; see
749 logfile_write for inspiration. */
752 free (expanded_prog);
753 return (pclose (pipefp));
756 /* FIXME: this function should have a way to report whether there was
757 an error so that server.c can know whether to report Notified back
760 notify_do (type, filename, who, val, watches, repository)
762 const char *filename;
766 const char *repository;
768 static struct addremove_args blank;
769 struct addremove_args args;
775 /* Initialize fields to 0, NULL, or 0.0. */
780 if (strpbrk (val, ",>;=\n") != NULL)
782 error (0, 0, "invalid character in editor value");
785 editor_set (filename, who, val);
789 editor_set (filename, who, NULL);
795 watchers = fileattr_get0 (filename, "_watchers");
804 endp = strchr (p, '>');
807 nextp = strchr (p, ',');
809 if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
811 /* Don't notify user of their own changes. Would perhaps
812 be better to check whether it is the same working
813 directory, not the same user, but that is hairy. */
814 p = nextp == NULL ? nextp : nextp + 1;
818 /* Now we point q at a string which looks like
819 "edit+unedit+commit,"... and walk down it. */
824 endq = strchr (q, '+');
825 if (endq == NULL || (nextp != NULL && endq > nextp))
828 endq = q + strlen (q);
836 /* If there is a temporary and a regular watch, send a single
837 notification, for the regular watch. */
838 if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
843 && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
848 && endq - q == 6 && strncmp ("commit", q, 6) == 0)
853 && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
856 notif = "temporary edit";
859 && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
862 notif = "temporary unedit";
865 && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
868 notif = "temporary commit";
877 struct notify_proc_args args;
878 size_t len = endp - p;
884 args.notifyee = NULL;
885 usersname = xmalloc (strlen (current_parsed_root->directory)
887 + sizeof CVSROOTADM_USERS
889 strcpy (usersname, current_parsed_root->directory);
890 strcat (usersname, "/");
891 strcat (usersname, CVSROOTADM);
892 strcat (usersname, "/");
893 strcat (usersname, CVSROOTADM_USERS);
894 fp = CVS_FOPEN (usersname, "r");
895 if (fp == NULL && !existence_error (errno))
896 error (0, errno, "cannot read %s", usersname);
899 while (getline (&line, &line_len, fp) >= 0)
901 if (strncmp (line, p, len) == 0
905 args.notifyee = xstrdup (line + len + 1);
907 /* There may or may not be more
908 colon-separated fields added to this in the
909 future; in any case, we ignore them right
910 now, and if there are none we make sure to
911 chop off the final newline, if any. */
912 cp = strpbrk (args.notifyee, ":\n");
920 error (0, errno, "cannot read %s", usersname);
922 error (0, errno, "cannot close %s", usersname);
928 if (args.notifyee == NULL)
931 tmp = xmalloc (endp - p + 1);
932 strncpy (tmp, p, endp - p);
933 tmp[endp - p] = '\0';
940 args.file = filename;
942 (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
944 /* It's okay to cast out the const for the free() below since we
945 * just allocated this a few lines above. The const was for
948 free ((char *)args.notifyee);
953 if (watchers != NULL)
966 args.add_tunedit = 1;
971 args.add_tcommit = 1;
973 watch_modify_watchers (filename, &args);
977 args.remove_temp = 1;
978 watch_modify_watchers (filename, &args);
983 #ifdef CLIENT_SUPPORT
984 /* Check and send notifications. This is only for the client. */
986 notify_check (repository, update_dir)
987 const char *repository;
988 const char *update_dir;
994 if (! server_started)
995 /* We are in the midst of a command which is not to talk to
996 the server (e.g. the first phase of a cvs edit). Just chill
997 out, we'll catch the notifications on the flip side. */
1000 /* We send notifications even if noexec. I'm not sure which behavior
1001 is most sensible. */
1003 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
1006 if (!existence_error (errno))
1007 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
1010 while (getline (&line, &line_len, fp) > 0)
1017 notif_type = line[0];
1018 if (notif_type == '\0')
1020 filename = line + 1;
1021 cp = strchr (filename, '\t');
1027 client_notify (repository, update_dir, filename, notif_type, val);
1032 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
1033 if (fclose (fp) < 0)
1034 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
1036 /* Leave the CVSADM_NOTIFY file there, until the server tells us it
1037 has dealt with it. */
1039 #endif /* CLIENT_SUPPORT */
1042 static const char *const editors_usage[] =
1044 "Usage: %s %s [-lR] [files...]\n",
1045 "\t-l\tProcess this directory only (not recursive).\n",
1046 "\t-R\tProcess directories recursively.\n",
1047 "(Specify the --help global option for a list of other help options)\n",
1051 static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1054 editors_fileproc (callerdat, finfo)
1056 struct file_info *finfo;
1061 them = fileattr_get0 (finfo->file, "_editors");
1065 cvs_output (finfo->fullname, 0);
1070 cvs_output ("\t", 1);
1071 while (*p != '>' && *p != '\0')
1072 cvs_output (p++, 1);
1075 /* Only happens if attribute is misformed. */
1076 cvs_output ("\n", 1);
1080 cvs_output ("\t", 1);
1083 while (*p != '+' && *p != ',' && *p != '\0')
1084 cvs_output (p++, 1);
1087 cvs_output ("\n", 1);
1096 cvs_output ("\t", 1);
1098 cvs_output ("\n", 1);
1106 editors (argc, argv)
1114 usage (editors_usage);
1117 while ((c = getopt (argc, argv, "+lR")) != -1)
1129 usage (editors_usage);
1136 #ifdef CLIENT_SUPPORT
1137 if (current_parsed_root->isremote)
1145 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
1146 send_file_names (argc, argv, SEND_EXPAND_WILD);
1147 send_to_server ("editors\012", 0);
1148 return get_responses_and_close ();
1150 #endif /* CLIENT_SUPPORT */
1152 return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
1153 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
1154 argc, argv, local, W_LOCAL, 0, 1, (char *) NULL,