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. */
20 static int watch_onoff (int, char **);
22 static bool check_edited = false;
23 static int setting_default;
24 static int turning_on;
26 static bool setting_tedit;
27 static bool setting_tunedit;
28 static bool setting_tcommit;
33 onoff_fileproc (void *callerdat, struct file_info *finfo)
35 fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
42 onoff_filesdoneproc (void *callerdat, int err, const char *repository,
43 const char *update_dir, List *entries)
46 fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
53 watch_onoff (int argc, char **argv)
60 while ((c = getopt (argc, argv, "+lR")) != -1)
80 if (current_parsed_root->isremote)
89 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
90 send_file_names (argc, argv, SEND_EXPAND_WILD);
91 send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
92 return get_responses_and_close ();
94 #endif /* CLIENT_SUPPORT */
96 setting_default = (argc <= 0);
98 lock_tree_promotably (argc, argv, local, W_LOCAL, 0);
100 err = start_recursion (onoff_fileproc, onoff_filesdoneproc, NULL, NULL,
101 NULL, argc, argv, local, W_LOCAL, 0, CVS_LOCK_WRITE,
109 watch_on (int argc, char **argv)
112 return watch_onoff (argc, argv);
116 watch_off (int argc, char **argv)
119 return watch_onoff (argc, argv);
125 dummy_fileproc (void *callerdat, struct file_info *finfo)
127 /* This is a pretty hideous hack, but the gist of it is that recurse.c
128 won't call notify_check unless there is a fileproc, so we can't just
129 pass NULL for fileproc. */
135 /* Check for and process notifications. Local only. I think that doing
136 this as a fileproc is the only way to catch all the
137 cases (e.g. foo/bar.c), even though that means checking over and over
138 for the same CVSADM_NOTIFY file which we removed the first time we
139 processed the directory. */
141 ncheck_fileproc (void *callerdat, struct file_info *finfo)
153 /* We send notifications even if noexec. I'm not sure which behavior
156 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
159 if (!existence_error (errno))
160 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
164 while (getline (&line, &line_len, fp) > 0)
166 notif_type = line[0];
167 if (notif_type == '\0')
170 cp = strchr (filename, '\t');
175 cp = strchr (val, '\t');
179 cp = strchr (cp, '\t');
183 cp = strchr (cp, '\t');
188 cp = strchr (cp, '\n');
193 notify_do (notif_type, filename, finfo->update_dir, getcaller (), val,
194 watches, finfo->repository);
199 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
201 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
203 if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
204 error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
211 /* Look through the CVSADM_NOTIFY file and process each item there
214 send_notifications (int argc, char **argv, int local)
218 #ifdef CLIENT_SUPPORT
219 /* OK, we've done everything which needs to happen on the client side.
220 Now we can try to contact the server; if we fail, then the
221 notifications stay in CVSADM_NOTIFY to be sent next time. */
222 if (current_parsed_root->isremote)
224 if (strcmp (cvs_cmd_name, "release") != 0)
230 err += start_recursion (dummy_fileproc, NULL, NULL, NULL, NULL, argc,
231 argv, local, W_LOCAL, 0, 0, NULL, 0, NULL);
233 send_to_server ("noop\012", 0);
234 if (strcmp (cvs_cmd_name, "release") == 0)
235 err += get_server_responses ();
237 err += get_responses_and_close ();
244 err += start_recursion (ncheck_fileproc, NULL, NULL, NULL, NULL, argc,
245 argv, local, W_LOCAL, 0, CVS_LOCK_WRITE, NULL,
254 void editors_output (const char *fullname, const char *p)
256 cvs_output (fullname, 0);
260 cvs_output ("\t", 1);
261 while (*p != '>' && *p != '\0')
265 /* Only happens if attribute is misformed. */
266 cvs_output ("\n", 1);
270 cvs_output ("\t", 1);
273 while (*p != '+' && *p != ',' && *p != '\0')
277 cvs_output ("\n", 1);
286 cvs_output ("\t", 1);
288 cvs_output ("\n", 1);
294 static int find_editors_and_output (struct file_info *finfo)
298 them = fileattr_get0 (finfo->file, "_editors");
302 editors_output (finfo->fullname, them);
309 /* Handle the client-side details of editing a file.
311 * These args could be const but this needs to fit the call_in_directory API.
314 edit_file (void *data, List *ent_list, const char *short_pathname,
315 const char *filename)
318 struct file_info finfo;
321 xchmod (filename, 1);
323 mkdir_if_needed (CVSADM_BASE);
324 basefilename = Xasprintf ("%s/%s", CVSADM_BASE, filename);
325 copy_file (filename, basefilename);
328 node = findnode_fn (ent_list, filename);
331 finfo.file = filename;
332 finfo.fullname = short_pathname;
333 finfo.update_dir = dir_name (short_pathname);
334 base_register (&finfo, ((Entnode *) node->data)->version);
335 free ((char *)finfo.update_dir);
342 edit_fileproc (void *callerdat, struct file_info *finfo)
349 #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
350 assert (!(current_parsed_root->isremote && check_edited));
352 assert (!check_edited);
353 #endif /* defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
358 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
362 error (0, 0, "no such file %s; ignored", finfo->fullname);
366 #ifdef CLIENT_SUPPORT
367 if (!current_parsed_root->isremote)
368 #endif /* CLIENT_SUPPORT */
370 char *editors = fileattr_get0 (finfo->file, "_editors");
375 /* In the !CHECK_EDIT case, this message is printed by
379 editors_output (finfo->fullname, editors);
380 /* Now warn the user if we skip the file, then return. */
382 error (0, 0, "Skipping file `%s' due to existing editors.",
390 fp = open_file (CVSADM_NOTIFY, "a");
393 ascnow = asctime (gmtime (&now));
395 /* Fix non-standard format. */
396 if (ascnow[8] == '0') ascnow[8] = ' ';
397 fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
398 ascnow, hostname, CurDir);
409 if (finfo->update_dir[0] == '\0')
410 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
412 error (0, errno, "cannot close %s/%s", finfo->update_dir,
416 /* Now stash the file away in CVSADM so that unedit can revert even if
417 it can't communicate with the server. We stash away a writable
418 copy so that if the user removes the working file, then restores it
419 with "cvs update" (which clears _editors but does not update
420 CVSADM_BASE), then a future "cvs edit" can still win. */
421 /* Could save a system call by only calling mkdir_if_needed if
422 trying to create the output file fails. But copy_file isn't
423 set up to facilitate that. */
424 #ifdef SERVER_SUPPORT
426 server_edit_file (finfo);
428 #endif /* SERVER_SUPPORT */
429 edit_file (NULL, finfo->entries, finfo->fullname, finfo->file);
434 static const char *const edit_usage[] =
436 "Usage: %s %s [-cflR] [files...]\n",
437 "-c: Check that working files are unedited\n",
438 "-f: Force edit if working files are edited (default)\n",
439 "-l: Local directory only, not recursive\n",
440 "-R: Process directories recursively\n",
441 "-a: Specify what actions for temporary watch, one of\n",
442 " edit,unedit,commit,all,none\n",
443 "(Specify the --help global option for a list of other help options)\n",
448 edit (int argc, char **argv)
453 bool a_omitted, a_all, a_none;
461 setting_tedit = false;
462 setting_tunedit = false;
463 setting_tcommit = false;
465 while ((c = getopt (argc, argv, "+cflRa:")) != -1)
473 check_edited = false;
483 if (strcmp (optarg, "edit") == 0)
484 setting_tedit = true;
485 else if (strcmp (optarg, "unedit") == 0)
486 setting_tunedit = true;
487 else if (strcmp (optarg, "commit") == 0)
488 setting_tcommit = true;
489 else if (strcmp (optarg, "all") == 0)
493 setting_tedit = true;
494 setting_tunedit = true;
495 setting_tcommit = true;
497 else if (strcmp (optarg, "none") == 0)
501 setting_tedit = false;
502 setting_tunedit = false;
503 setting_tcommit = false;
517 if (strpbrk (hostname, "+,>;=\t\n") != NULL)
519 "host name (%s) contains an invalid character (+,>;=\\t\\n)",
521 if (strpbrk (CurDir, "+,>;=\t\n") != NULL)
523 "current directory (%s) contains an invalid character (+,>;=\\t\\n)",
526 #ifdef CLIENT_SUPPORT
527 if (check_edited && current_parsed_root->isremote)
529 /* When CHECK_EDITED, we might as well contact the server and let it do
530 * the work since we don't want an edit unless we know it is safe.
532 * When !CHECK_EDITED, we set up notifications and then attempt to
533 * contact the server in order to allow disconnected edits.
537 if (!supported_request ("edit"))
538 error (1, 0, "Server does not support enforced advisory locks.");
542 send_to_server ("Hostname ", 0);
543 send_to_server (hostname, 0);
544 send_to_server ("\012", 1);
545 send_to_server ("LocalDir ", 0);
546 send_to_server (CurDir, 0);
547 send_to_server ("\012", 1);
555 option_with_arg ("-a", "all");
557 option_with_arg ("-a", "none");
561 option_with_arg ("-a", "edit");
563 option_with_arg ("-a", "unedit");
565 option_with_arg ("-a", "commit");
569 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
570 send_file_names (argc, argv, SEND_EXPAND_WILD);
571 send_to_server ("edit\012", 0);
572 return get_responses_and_close ();
574 #endif /* CLIENT_SUPPORT */
576 /* Now, either SERVER_ACTIVE, local mode, or !CHECK_EDITED. */
580 setting_tedit = true;
581 setting_tunedit = true;
582 setting_tcommit = true;
585 TRACE (TRACE_DATA, "edit(): EUC: %d%d%d edit-check: %d",
586 setting_tedit, setting_tunedit, setting_tcommit, check_edited);
588 err = start_recursion (edit_fileproc, NULL, NULL, NULL, NULL, argc, argv,
589 local, W_LOCAL, 0, 0, NULL, 0, NULL);
591 err += send_notifications (argc, argv, local);
596 static int unedit_fileproc (void *callerdat, struct file_info *finfo);
599 unedit_fileproc (void *callerdat, struct file_info *finfo)
609 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
610 strcpy (basefilename, CVSADM_BASE);
611 strcat (basefilename, "/");
612 strcat (basefilename, finfo->file);
613 if (!isfile (basefilename))
615 /* This file apparently was never cvs edit'd (e.g. we are uneditting
616 a directory where only some of the files were cvs edit'd. */
621 if (xcmp (finfo->file, basefilename) != 0)
623 printf ("%s has been modified; revert changes? ", finfo->fullname);
633 rename_file (basefilename, finfo->file);
636 fp = open_file (CVSADM_NOTIFY, "a");
639 ascnow = asctime (gmtime (&now));
641 /* Fix non-standard format. */
642 if (ascnow[8] == '0') ascnow[8] = ' ';
643 fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
644 ascnow, hostname, CurDir);
648 if (finfo->update_dir[0] == '\0')
649 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
651 error (0, errno, "cannot close %s/%s", finfo->update_dir,
655 /* Now update the revision number in CVS/Entries from CVS/Baserev.
656 The basic idea here is that we are reverting to the revision
657 that the user edited. If we wanted "cvs update" to update
658 CVS/Base as we go along (so that an unedit could revert to the
659 current repository revision), we would need:
661 update (or all send_files?) (client) needs to send revision in
662 new Entry-base request. update (server/local) needs to check
663 revision against repository and send new Update-base response
664 (like Update-existing in that the file already exists. While
665 we are at it, might try to clean up the syntax by having the
666 mode only in a "Mode" response, not in the Update-base itself). */
672 baserev = base_get (finfo);
673 node = findnode_fn (finfo->entries, finfo->file);
674 /* The case where node is NULL probably should be an error or
675 something, but I don't want to think about it too hard right
679 entdata = node->data;
682 /* This can only happen if the CVS/Baserev file got
683 corrupted. We suspect it might be possible if the
684 user interrupts CVS, although I haven't verified
686 error (0, 0, "%s not mentioned in %s", finfo->fullname,
689 /* Since we don't know what revision the file derives from,
690 keeping it around would be asking for trouble. */
691 if (unlink_file (finfo->file) < 0)
692 error (0, errno, "cannot remove %s", finfo->fullname);
694 /* This is cheesy, in a sense; why shouldn't we do the
695 update for the user? However, doing that would require
696 contacting the server, so maybe this is OK. */
697 error (0, 0, "run update to complete the unedit");
700 Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
701 entdata->options, entdata->tag, entdata->date,
705 base_deregister (finfo);
708 xchmod (finfo->file, 0);
712 static const char *const unedit_usage[] =
714 "Usage: %s %s [-lR] [files...]\n",
715 "-l: Local directory only, not recursive\n",
716 "-R: Process directories recursively\n",
717 "(Specify the --help global option for a list of other help options)\n",
722 unedit (int argc, char **argv)
729 usage (unedit_usage);
732 while ((c = getopt (argc, argv, "+lR")) != -1)
744 usage (unedit_usage);
751 /* No need to readlock since we aren't doing anything to the
753 err = start_recursion (unedit_fileproc, NULL, NULL, NULL, NULL, argc, argv,
754 local, W_LOCAL, 0, 0, NULL, 0, NULL);
756 err += send_notifications (argc, argv, local);
764 mark_up_to_date (const char *file)
768 /* The file is up to date, so we better get rid of an out of
769 date file in CVSADM_BASE. */
770 base = xmalloc (strlen (file) + 80);
771 strcpy (base, CVSADM_BASE);
774 if (unlink_file (base) < 0 && ! existence_error (errno))
775 error (0, errno, "cannot remove %s", file);
782 editor_set (const char *filename, const char *editor, const char *val)
787 edlist = fileattr_get0 (filename, "_editors");
788 newlist = fileattr_modify (edlist, editor, val, '>', ',');
789 /* If the attributes is unchanged, don't rewrite the attribute file. */
790 if (!((edlist == NULL && newlist == NULL)
793 && strcmp (edlist, newlist) == 0)))
794 fileattr_set (filename, "_editors", newlist);
801 struct notify_proc_args {
802 /* What kind of notification, "edit", "tedit", etc. */
804 /* User who is running the command which causes notification. */
806 /* User to be notified. */
807 const char *notifyee;
815 notify_proc (const char *repository, const char *filter, void *closure)
819 const char *srepos = Short_Repository (repository);
820 struct notify_proc_args *args = closure;
822 cmdline = format_cmdline (
823 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
825 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
827 "c", "s", cvs_cmd_name,
828 #ifdef SERVER_SUPPORT
829 "R", "s", referrer ? referrer->original : "NONE",
830 #endif /* SERVER_SUPPORT */
832 "r", "s", current_parsed_root->directory,
833 "s", "s", args->notifyee,
836 if (!cmdline || !strlen(cmdline))
838 if (cmdline) free (cmdline);
839 error(0, 0, "pretag proc resolved to the empty string!");
843 pipefp = run_popen (cmdline, "w");
846 error (0, errno, "cannot write entry to notify filter: %s", cmdline);
851 fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
852 fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
853 fprintf (pipefp, "By %s\n", args->who);
855 /* Lots more potentially useful information we could add here; see
856 logfile_write for inspiration. */
859 return pclose (pipefp);
864 /* FIXME: this function should have a way to report whether there was
865 an error so that server.c can know whether to report Notified back
868 notify_do (int type, const char *filename, const char *update_dir,
869 const char *who, const char *val, const char *watches,
870 const char *repository)
872 static struct addremove_args blank;
873 struct addremove_args args;
879 /* Print out information on current editors if we were called during an
882 if (type == 'E' && !check_edited && !quiet)
884 char *editors = fileattr_get0 (filename, "_editors");
887 /* In the CHECK_EDIT case, this message is printed by
888 * edit_check. It needs to be done here too since files
889 * which are found to be edited when CHECK_EDIT are not
890 * added to the notify list.
893 if (update_dir && *update_dir)
894 tmp = Xasprintf ("%s/%s", update_dir, filename);
898 editors_output (tmp, editors);
900 if (update_dir && *update_dir) free ((char *)tmp);
905 /* Initialize fields to 0, NULL, or 0.0. */
910 if (strpbrk (val, ",>;=\n") != NULL)
912 error (0, 0, "invalid character in editor value");
915 editor_set (filename, who, val);
919 editor_set (filename, who, NULL);
925 watchers = fileattr_get0 (filename, "_watchers");
934 endp = strchr (p, '>');
937 nextp = strchr (p, ',');
939 if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
941 /* Don't notify user of their own changes. Would perhaps
942 be better to check whether it is the same working
943 directory, not the same user, but that is hairy. */
944 p = nextp == NULL ? nextp : nextp + 1;
948 /* Now we point q at a string which looks like
949 "edit+unedit+commit,"... and walk down it. */
954 endq = strchr (q, '+');
955 if (endq == NULL || (nextp != NULL && endq > nextp))
958 endq = q + strlen (q);
966 /* If there is a temporary and a regular watch, send a single
967 notification, for the regular watch. */
968 if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
973 && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
978 && endq - q == 6 && strncmp ("commit", q, 6) == 0)
983 && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
986 notif = "temporary edit";
989 && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
992 notif = "temporary unedit";
995 && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
998 notif = "temporary commit";
1007 struct notify_proc_args args;
1008 size_t len = endp - p;
1012 size_t line_len = 0;
1014 args.notifyee = NULL;
1015 usersname = xmalloc (strlen (current_parsed_root->directory)
1017 + sizeof CVSROOTADM_USERS
1019 strcpy (usersname, current_parsed_root->directory);
1020 strcat (usersname, "/");
1021 strcat (usersname, CVSROOTADM);
1022 strcat (usersname, "/");
1023 strcat (usersname, CVSROOTADM_USERS);
1024 fp = CVS_FOPEN (usersname, "r");
1025 if (fp == NULL && !existence_error (errno))
1026 error (0, errno, "cannot read %s", usersname);
1029 while (getline (&line, &line_len, fp) >= 0)
1031 if (strncmp (line, p, len) == 0
1032 && line[len] == ':')
1035 args.notifyee = xstrdup (line + len + 1);
1037 /* There may or may not be more
1038 colon-separated fields added to this in the
1039 future; in any case, we ignore them right
1040 now, and if there are none we make sure to
1041 chop off the final newline, if any. */
1042 cp = strpbrk (args.notifyee, ":\n");
1050 error (0, errno, "cannot read %s", usersname);
1051 if (fclose (fp) < 0)
1052 error (0, errno, "cannot close %s", usersname);
1058 if (args.notifyee == NULL)
1061 tmp = xmalloc (endp - p + 1);
1062 strncpy (tmp, p, endp - p);
1063 tmp[endp - p] = '\0';
1064 args.notifyee = tmp;
1069 args.file = filename;
1071 (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc,
1073 /* It's okay to cast out the const for the free() below since we
1074 * just allocated this a few lines above. The const was for
1077 free ((char *)args.notifyee);
1082 if (watchers != NULL)
1088 if (*watches == 'E')
1093 if (*watches == 'U')
1095 args.add_tunedit = 1;
1098 if (*watches == 'C')
1100 args.add_tcommit = 1;
1102 watch_modify_watchers (filename, &args);
1106 args.remove_temp = 1;
1107 watch_modify_watchers (filename, &args);
1114 #ifdef CLIENT_SUPPORT
1115 /* Check and send notifications. This is only for the client. */
1117 notify_check (const char *repository, const char *update_dir)
1121 size_t line_len = 0;
1123 if (!server_started)
1124 /* We are in the midst of a command which is not to talk to
1125 the server (e.g. the first phase of a cvs edit). Just chill
1126 out, we'll catch the notifications on the flip side. */
1129 /* We send notifications even if noexec. I'm not sure which behavior
1130 is most sensible. */
1132 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
1135 if (!existence_error (errno))
1136 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
1139 while (getline (&line, &line_len, fp) > 0)
1146 notif_type = line[0];
1147 if (notif_type == '\0')
1149 filename = line + 1;
1150 cp = strchr (filename, '\t');
1156 client_notify (repository, update_dir, filename, notif_type, val);
1161 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
1162 if (fclose (fp) < 0)
1163 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
1165 /* Leave the CVSADM_NOTIFY file there, until the server tells us it
1166 has dealt with it. */
1168 #endif /* CLIENT_SUPPORT */
1171 static const char *const editors_usage[] =
1173 "Usage: %s %s [-lR] [files...]\n",
1174 "\t-l\tProcess this directory only (not recursive).\n",
1175 "\t-R\tProcess directories recursively.\n",
1176 "(Specify the --help global option for a list of other help options)\n",
1183 editors_fileproc (void *callerdat, struct file_info *finfo)
1185 return find_editors_and_output (finfo);
1191 editors (int argc, char **argv)
1197 usage (editors_usage);
1200 while ((c = getopt (argc, argv, "+lR")) != -1)
1212 usage (editors_usage);
1219 #ifdef CLIENT_SUPPORT
1220 if (current_parsed_root->isremote)
1228 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
1229 send_file_names (argc, argv, SEND_EXPAND_WILD);
1230 send_to_server ("editors\012", 0);
1231 return get_responses_and_close ();
1233 #endif /* CLIENT_SUPPORT */
1235 return start_recursion (editors_fileproc, NULL, NULL, NULL, NULL,
1236 argc, argv, local, W_LOCAL, 0, CVS_LOCK_READ, NULL,