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 (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;
31 onoff_fileproc (void *callerdat, struct file_info *finfo)
33 fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
40 onoff_filesdoneproc (void *callerdat, int err, const char *repository,
41 const char *update_dir, List *entries)
44 fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
51 watch_onoff (int argc, char **argv)
58 while ((c = getopt (argc, argv, "+lR")) != -1)
78 if (current_parsed_root->isremote)
87 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
88 send_file_names (argc, argv, SEND_EXPAND_WILD);
89 send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
90 return get_responses_and_close ();
92 #endif /* CLIENT_SUPPORT */
94 setting_default = (argc <= 0);
96 lock_tree_promotably (argc, argv, local, W_LOCAL, 0);
99 (onoff_fileproc, onoff_filesdoneproc,
101 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);
122 static int dummy_fileproc (void *callerdat, struct file_info *finfo);
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. */
133 static int ncheck_fileproc (void *callerdat, struct file_info *finfo);
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. */
142 ncheck_fileproc (void *callerdat, struct file_info *finfo)
154 /* We send notifications even if noexec. I'm not sure which behavior
157 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
160 if (!existence_error (errno))
161 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
165 while (getline (&line, &line_len, fp) > 0)
167 notif_type = line[0];
168 if (notif_type == '\0')
171 cp = strchr (filename, '\t');
176 cp = strchr (val, '\t');
180 cp = strchr (cp, '\t');
184 cp = strchr (cp, '\t');
189 cp = strchr (cp, '\n');
194 notify_do (notif_type, filename, getcaller (), val, watches,
200 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
202 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
204 if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
205 error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
210 static int send_notifications (int, char **, int);
212 /* Look through the CVSADM_NOTIFY file and process each item there
215 send_notifications (int argc, char **argv, int local)
219 #ifdef CLIENT_SUPPORT
220 /* OK, we've done everything which needs to happen on the client side.
221 Now we can try to contact the server; if we fail, then the
222 notifications stay in CVSADM_NOTIFY to be sent next time. */
223 if (current_parsed_root->isremote)
225 if (strcmp (cvs_cmd_name, "release") != 0)
231 err += start_recursion
232 ( dummy_fileproc, (FILESDONEPROC) NULL,
233 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
234 argc, argv, local, W_LOCAL, 0, 0, (char *) NULL,
237 send_to_server ("noop\012", 0);
238 if (strcmp (cvs_cmd_name, "release") == 0)
239 err += get_server_responses ();
241 err += get_responses_and_close ();
248 lock_tree_promotably (argc, argv, local, W_LOCAL, 0);
249 err += start_recursion
250 (ncheck_fileproc, NULL, NULL, NULL, NULL,
251 argc, argv, local, W_LOCAL, 0, CVS_LOCK_WRITE,
260 static int edit_fileproc (void *callerdat, struct file_info *finfo);
263 edit_fileproc (void *callerdat, struct file_info *finfo)
273 /* This is a somewhat screwy way to check for this, because it
274 doesn't help errors other than the nonexistence of the file
275 (e.g. permissions problems). It might be better to rearrange
276 the code so that CVSADM_NOTIFY gets written only after the
277 various actions succeed (but what if only some of them
279 if (!isfile (finfo->file))
281 error (0, 0, "no such file %s; ignored", finfo->fullname);
285 fp = open_file (CVSADM_NOTIFY, "a");
288 ascnow = asctime (gmtime (&now));
290 /* Fix non-standard format. */
291 if (ascnow[8] == '0') ascnow[8] = ' ';
292 fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
293 ascnow, hostname, CurDir);
304 if (finfo->update_dir[0] == '\0')
305 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
307 error (0, errno, "cannot close %s/%s", finfo->update_dir,
311 xchmod (finfo->file, 1);
313 /* Now stash the file away in CVSADM so that unedit can revert even if
314 it can't communicate with the server. We stash away a writable
315 copy so that if the user removes the working file, then restores it
316 with "cvs update" (which clears _editors but does not update
317 CVSADM_BASE), then a future "cvs edit" can still win. */
318 /* Could save a system call by only calling mkdir_if_needed if
319 trying to create the output file fails. But copy_file isn't
320 set up to facilitate that. */
321 mkdir_if_needed (CVSADM_BASE);
322 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
323 strcpy (basefilename, CVSADM_BASE);
324 strcat (basefilename, "/");
325 strcat (basefilename, finfo->file);
326 copy_file (finfo->file, basefilename);
332 node = findnode_fn (finfo->entries, finfo->file);
334 base_register (finfo, ((Entnode *) node->data)->version);
340 static const char *const edit_usage[] =
342 "Usage: %s %s [-lR] [files...]\n",
343 "-l: Local directory only, not recursive\n",
344 "-R: Process directories recursively\n",
345 "-a: Specify what actions for temporary watch, one of\n",
346 " edit,unedit,commit,all,none\n",
347 "(Specify the --help global option for a list of other help options)\n",
352 edit (int argc, char **argv)
367 while ((c = getopt (argc, argv, "+lRa:")) != -1)
379 if (strcmp (optarg, "edit") == 0)
381 else if (strcmp (optarg, "unedit") == 0)
383 else if (strcmp (optarg, "commit") == 0)
385 else if (strcmp (optarg, "all") == 0)
391 else if (strcmp (optarg, "none") == 0)
416 if (strpbrk (hostname, "+,>;=\t\n") != NULL)
418 "host name (%s) contains an invalid character (+,>;=\\t\\n)",
420 if (strpbrk (CurDir, "+,>;=\t\n") != NULL)
422 "current directory (%s) contains an invalid character (+,>;=\\t\\n)",
425 /* No need to readlock since we aren't doing anything to the
427 err = start_recursion
428 ( edit_fileproc, (FILESDONEPROC) NULL,
429 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
430 argc, argv, local, W_LOCAL, 0, 0, (char *) NULL,
433 err += send_notifications (argc, argv, local);
438 static int unedit_fileproc (void *callerdat, struct file_info *finfo);
441 unedit_fileproc (void *callerdat, struct file_info *finfo)
451 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
452 strcpy (basefilename, CVSADM_BASE);
453 strcat (basefilename, "/");
454 strcat (basefilename, finfo->file);
455 if (!isfile (basefilename))
457 /* This file apparently was never cvs edit'd (e.g. we are uneditting
458 a directory where only some of the files were cvs edit'd. */
463 if (xcmp (finfo->file, basefilename) != 0)
465 printf ("%s has been modified; revert changes? ", finfo->fullname);
473 rename_file (basefilename, finfo->file);
476 fp = open_file (CVSADM_NOTIFY, "a");
479 ascnow = asctime (gmtime (&now));
481 /* Fix non-standard format. */
482 if (ascnow[8] == '0') ascnow[8] = ' ';
483 fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
484 ascnow, hostname, CurDir);
488 if (finfo->update_dir[0] == '\0')
489 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
491 error (0, errno, "cannot close %s/%s", finfo->update_dir,
495 /* Now update the revision number in CVS/Entries from CVS/Baserev.
496 The basic idea here is that we are reverting to the revision
497 that the user edited. If we wanted "cvs update" to update
498 CVS/Base as we go along (so that an unedit could revert to the
499 current repository revision), we would need:
501 update (or all send_files?) (client) needs to send revision in
502 new Entry-base request. update (server/local) needs to check
503 revision against repository and send new Update-base response
504 (like Update-existing in that the file already exists. While
505 we are at it, might try to clean up the syntax by having the
506 mode only in a "Mode" response, not in the Update-base itself). */
512 baserev = base_get (finfo);
513 node = findnode_fn (finfo->entries, finfo->file);
514 /* The case where node is NULL probably should be an error or
515 something, but I don't want to think about it too hard right
519 entdata = node->data;
522 /* This can only happen if the CVS/Baserev file got
523 corrupted. We suspect it might be possible if the
524 user interrupts CVS, although I haven't verified
526 error (0, 0, "%s not mentioned in %s", finfo->fullname,
529 /* Since we don't know what revision the file derives from,
530 keeping it around would be asking for trouble. */
531 if (unlink_file (finfo->file) < 0)
532 error (0, errno, "cannot remove %s", finfo->fullname);
534 /* This is cheesy, in a sense; why shouldn't we do the
535 update for the user? However, doing that would require
536 contacting the server, so maybe this is OK. */
537 error (0, 0, "run update to complete the unedit");
540 Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
541 entdata->options, entdata->tag, entdata->date,
545 base_deregister (finfo);
548 xchmod (finfo->file, 0);
552 static const char *const unedit_usage[] =
554 "Usage: %s %s [-lR] [files...]\n",
555 "-l: Local directory only, not recursive\n",
556 "-R: Process directories recursively\n",
557 "(Specify the --help global option for a list of other help options)\n",
562 unedit (int argc, char **argv)
569 usage (unedit_usage);
572 while ((c = getopt (argc, argv, "+lR")) != -1)
584 usage (unedit_usage);
591 /* No need to readlock since we aren't doing anything to the
593 err = start_recursion
594 ( unedit_fileproc, (FILESDONEPROC) NULL,
595 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
596 argc, argv, local, W_LOCAL, 0, 0, (char *) NULL,
599 err += send_notifications (argc, argv, local);
607 mark_up_to_date (const char *file)
611 /* The file is up to date, so we better get rid of an out of
612 date file in CVSADM_BASE. */
613 base = xmalloc (strlen (file) + 80);
614 strcpy (base, CVSADM_BASE);
617 if (unlink_file (base) < 0 && ! existence_error (errno))
618 error (0, errno, "cannot remove %s", file);
625 editor_set (const char *filename, const char *editor, const char *val)
630 edlist = fileattr_get0 (filename, "_editors");
631 newlist = fileattr_modify (edlist, editor, val, '>', ',');
632 /* If the attributes is unchanged, don't rewrite the attribute file. */
633 if (!((edlist == NULL && newlist == NULL)
636 && strcmp (edlist, newlist) == 0)))
637 fileattr_set (filename, "_editors", newlist);
644 struct notify_proc_args {
645 /* What kind of notification, "edit", "tedit", etc. */
647 /* User who is running the command which causes notification. */
649 /* User to be notified. */
650 const char *notifyee;
658 notify_proc (const char *repository, const char *filter, void *closure)
662 const char *srepos = Short_Repository (repository);
663 struct notify_proc_args *args = (struct notify_proc_args *)closure;
665 cmdline = format_cmdline (
666 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
668 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
671 "r", "s", current_parsed_root->directory,
672 "s", "s", args->notifyee,
675 if (!cmdline || !strlen(cmdline))
677 if (cmdline) free (cmdline);
678 error(0, 0, "pretag proc resolved to the empty string!");
682 pipefp = run_popen (cmdline, "w");
685 error (0, errno, "cannot write entry to notify filter: %s", cmdline);
690 fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
691 fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
692 fprintf (pipefp, "By %s\n", args->who);
694 /* Lots more potentially useful information we could add here; see
695 logfile_write for inspiration. */
698 return (pclose (pipefp));
703 /* FIXME: this function should have a way to report whether there was
704 an error so that server.c can know whether to report Notified back
707 notify_do (int type, const char *filename, const char *who, const char *val,
708 const char *watches, const char *repository)
710 static struct addremove_args blank;
711 struct addremove_args args;
717 /* Initialize fields to 0, NULL, or 0.0. */
722 if (strpbrk (val, ",>;=\n") != NULL)
724 error (0, 0, "invalid character in editor value");
727 editor_set (filename, who, val);
731 editor_set (filename, who, NULL);
737 watchers = fileattr_get0 (filename, "_watchers");
746 endp = strchr (p, '>');
749 nextp = strchr (p, ',');
751 if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
753 /* Don't notify user of their own changes. Would perhaps
754 be better to check whether it is the same working
755 directory, not the same user, but that is hairy. */
756 p = nextp == NULL ? nextp : nextp + 1;
760 /* Now we point q at a string which looks like
761 "edit+unedit+commit,"... and walk down it. */
766 endq = strchr (q, '+');
767 if (endq == NULL || (nextp != NULL && endq > nextp))
770 endq = q + strlen (q);
778 /* If there is a temporary and a regular watch, send a single
779 notification, for the regular watch. */
780 if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
785 && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
790 && endq - q == 6 && strncmp ("commit", q, 6) == 0)
795 && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
798 notif = "temporary edit";
801 && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
804 notif = "temporary unedit";
807 && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
810 notif = "temporary commit";
819 struct notify_proc_args args;
820 size_t len = endp - p;
826 args.notifyee = NULL;
827 usersname = xmalloc (strlen (current_parsed_root->directory)
829 + sizeof CVSROOTADM_USERS
831 strcpy (usersname, current_parsed_root->directory);
832 strcat (usersname, "/");
833 strcat (usersname, CVSROOTADM);
834 strcat (usersname, "/");
835 strcat (usersname, CVSROOTADM_USERS);
836 fp = CVS_FOPEN (usersname, "r");
837 if (fp == NULL && !existence_error (errno))
838 error (0, errno, "cannot read %s", usersname);
841 while (getline (&line, &line_len, fp) >= 0)
843 if (strncmp (line, p, len) == 0
847 args.notifyee = xstrdup (line + len + 1);
849 /* There may or may not be more
850 colon-separated fields added to this in the
851 future; in any case, we ignore them right
852 now, and if there are none we make sure to
853 chop off the final newline, if any. */
854 cp = strpbrk (args.notifyee, ":\n");
862 error (0, errno, "cannot read %s", usersname);
864 error (0, errno, "cannot close %s", usersname);
870 if (args.notifyee == NULL)
873 tmp = xmalloc (endp - p + 1);
874 strncpy (tmp, p, endp - p);
875 tmp[endp - p] = '\0';
881 args.file = filename;
883 (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc,
885 /* It's okay to cast out the const for the free() below since we
886 * just allocated this a few lines above. The const was for
889 free ((char *)args.notifyee);
894 if (watchers != NULL)
907 args.add_tunedit = 1;
912 args.add_tcommit = 1;
914 watch_modify_watchers (filename, &args);
918 args.remove_temp = 1;
919 watch_modify_watchers (filename, &args);
926 #ifdef CLIENT_SUPPORT
927 /* Check and send notifications. This is only for the client. */
929 notify_check (const char *repository, const char *update_dir)
935 if (! server_started)
936 /* We are in the midst of a command which is not to talk to
937 the server (e.g. the first phase of a cvs edit). Just chill
938 out, we'll catch the notifications on the flip side. */
941 /* We send notifications even if noexec. I'm not sure which behavior
944 fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
947 if (!existence_error (errno))
948 error (0, errno, "cannot open %s", CVSADM_NOTIFY);
951 while (getline (&line, &line_len, fp) > 0)
958 notif_type = line[0];
959 if (notif_type == '\0')
962 cp = strchr (filename, '\t');
968 client_notify (repository, update_dir, filename, notif_type, val);
973 error (0, errno, "cannot read %s", CVSADM_NOTIFY);
975 error (0, errno, "cannot close %s", CVSADM_NOTIFY);
977 /* Leave the CVSADM_NOTIFY file there, until the server tells us it
978 has dealt with it. */
980 #endif /* CLIENT_SUPPORT */
983 static const char *const editors_usage[] =
985 "Usage: %s %s [-lR] [files...]\n",
986 "\t-l\tProcess this directory only (not recursive).\n",
987 "\t-R\tProcess directories recursively.\n",
988 "(Specify the --help global option for a list of other help options)\n",
992 static int editors_fileproc (void *callerdat, struct file_info *finfo);
995 editors_fileproc (void *callerdat, struct file_info *finfo)
1000 them = fileattr_get0 (finfo->file, "_editors");
1004 cvs_output (finfo->fullname, 0);
1009 cvs_output ("\t", 1);
1010 while (*p != '>' && *p != '\0')
1011 cvs_output (p++, 1);
1014 /* Only happens if attribute is misformed. */
1015 cvs_output ("\n", 1);
1019 cvs_output ("\t", 1);
1022 while (*p != '+' && *p != ',' && *p != '\0')
1023 cvs_output (p++, 1);
1026 cvs_output ("\n", 1);
1035 cvs_output ("\t", 1);
1037 cvs_output ("\n", 1);
1045 editors (int argc, char **argv)
1051 usage (editors_usage);
1054 while ((c = getopt (argc, argv, "+lR")) != -1)
1066 usage (editors_usage);
1073 #ifdef CLIENT_SUPPORT
1074 if (current_parsed_root->isremote)
1082 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
1083 send_file_names (argc, argv, SEND_EXPAND_WILD);
1084 send_to_server ("editors\012", 0);
1085 return get_responses_and_close ();
1087 #endif /* CLIENT_SUPPORT */
1089 return start_recursion
1090 ( editors_fileproc, (FILESDONEPROC) NULL,
1091 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
1092 argc, argv, local, W_LOCAL, 0, 1, (char *) NULL,