]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/src/rtag.c
Import cvs-1.9.24 since it came quite soon after the snapshot that was
[FreeBSD/FreeBSD.git] / contrib / cvs / src / rtag.c
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  * Copyright (c) 1989-1992, Brian Berliner
4  * 
5  * You may distribute under the terms of the GNU General Public License as
6  * specified in the README file that comes with the CVS source distribution.
7  * 
8  * Rtag
9  * 
10  * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
11  * Uses the modules database, if necessary.
12  */
13
14 #include "cvs.h"
15
16 static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
17 static int check_filesdoneproc PROTO ((void *callerdat, int err,
18                                        char *repos, char *update_dir,
19                                        List *entries));
20 static int pretag_proc PROTO((char *repository, char *filter));
21 static void masterlist_delproc PROTO((Node *p));
22 static void tag_delproc PROTO((Node *p));
23 static int pretag_list_proc PROTO((Node *p, void *closure));
24
25 static Dtype rtag_dirproc PROTO ((void *callerdat, char *dir,
26                                   char *repos, char *update_dir,
27                                   List *entries));
28 static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
29 static int rtag_filesdoneproc PROTO ((void *callerdat, int err,
30                                       char *repos, char *update_dir,
31                                       List *entries));
32 static int rtag_proc PROTO((int *pargc, char **argv, char *xwhere,
33                       char *mwhere, char *mfile, int shorten,
34                       int local_specified, char *mname, char *msg));
35 static int rtag_delete PROTO((RCSNode *rcsfile));
36
37
38 struct tag_info
39 {
40     Ctype status;
41     char *rev;
42     char *tag;
43     char *options;
44 };
45
46 struct master_lists
47 {
48     List *tlist;
49 };
50
51 static List *mtlist;
52 static List *tlist;
53
54 static char *symtag;
55 static char *numtag;
56 static int numtag_validated = 0;
57 static int delete_flag;                 /* adding a tag by default */
58 static int attic_too;                   /* remove tag from Attic files */
59 static int branch_mode;                 /* make an automagic "branch" tag */
60 static char *date;
61 static int local;                       /* recursive by default */
62 static int force_tag_match = 1;         /* force by default */
63 static int force_tag_move;              /* don't move existing tags by default */
64
65 static const char *const rtag_usage[] =
66 {
67     "Usage: %s %s [-aflRnF] [-b] [-d] [-r tag|-D date] tag modules...\n",
68     "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
69     "\t-f\tForce a head revision match if tag/date not found.\n",
70     "\t-l\tLocal directory only, not recursive\n",
71     "\t-R\tProcess directories recursively.\n",
72     "\t-n\tNo execution of 'tag program'\n",
73     "\t-d\tDelete the given Tag.\n",
74     "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
75     "\t-r rev\tExisting revision/tag.\n",
76     "\t-D\tExisting date.\n",
77     "\t-F\tMove tag if it already exists\n",    
78     "(Specify the --help global option for a list of other help options)\n",
79     NULL
80 };
81
82 int
83 rtag (argc, argv)
84     int argc;
85     char **argv;
86 {
87     register int i;
88     int c;
89     DBM *db;
90     int run_module_prog = 1;
91     int err = 0;
92
93     if (argc == -1)
94         usage (rtag_usage);
95
96     optind = 0;
97     while ((c = getopt (argc, argv, "+FanfQqlRdbr:D:")) != -1)
98     {
99         switch (c)
100         {
101             case 'a':
102                 attic_too = 1;
103                 break;
104             case 'n':
105                 run_module_prog = 0;
106                 break;
107             case 'Q':
108             case 'q':
109 #ifdef SERVER_SUPPORT
110                 /* The CVS 1.5 client sends these options (in addition to
111                    Global_option requests), so we must ignore them.  */
112                 if (!server_active)
113 #endif
114                     error (1, 0,
115                            "-q or -Q must be specified before \"%s\"",
116                            command_name);
117                 break;
118             case 'l':
119                 local = 1;
120                 break;
121             case 'R':
122                 local = 0;
123                 break;
124             case 'd':
125                 delete_flag = 1;
126                 break;
127             case 'f':
128                 force_tag_match = 0;
129                 break;
130             case 'b':
131                 branch_mode = 1;
132                 break;
133             case 'r':
134                 numtag = optarg;
135                 break;
136             case 'D':
137                 if (date)
138                     free (date);
139                 date = Make_Date (optarg);
140                 break;
141             case 'F':
142                 force_tag_move = 1;
143                 break;
144             case '?':
145             default:
146                 usage (rtag_usage);
147                 break;
148         }
149     }
150     argc -= optind;
151     argv += optind;
152     if (argc < 2)
153         usage (rtag_usage);
154     symtag = argv[0];
155     argc--;
156     argv++;
157
158     if (date && numtag)
159         error (1, 0, "-r and -D options are mutually exclusive");
160     if (delete_flag && branch_mode)
161         error (0, 0, "warning: -b ignored with -d options");
162     RCS_check_tag (symtag);
163
164 #ifdef CLIENT_SUPPORT
165     if (client_active)
166     {
167         /* We're the client side.  Fire up the remote server.  */
168         start_server ();
169
170         ign_setup ();
171
172         if (!force_tag_match)
173             send_arg ("-f");
174         if (local)
175             send_arg("-l");
176         if (delete_flag)
177             send_arg("-d");
178         if (branch_mode)
179             send_arg("-b");
180         if (force_tag_move)
181             send_arg("-F");
182         if (!run_module_prog)
183             send_arg("-n");
184         if (attic_too)
185             send_arg("-a");
186
187         if (numtag)
188             option_with_arg ("-r", numtag);
189         if (date)
190             client_senddate (date);
191
192         send_arg (symtag);
193
194         {
195             int i;
196             for (i = 0; i < argc; ++i)
197                 send_arg (argv[i]);
198         }
199
200         send_to_server ("rtag\012", 0);
201         return get_responses_and_close ();
202     }
203 #endif
204
205     db = open_module ();
206     for (i = 0; i < argc; i++)
207     {
208         /* XXX last arg should be repository, but doesn't make sense here */
209         history_write ('T', (delete_flag ? "D" : (numtag ? numtag : 
210                        (date ? date : "A"))), symtag, argv[i], "");
211         err += do_module (db, argv[i], TAG,
212                           delete_flag ? "Untagging" : "Tagging",
213                           rtag_proc, (char *) NULL, 0, 0, run_module_prog,
214                           symtag);
215     }
216     close_module (db);
217     return (err);
218 }
219
220 /*
221  * callback proc for doing the real work of tagging
222  */
223 /* ARGSUSED */
224 static int
225 rtag_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
226            mname, msg)
227     int *pargc;
228     char **argv;
229     char *xwhere;
230     char *mwhere;
231     char *mfile;
232     int shorten;
233     int local_specified;
234     char *mname;
235     char *msg;
236 {
237     /* Begin section which is identical to patch_proc--should this
238        be abstracted out somehow?  */
239     int err = 0;
240     int which;
241     char *repository;
242     char *where;
243
244     repository = xmalloc (strlen (CVSroot_directory) + strlen (argv[0])
245                           + (mfile == NULL ? 0 : strlen (mfile)) + 30);
246     (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]);
247     where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile))
248                      + 10);
249     (void) strcpy (where, argv[0]);
250
251     /* if mfile isn't null, we need to set up to do only part of the module */
252     if (mfile != NULL)
253     {
254         char *cp;
255         char *path;
256
257         /* if the portion of the module is a path, put the dir part on repos */
258         if ((cp = strrchr (mfile, '/')) != NULL)
259         {
260             *cp = '\0';
261             (void) strcat (repository, "/");
262             (void) strcat (repository, mfile);
263             (void) strcat (where, "/");
264             (void) strcat (where, mfile);
265             mfile = cp + 1;
266         }
267
268         /* take care of the rest */
269         path = xmalloc (strlen (repository) + strlen (mfile) + 5);
270         (void) sprintf (path, "%s/%s", repository, mfile);
271         if (isdir (path))
272         {
273             /* directory means repository gets the dir tacked on */
274             (void) strcpy (repository, path);
275             (void) strcat (where, "/");
276             (void) strcat (where, mfile);
277         }
278         else
279         {
280             int i;
281
282             /* a file means muck argv */
283             for (i = 1; i < *pargc; i++)
284                 free (argv[i]);
285             argv[1] = xstrdup (mfile);
286             (*pargc) = 2;
287         }
288         free (path);
289     }
290
291     /* cd to the starting repository */
292     if ( CVS_CHDIR (repository) < 0)
293     {
294         error (0, errno, "cannot chdir to %s", repository);
295         free (repository);
296         return (1);
297     }
298     free (repository);
299     /* End section which is identical to patch_proc.  */
300
301     if (delete_flag || attic_too || (force_tag_match && numtag))
302         which = W_REPOS | W_ATTIC;
303     else
304         which = W_REPOS;
305
306     if (numtag != NULL && !numtag_validated)
307     {
308         tag_check_valid (numtag, *pargc - 1, argv + 1, local, 0, NULL);
309         numtag_validated = 1;
310     }
311
312     /* check to make sure they are authorized to tag all the 
313        specified files in the repository */
314
315     mtlist = getlist();
316     err = start_recursion (check_fileproc, check_filesdoneproc,
317                            (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
318                            *pargc - 1, argv + 1, local, which, 0, 1,
319                            where, 1);
320     
321     if (err)
322     {
323        error (1, 0, "correct the above errors first!");
324     }
325      
326     /* start the recursion processor */
327     err = start_recursion (rtag_fileproc, rtag_filesdoneproc, rtag_dirproc,
328                            (DIRLEAVEPROC) NULL, NULL,
329                            *pargc - 1, argv + 1, local,
330                            which, 0, 0, where, 1);
331     free (where);
332     dellist(&mtlist);
333
334     return (err);
335 }
336
337 /* check file that is to be tagged */
338 /* All we do here is add it to our list */
339
340 static int
341 check_fileproc (callerdat, finfo)
342     void *callerdat;
343     struct file_info *finfo;
344 {
345     char *xdir;
346     Node *p;
347     Vers_TS *vers;
348     
349     if (finfo->update_dir[0] == '\0')
350         xdir = ".";
351     else
352         xdir = finfo->update_dir;
353     if ((p = findnode (mtlist, xdir)) != NULL)
354     {
355         tlist = ((struct master_lists *) p->data)->tlist;
356     }
357     else
358     {
359         struct master_lists *ml;
360         
361         tlist = getlist ();
362         p = getnode ();
363         p->key = xstrdup (xdir);
364         p->type = UPDATE;
365         ml = (struct master_lists *)
366             xmalloc (sizeof (struct master_lists));
367         ml->tlist = tlist;
368         p->data = (char *) ml;
369         p->delproc = masterlist_delproc;
370         (void) addnode (mtlist, p);
371     }
372     /* do tlist */
373     p = getnode ();
374     p->key = xstrdup (finfo->file);
375     p->type = UPDATE;
376     p->delproc = tag_delproc;
377     vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
378     p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match,
379                              (int *) NULL);
380     if (p->data != NULL)
381     {
382         int addit = 1;
383         char *oversion;
384         
385         oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
386                                    (int *) NULL);
387         if (oversion == NULL) 
388         {
389             if (delete_flag)
390             {
391                 addit = 0;
392             }
393         }
394         else if (strcmp(oversion, p->data) == 0)
395         {
396             addit = 0;
397         }
398         else if (!force_tag_move)
399         {
400             addit = 0;
401         }
402         if (oversion != NULL)
403         {
404             free(oversion);
405         }
406         if (!addit)
407         {
408             free(p->data);
409             p->data = NULL;
410         }
411     }
412     freevers_ts (&vers);
413     (void) addnode (tlist, p);
414     return (0);
415 }
416                          
417 static int
418 check_filesdoneproc (callerdat, err, repos, update_dir, entries)
419     void *callerdat;
420     int err;
421     char *repos;
422     char *update_dir;
423     List *entries;
424 {
425     int n;
426     Node *p;
427
428     p = findnode(mtlist, update_dir);
429     if (p != NULL)
430     {
431         tlist = ((struct master_lists *) p->data)->tlist;
432     }
433     else
434     {
435         tlist = (List *) NULL;
436     }
437     if ((tlist == NULL) || (tlist->list->next == tlist->list))
438     {
439         return (err);
440     }
441     if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
442     {
443         error (0, 0, "Pre-tag check failed");
444         err += n;
445     }
446     return (err);
447 }
448
449 static int
450 pretag_proc(repository, filter)
451     char *repository;
452     char *filter;
453 {
454     if (filter[0] == '/')
455     {
456         char *s, *cp;
457
458         s = xstrdup(filter);
459         for (cp=s; *cp; cp++)
460         {
461             if (isspace(*cp))
462             {
463                 *cp = '\0';
464                 break;
465             }
466         }
467         if (!isfile(s))
468         {
469             error (0, errno, "cannot find pre-tag filter '%s'", s);
470             free(s);
471             return (1);
472         }
473         free(s);
474     }
475     run_setup (filter);
476     run_arg (symtag);
477     run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
478     run_arg (repository);
479     walklist(tlist, pretag_list_proc, NULL);
480     return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
481 }
482
483 static void
484 masterlist_delproc(p)
485     Node *p;
486 {
487     struct master_lists *ml;
488
489     ml = (struct master_lists *)p->data;
490     dellist(&ml->tlist);
491     free(ml);
492     return;
493 }
494
495 static void
496 tag_delproc(p)
497     Node *p;
498 {
499     if (p->data != NULL)
500     {
501         free(p->data);
502         p->data = NULL;
503     }
504     return;
505 }
506
507 static int
508 pretag_list_proc(p, closure)
509     Node *p;
510     void *closure;
511 {
512     if (p->data != NULL)
513     {
514         run_arg(p->key);
515         run_arg(p->data);
516     }
517     return (0);
518 }
519
520 /*
521  * Called to tag a particular file, as appropriate with the options that were
522  * set above.
523  */
524 /* ARGSUSED */
525 static int
526 rtag_fileproc (callerdat, finfo)
527     void *callerdat;
528     struct file_info *finfo;
529 {
530     RCSNode *rcsfile;
531     char *version, *rev;
532     int retcode = 0;
533
534     /* Lock the directory if it is not already locked.  We might be
535        able to rely on rtag_dirproc for this.  */
536
537     /* It would be nice to provide consistency with respect to
538        commits; however CVS lacks the infrastructure to do that (see
539        Concurrency in cvs.texinfo and comment in do_recursion).  We
540        can and will prevent simultaneous tag operations from
541        interfering with each other, by write locking each directory as
542        we enter it, and unlocking it as we leave it.  */
543
544     lock_dir_for_write (finfo->repository);
545
546     /* find the parsed RCS data */
547     if ((rcsfile = finfo->rcs) == NULL)
548         return (1);
549
550     /*
551      * For tagging an RCS file which is a symbolic link, you'd best be
552      * running with RCS 5.6, since it knows how to handle symbolic links
553      * correctly without breaking your link!
554      */
555
556     if (delete_flag)
557         return (rtag_delete (rcsfile));
558
559     /*
560      * If we get here, we are adding a tag.  But, if -a was specified, we
561      * need to check to see if a -r or -D option was specified.  If neither
562      * was specified and the file is in the Attic, remove the tag.
563      */
564     if (attic_too && (!numtag && !date))
565     {
566         if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
567             return (rtag_delete (rcsfile));
568     }
569
570     version = RCS_getversion (rcsfile, numtag, date, force_tag_match,
571                               (int *) NULL);
572     if (version == NULL)
573     {
574         /* If -a specified, clean up any old tags */
575         if (attic_too)
576             (void) rtag_delete (rcsfile);
577
578         if (!quiet && !force_tag_match)
579         {
580             error (0, 0, "cannot find tag `%s' in `%s'",
581                    numtag ? numtag : "head", rcsfile->path);
582             return (1);
583         }
584         return (0);
585     }
586     if (numtag && isdigit (*numtag) && strcmp (numtag, version) != 0)
587     {
588
589         /*
590          * We didn't find a match for the numeric tag that was specified, but
591          * that's OK.  just pass the numeric tag on to rcs, to be tagged as
592          * specified.  Could get here if one tried to tag "1.1.1" and there
593          * was a 1.1.1 branch with some head revision.  In this case, we want
594          * the tag to reference "1.1.1" and not the revision at the head of
595          * the branch.  Use a symbolic tag for that.
596          */
597         rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
598         retcode = RCS_settag(rcsfile, symtag, numtag);
599         if (retcode == 0)
600             RCS_rewrite (rcsfile, NULL, NULL);
601     }
602     else
603     {
604         char *oversion;
605        
606         /*
607          * As an enhancement for the case where a tag is being re-applied to
608          * a large body of a module, make one extra call to RCS_getversion to
609          * see if the tag is already set in the RCS file.  If so, check to
610          * see if it needs to be moved.  If not, do nothing.  This will
611          * likely save a lot of time when simply moving the tag to the
612          * "current" head revisions of a module -- which I have found to be a
613          * typical tagging operation.
614          */
615         rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
616         oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
617                                    (int *) NULL);
618         if (oversion != NULL)
619         {
620             int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
621
622             /*
623              * if versions the same and neither old or new are branches don't
624              * have to do anything
625              */
626             if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
627             {
628                 free (oversion);
629                 free (version);
630                 return (0);
631             }
632           
633             if (!force_tag_move)
634             {
635                 /* we're NOT going to move the tag */
636                 (void) printf ("W %s", finfo->fullname);
637
638                 (void) printf (" : %s already exists on %s %s", 
639                                symtag, isbranch ? "branch" : "version",
640                                oversion);
641                 (void) printf (" : NOT MOVING tag to %s %s\n", 
642                                branch_mode ? "branch" : "version", rev);
643                 free (oversion);
644                 free (version);
645                 return (0);
646             }
647             free (oversion);
648         }
649         retcode = RCS_settag(rcsfile, symtag, rev);
650         if (retcode == 0)
651             RCS_rewrite (rcsfile, NULL, NULL);
652     }
653
654     if (retcode != 0)
655     {
656         error (1, retcode == -1 ? errno : 0,
657                "failed to set tag `%s' to revision `%s' in `%s'",
658                symtag, rev, rcsfile->path);
659         if (branch_mode)
660             free (rev);
661         free (version);
662         return (1);
663     }
664     if (branch_mode)
665         free (rev);
666     free (version);
667     return (0);
668 }
669
670 /*
671  * If -d is specified, "force_tag_match" is set, so that this call to
672  * RCS_getversion() will return a NULL version string if the symbolic
673  * tag does not exist in the RCS file.
674  * 
675  * If the -r flag was used, numtag is set, and we only delete the
676  * symtag from files that have numtag.
677  * 
678  * This is done here because it's MUCH faster than just blindly calling
679  * "rcs" to remove the tag... trust me.
680  */
681 static int
682 rtag_delete (rcsfile)
683     RCSNode *rcsfile;
684 {
685     char *version;
686     int retcode;
687
688     if (numtag)
689     {
690         version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1,
691                                   (int *) NULL);
692         if (version == NULL)
693             return (0);
694         free (version);
695     }
696
697     version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
698                               (int *) NULL);
699     if (version == NULL)
700         return (0);
701     free (version);
702
703     if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
704     {
705         if (!quiet)
706             error (0, retcode == -1 ? errno : 0,
707                    "failed to remove tag `%s' from `%s'", symtag,
708                    rcsfile->path);
709         return (1);
710     }
711     RCS_rewrite (rcsfile, NULL, NULL);
712     return (0);
713 }
714
715 /* Clear any lock we may hold on the current directory.  */
716
717 static int
718 rtag_filesdoneproc (callerdat, err, repos, update_dir, entries)
719     void *callerdat;
720     int err;
721     char *repos;
722     char *update_dir;
723     List *entries;
724 {
725     Lock_Cleanup ();
726
727     return (err);
728 }
729
730 /*
731  * Print a warm fuzzy message
732  */
733 /* ARGSUSED */
734 static Dtype
735 rtag_dirproc (callerdat, dir, repos, update_dir, entries)
736     void *callerdat;
737     char *dir;
738     char *repos;
739     char *update_dir;
740     List *entries;
741 {
742     if (ignore_directory (update_dir))
743     {
744         /* print the warm fuzzy message */
745         if (!quiet)
746           error (0, 0, "Ignoring %s", update_dir);
747         return R_SKIP_ALL;
748     }
749
750     if (!quiet)
751         error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging",
752                update_dir);
753     return (R_PROCESS);
754 }
755
756
757