File: | src/gnu/usr.bin/cvs/src/tag.c |
Warning: | line 1213, column 5 Value stored to 'err' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 | * Tag and Rtag |
9 | * |
10 | * Add or delete a symbolic name to an RCS file, or a collection of RCS files. |
11 | * Tag uses the checked out revision in the current directory, rtag uses |
12 | * the modules database, if necessary. |
13 | */ |
14 | |
15 | #include "cvs.h" |
16 | #include "savecwd.h" |
17 | |
18 | static int rtag_proc PROTO((int argc, char **argv, char *xwhere,(int argc, char **argv, char *xwhere, char *mwhere, char *mfile , int shorten, int local_specified, char *mname, char *msg) |
19 | char *mwhere, char *mfile, int shorten,(int argc, char **argv, char *xwhere, char *mwhere, char *mfile , int shorten, int local_specified, char *mname, char *msg) |
20 | int local_specified, char *mname, char *msg))(int argc, char **argv, char *xwhere, char *mwhere, char *mfile , int shorten, int local_specified, char *mname, char *msg); |
21 | static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
22 | static int check_filesdoneproc PROTO ((void *callerdat, int err,(void *callerdat, int err, char *repos, char *update_dir, List *entries) |
23 | char *repos, char *update_dir,(void *callerdat, int err, char *repos, char *update_dir, List *entries) |
24 | List *entries))(void *callerdat, int err, char *repos, char *update_dir, List *entries); |
25 | static int pretag_proc PROTO((char *repository, char *filter))(char *repository, char *filter); |
26 | static void masterlist_delproc PROTO((Node *p))(Node *p); |
27 | static void tag_delproc PROTO((Node *p))(Node *p); |
28 | static int pretag_list_proc PROTO((Node *p, void *closure))(Node *p, void *closure); |
29 | |
30 | static Dtype tag_dirproc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) |
31 | char *repos, char *update_dir,(void *callerdat, char *dir, char *repos, char *update_dir, List *entries) |
32 | List *entries))(void *callerdat, char *dir, char *repos, char *update_dir, List *entries); |
33 | static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
34 | static int rtag_delete PROTO((RCSNode *rcsfile))(RCSNode *rcsfile); |
35 | static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
36 | |
37 | static char *numtag; /* specific revision to tag */ |
38 | static int numtag_validated = 0; |
39 | static char *date = NULL((void*)0); |
40 | static char *symtag; /* tag to add or delete */ |
41 | static int delete_flag; /* adding a tag by default */ |
42 | static int branch_mode; /* make an automagic "branch" tag */ |
43 | static int local; /* recursive by default */ |
44 | static int force_tag_match = 1; /* force tag to match by default */ |
45 | static int force_tag_move; /* don't force tag to move by default */ |
46 | static int check_uptodate; /* no uptodate-check by default */ |
47 | static int attic_too; /* remove tag from Attic files */ |
48 | static int is_rtag; |
49 | |
50 | struct tag_info |
51 | { |
52 | Ctype status; |
53 | char *rev; |
54 | char *tag; |
55 | char *options; |
56 | }; |
57 | |
58 | struct master_lists |
59 | { |
60 | List *tlist; |
61 | }; |
62 | |
63 | static List *mtlist; |
64 | static List *tlist; |
65 | |
66 | static const char rtag_opts[] = "+abdFflnQqRr:D:"; |
67 | static const char *const rtag_usage[] = |
68 | { |
69 | "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n", |
70 | "\t-a\tClear tag from removed files that would not otherwise be tagged.\n", |
71 | "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", |
72 | "\t-d\tDelete the given tag.\n", |
73 | "\t-F\tMove tag if it already exists.\n", |
74 | "\t-f\tForce a head revision match if tag/date not found.\n", |
75 | "\t-l\tLocal directory only, not recursive.\n", |
76 | "\t-n\tNo execution of 'tag program'.\n", |
77 | "\t-R\tProcess directories recursively.\n", |
78 | "\t-r rev\tExisting revision/tag.\n", |
79 | "\t-D\tExisting date.\n", |
80 | "(Specify the --help global option for a list of other help options)\n", |
81 | NULL((void*)0) |
82 | }; |
83 | |
84 | static const char tag_opts[] = "+bcdFflQqRr:D:"; |
85 | static const char *const tag_usage[] = |
86 | { |
87 | "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n", |
88 | "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", |
89 | "\t-c\tCheck that working files are unmodified.\n", |
90 | "\t-d\tDelete the given tag.\n", |
91 | "\t-F\tMove tag if it already exists.\n", |
92 | "\t-f\tForce a head revision match if tag/date not found.\n", |
93 | "\t-l\tLocal directory only, not recursive.\n", |
94 | "\t-R\tProcess directories recursively.\n", |
95 | "\t-r rev\tExisting revision/tag.\n", |
96 | "\t-D\tExisting date.\n", |
97 | "(Specify the --help global option for a list of other help options)\n", |
98 | NULL((void*)0) |
99 | }; |
100 | |
101 | int |
102 | cvstag (argc, argv) |
103 | int argc; |
104 | char **argv; |
105 | { |
106 | int c; |
107 | int err = 0; |
108 | int run_module_prog = 1; |
109 | |
110 | is_rtag = (strcmp (command_name, "rtag") == 0); |
111 | |
112 | if (argc == -1) |
113 | usage (is_rtag ? rtag_usage : tag_usage); |
114 | |
115 | optind = 0; |
116 | while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1) |
117 | { |
118 | switch (c) |
119 | { |
120 | case 'a': |
121 | attic_too = 1; |
122 | break; |
123 | case 'b': |
124 | branch_mode = 1; |
125 | break; |
126 | case 'c': |
127 | check_uptodate = 1; |
128 | break; |
129 | case 'd': |
130 | delete_flag = 1; |
131 | break; |
132 | case 'F': |
133 | force_tag_move = 1; |
134 | break; |
135 | case 'f': |
136 | force_tag_match = 0; |
137 | break; |
138 | case 'l': |
139 | local = 1; |
140 | break; |
141 | case 'n': |
142 | run_module_prog = 0; |
143 | break; |
144 | case 'Q': |
145 | case 'q': |
146 | #ifdef SERVER_SUPPORT1 |
147 | /* The CVS 1.5 client sends these options (in addition to |
148 | Global_option requests), so we must ignore them. */ |
149 | if (!server_active) |
150 | #endif |
151 | error (1, 0, |
152 | "-q or -Q must be specified before \"%s\"", |
153 | command_name); |
154 | break; |
155 | case 'R': |
156 | local = 0; |
157 | break; |
158 | case 'r': |
159 | numtag = optarg; |
160 | break; |
161 | case 'D': |
162 | if (date) |
163 | free (date); |
164 | date = Make_Date (optarg); |
165 | break; |
166 | case '?': |
167 | default: |
168 | usage (is_rtag ? rtag_usage : tag_usage); |
169 | break; |
170 | } |
171 | } |
172 | argc -= optind; |
173 | argv += optind; |
174 | |
175 | if (argc < (is_rtag ? 2 : 1)) |
176 | usage (is_rtag ? rtag_usage : tag_usage); |
177 | symtag = argv[0]; |
178 | argc--; |
179 | argv++; |
180 | |
181 | if (date && numtag) |
182 | error (1, 0, "-r and -D options are mutually exclusive"); |
183 | if (delete_flag && branch_mode) |
184 | error (0, 0, "warning: -b ignored with -d options"); |
185 | RCS_check_tag (symtag); |
186 | |
187 | #ifdef CLIENT_SUPPORT1 |
188 | if (current_parsed_root->isremote) |
189 | { |
190 | /* We're the client side. Fire up the remote server. */ |
191 | start_server (); |
192 | |
193 | ign_setup (); |
194 | |
195 | if (attic_too) |
196 | send_arg("-a"); |
197 | if (branch_mode) |
198 | send_arg("-b"); |
199 | if (check_uptodate) |
200 | send_arg("-c"); |
201 | if (delete_flag) |
202 | send_arg("-d"); |
203 | if (force_tag_move) |
204 | send_arg("-F"); |
205 | if (!force_tag_match) |
206 | send_arg ("-f"); |
207 | if (local) |
208 | send_arg("-l"); |
209 | if (!run_module_prog) |
210 | send_arg("-n"); |
211 | |
212 | if (numtag) |
213 | option_with_arg ("-r", numtag); |
214 | if (date) |
215 | client_senddate (date); |
216 | |
217 | send_arg (symtag); |
218 | |
219 | if (is_rtag) |
220 | { |
221 | int i; |
222 | for (i = 0; i < argc; ++i) |
223 | send_arg (argv[i]); |
224 | send_to_server ("rtag\012", 0); |
225 | } |
226 | else |
227 | { |
228 | |
229 | send_files (argc, argv, local, 0, |
230 | |
231 | /* I think the -c case is like "cvs status", in |
232 | which we really better be correct rather than |
233 | being fast; it is just too confusing otherwise. */ |
234 | check_uptodate ? 0 : SEND_NO_CONTENTS4); |
235 | send_file_names (argc, argv, SEND_EXPAND_WILD1); |
236 | send_to_server ("tag\012", 0); |
237 | } |
238 | |
239 | return get_responses_and_close (); |
240 | } |
241 | #endif |
242 | |
243 | if (is_rtag) |
244 | { |
245 | DBM *db; |
246 | int i; |
247 | db = open_module (); |
248 | for (i = 0; i < argc; i++) |
249 | { |
250 | /* XXX last arg should be repository, but doesn't make sense here */ |
251 | history_write ('T', (delete_flag ? "D" : (numtag ? numtag : |
252 | (date ? date : "A"))), symtag, argv[i], ""); |
253 | err += do_module (db, argv[i], TAG, |
254 | delete_flag ? "Untagging" : "Tagging", |
255 | rtag_proc, (char *) NULL((void*)0), 0, 0, run_module_prog, |
256 | 0, symtag); |
257 | } |
258 | close_module (db); |
259 | } |
260 | else |
261 | { |
262 | err = rtag_proc (argc + 1, argv - 1, NULL((void*)0), NULL((void*)0), NULL((void*)0), 0, 0, NULL((void*)0), |
263 | NULL((void*)0)); |
264 | } |
265 | |
266 | return (err); |
267 | } |
268 | |
269 | /* |
270 | * callback proc for doing the real work of tagging |
271 | */ |
272 | /* ARGSUSED */ |
273 | static int |
274 | rtag_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified, |
275 | mname, msg) |
276 | int argc; |
277 | char **argv; |
278 | char *xwhere; |
279 | char *mwhere; |
280 | char *mfile; |
281 | int shorten; |
282 | int local_specified; |
283 | char *mname; |
284 | char *msg; |
285 | { |
286 | /* Begin section which is identical to patch_proc--should this |
287 | be abstracted out somehow? */ |
288 | char *myargv[2]; |
289 | int err = 0; |
290 | int which; |
291 | char *repository; |
292 | char *where; |
293 | |
294 | if (is_rtag) |
295 | { |
296 | repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0]) |
297 | + (mfile == NULL((void*)0) ? 0 : strlen (mfile) + 1) + 2); |
298 | (void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]); |
299 | where = xmalloc (strlen (argv[0]) + (mfile == NULL((void*)0) ? 0 : strlen (mfile) + 1) |
300 | + 1); |
301 | (void) strcpy (where, argv[0]); |
302 | |
303 | /* if mfile isn't null, we need to set up to do only part of the module */ |
304 | if (mfile != NULL((void*)0)) |
305 | { |
306 | char *cp; |
307 | char *path; |
308 | |
309 | /* if the portion of the module is a path, put the dir part on repos */ |
310 | if ((cp = strrchr (mfile, '/')) != NULL((void*)0)) |
311 | { |
312 | *cp = '\0'; |
313 | (void) strcat (repository, "/"); |
314 | (void) strcat (repository, mfile); |
315 | (void) strcat (where, "/"); |
316 | (void) strcat (where, mfile); |
317 | mfile = cp + 1; |
318 | } |
319 | |
320 | /* take care of the rest */ |
321 | path = xmalloc (strlen (repository) + strlen (mfile) + 5); |
322 | (void) sprintf (path, "%s/%s", repository, mfile); |
323 | if (isdir (path)) |
324 | { |
325 | /* directory means repository gets the dir tacked on */ |
326 | (void) strcpy (repository, path); |
327 | (void) strcat (where, "/"); |
328 | (void) strcat (where, mfile); |
329 | } |
330 | else |
331 | { |
332 | myargv[0] = argv[0]; |
333 | myargv[1] = mfile; |
334 | argc = 2; |
335 | argv = myargv; |
336 | } |
337 | free (path); |
338 | } |
339 | |
340 | /* cd to the starting repository */ |
341 | if ( CVS_CHDIRchdir (repository) < 0) |
342 | { |
343 | error (0, errno(*__errno()), "cannot chdir to %s", repository); |
344 | free (repository); |
345 | return (1); |
346 | } |
347 | free (repository); |
348 | /* End section which is identical to patch_proc. */ |
349 | |
350 | if (delete_flag || attic_too || (force_tag_match && numtag)) |
351 | which = W_REPOS0x02 | W_ATTIC0x04; |
352 | else |
353 | which = W_REPOS0x02; |
354 | repository = NULL((void*)0); |
355 | } |
356 | else |
357 | { |
358 | where = NULL((void*)0); |
359 | which = W_LOCAL0x01; |
360 | repository = ""; |
361 | } |
362 | |
363 | if (numtag != NULL((void*)0) && !numtag_validated) |
364 | { |
365 | tag_check_valid (numtag, argc - 1, argv + 1, local, 0, repository); |
366 | numtag_validated = 1; |
367 | } |
368 | |
369 | /* check to make sure they are authorized to tag all the |
370 | specified files in the repository */ |
371 | |
372 | mtlist = getlist(); |
373 | err = start_recursion (check_fileproc, check_filesdoneproc, |
374 | (DIRENTPROC) NULL((void*)0), (DIRLEAVEPROC) NULL((void*)0), NULL((void*)0), |
375 | argc - 1, argv + 1, local, which, 0, 1, |
376 | where, 1); |
377 | |
378 | if (err) |
379 | { |
380 | error (1, 0, "correct the above errors first!"); |
381 | } |
382 | |
383 | /* It would be nice to provide consistency with respect to |
384 | commits; however CVS lacks the infrastructure to do that (see |
385 | Concurrency in cvs.texinfo and comment in do_recursion). We |
386 | do need to ensure that the RCS file info that gets read and |
387 | cached in do_recursion isn't stale by the time we get around |
388 | to using it to rewrite the RCS file in the callback, and this |
389 | is the easiest way to accomplish that. */ |
390 | lock_tree_for_write (argc - 1, argv + 1, local, which, 0); |
391 | |
392 | /* start the recursion processor */ |
393 | err = start_recursion (is_rtag ? rtag_fileproc : tag_fileproc, |
394 | (FILESDONEPROC) NULL((void*)0), tag_dirproc, |
395 | (DIRLEAVEPROC) NULL((void*)0), NULL((void*)0), argc - 1, argv + 1, |
396 | local, which, 0, 0, where, 1); |
397 | Lock_Cleanup (); |
398 | dellist (&mtlist); |
399 | if (where != NULL((void*)0)) |
400 | free (where); |
401 | return (err); |
402 | } |
403 | |
404 | /* check file that is to be tagged */ |
405 | /* All we do here is add it to our list */ |
406 | |
407 | static int |
408 | check_fileproc (callerdat, finfo) |
409 | void *callerdat; |
410 | struct file_info *finfo; |
411 | { |
412 | char *xdir; |
413 | Node *p; |
414 | Vers_TS *vers; |
415 | |
416 | if (check_uptodate) |
417 | { |
418 | Ctype status = Classify_File (finfo, (char *) NULL((void*)0), (char *) NULL((void*)0), |
419 | (char *) NULL((void*)0), 1, 0, &vers, 0); |
420 | if ((status != T_UPTODATE) && (status != T_CHECKOUT) && |
421 | (status != T_PATCH)) |
422 | { |
423 | error (0, 0, "%s is locally modified", finfo->fullname); |
424 | freevers_ts (&vers); |
425 | return (1); |
426 | } |
427 | } |
428 | else |
429 | vers = Version_TS (finfo, NULL((void*)0), NULL((void*)0), NULL((void*)0), 0, 0); |
430 | |
431 | if (finfo->update_dir[0] == '\0') |
432 | xdir = "."; |
433 | else |
434 | xdir = finfo->update_dir; |
435 | if ((p = findnode (mtlist, xdir)) != NULL((void*)0)) |
436 | { |
437 | tlist = ((struct master_lists *) p->data)->tlist; |
438 | } |
439 | else |
440 | { |
441 | struct master_lists *ml; |
442 | |
443 | tlist = getlist (); |
444 | p = getnode (); |
445 | p->key = xstrdup (xdir); |
446 | p->type = UPDATE; |
447 | ml = (struct master_lists *) |
448 | xmalloc (sizeof (struct master_lists)); |
449 | ml->tlist = tlist; |
450 | p->data = (char *) ml; |
451 | p->delproc = masterlist_delproc; |
452 | (void) addnode (mtlist, p); |
453 | } |
454 | /* do tlist */ |
455 | p = getnode (); |
456 | p->key = xstrdup (finfo->file); |
457 | p->type = UPDATE; |
458 | p->delproc = tag_delproc; |
459 | if (vers->srcfile == NULL((void*)0)) |
460 | { |
461 | if (!really_quiet) |
462 | error (0, 0, "nothing known about %s", finfo->file); |
463 | freevers_ts (&vers); |
464 | freenode (p); |
465 | return (1); |
466 | } |
467 | |
468 | /* Here we duplicate the calculation in tag_fileproc about which |
469 | version we are going to tag. There probably are some subtle races |
470 | (e.g. numtag is "foo" which gets moved between here and |
471 | tag_fileproc). */ |
472 | if (!is_rtag && numtag == NULL((void*)0) && date == NULL((void*)0)) |
473 | p->data = xstrdup (vers->vn_user); |
474 | else |
475 | p->data = RCS_getversion (vers->srcfile, numtag, date, |
476 | force_tag_match, NULL((void*)0)); |
477 | |
478 | if (p->data != NULL((void*)0)) |
479 | { |
480 | int addit = 1; |
481 | char *oversion; |
482 | |
483 | oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL((void*)0), 1, |
484 | (int *) NULL((void*)0)); |
485 | if (oversion == NULL((void*)0)) |
486 | { |
487 | if (delete_flag) |
488 | { |
489 | /* Deleting a tag which did not exist is a noop and |
490 | should not be logged. */ |
491 | addit = 0; |
492 | } |
493 | } |
494 | else if (delete_flag) |
495 | { |
496 | free (p->data); |
497 | p->data = xstrdup (oversion); |
498 | } |
499 | else if (strcmp(oversion, p->data) == 0) |
500 | { |
501 | addit = 0; |
502 | } |
503 | else if (!force_tag_move) |
504 | { |
505 | addit = 0; |
506 | } |
507 | if (oversion != NULL((void*)0)) |
508 | { |
509 | free(oversion); |
510 | } |
511 | if (!addit) |
512 | { |
513 | free(p->data); |
514 | p->data = NULL((void*)0); |
515 | } |
516 | } |
517 | freevers_ts (&vers); |
518 | (void) addnode (tlist, p); |
519 | return (0); |
520 | } |
521 | |
522 | static int |
523 | check_filesdoneproc (callerdat, err, repos, update_dir, entries) |
524 | void *callerdat; |
525 | int err; |
526 | char *repos; |
527 | char *update_dir; |
528 | List *entries; |
529 | { |
530 | int n; |
531 | Node *p; |
532 | |
533 | p = findnode(mtlist, update_dir); |
534 | if (p != NULL((void*)0)) |
535 | { |
536 | tlist = ((struct master_lists *) p->data)->tlist; |
537 | } |
538 | else |
539 | { |
540 | tlist = (List *) NULL((void*)0); |
541 | } |
542 | if ((tlist == NULL((void*)0)) || (tlist->list->next == tlist->list)) |
543 | { |
544 | return (err); |
545 | } |
546 | if ((n = Parse_Info(CVSROOTADM_TAGINFO"taginfo", repos, pretag_proc, 1)) > 0) |
547 | { |
548 | error (0, 0, "Pre-tag check failed"); |
549 | err += n; |
550 | } |
551 | return (err); |
552 | } |
553 | |
554 | static int |
555 | pretag_proc(repository, filter) |
556 | char *repository; |
557 | char *filter; |
558 | { |
559 | if (filter[0] == '/') |
560 | { |
561 | char *s, *cp; |
562 | |
563 | s = xstrdup(filter); |
564 | for (cp=s; *cp; cp++) |
565 | { |
566 | if (isspace ((unsigned char) *cp)) |
567 | { |
568 | *cp = '\0'; |
569 | break; |
570 | } |
571 | } |
572 | if (!isfile(s)) |
573 | { |
574 | error (0, errno(*__errno()), "cannot find pre-tag filter '%s'", s); |
575 | free(s); |
576 | return (1); |
577 | } |
578 | free(s); |
579 | } |
580 | run_setup (filter); |
581 | run_arg (symtag); |
582 | run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add"); |
583 | run_arg (repository); |
584 | walklist(tlist, pretag_list_proc, NULL((void*)0)); |
585 | return (run_exec (RUN_TTY(char *)0, RUN_TTY(char *)0, RUN_TTY(char *)0, RUN_NORMAL0x0000)); |
586 | } |
587 | |
588 | static void |
589 | masterlist_delproc(p) |
590 | Node *p; |
591 | { |
592 | struct master_lists *ml; |
593 | |
594 | ml = (struct master_lists *)p->data; |
595 | dellist(&ml->tlist); |
596 | free(ml); |
597 | return; |
598 | } |
599 | |
600 | static void |
601 | tag_delproc(p) |
602 | Node *p; |
603 | { |
604 | if (p->data != NULL((void*)0)) |
605 | { |
606 | free(p->data); |
607 | p->data = NULL((void*)0); |
608 | } |
609 | return; |
610 | } |
611 | |
612 | static int |
613 | pretag_list_proc(p, closure) |
614 | Node *p; |
615 | void *closure; |
616 | { |
617 | if (p->data != NULL((void*)0)) |
618 | { |
619 | run_arg(p->key); |
620 | run_arg(p->data); |
621 | } |
622 | return (0); |
623 | } |
624 | |
625 | |
626 | /* |
627 | * Called to rtag a particular file, as appropriate with the options that were |
628 | * set above. |
629 | */ |
630 | /* ARGSUSED */ |
631 | static int |
632 | rtag_fileproc (callerdat, finfo) |
633 | void *callerdat; |
634 | struct file_info *finfo; |
635 | { |
636 | RCSNode *rcsfile; |
637 | char *version, *rev; |
638 | int retcode = 0; |
639 | |
640 | /* find the parsed RCS data */ |
641 | if ((rcsfile = finfo->rcs) == NULL((void*)0)) |
642 | return (1); |
643 | |
644 | /* |
645 | * For tagging an RCS file which is a symbolic link, you'd best be |
646 | * running with RCS 5.6, since it knows how to handle symbolic links |
647 | * correctly without breaking your link! |
648 | */ |
649 | |
650 | if (delete_flag) |
651 | return (rtag_delete (rcsfile)); |
652 | |
653 | /* |
654 | * If we get here, we are adding a tag. But, if -a was specified, we |
655 | * need to check to see if a -r or -D option was specified. If neither |
656 | * was specified and the file is in the Attic, remove the tag. |
657 | */ |
658 | if (attic_too && (!numtag && !date)) |
659 | { |
660 | if ((rcsfile->flags & VALID0x1) && (rcsfile->flags & INATTIC0x2)) |
661 | return (rtag_delete (rcsfile)); |
662 | } |
663 | |
664 | version = RCS_getversion (rcsfile, numtag, date, force_tag_match, |
665 | (int *) NULL((void*)0)); |
666 | if (version == NULL((void*)0)) |
667 | { |
668 | /* If -a specified, clean up any old tags */ |
669 | if (attic_too) |
670 | (void) rtag_delete (rcsfile); |
671 | |
672 | if (!quiet && !force_tag_match) |
673 | { |
674 | error (0, 0, "cannot find tag `%s' in `%s'", |
675 | numtag ? numtag : "head", rcsfile->path); |
676 | return (1); |
677 | } |
678 | return (0); |
679 | } |
680 | if (numtag |
681 | && isdigit ((unsigned char) *numtag) |
682 | && strcmp (numtag, version) != 0) |
683 | { |
684 | |
685 | /* |
686 | * We didn't find a match for the numeric tag that was specified, but |
687 | * that's OK. just pass the numeric tag on to rcs, to be tagged as |
688 | * specified. Could get here if one tried to tag "1.1.1" and there |
689 | * was a 1.1.1 branch with some head revision. In this case, we want |
690 | * the tag to reference "1.1.1" and not the revision at the head of |
691 | * the branch. Use a symbolic tag for that. |
692 | */ |
693 | rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag; |
694 | retcode = RCS_settag(rcsfile, symtag, numtag); |
695 | if (retcode == 0) |
696 | RCS_rewrite (rcsfile, NULL((void*)0), NULL((void*)0)); |
697 | } |
698 | else |
699 | { |
700 | char *oversion; |
701 | |
702 | /* |
703 | * As an enhancement for the case where a tag is being re-applied to |
704 | * a large body of a module, make one extra call to RCS_getversion to |
705 | * see if the tag is already set in the RCS file. If so, check to |
706 | * see if it needs to be moved. If not, do nothing. This will |
707 | * likely save a lot of time when simply moving the tag to the |
708 | * "current" head revisions of a module -- which I have found to be a |
709 | * typical tagging operation. |
710 | */ |
711 | rev = branch_mode ? RCS_magicrev (rcsfile, version) : version; |
712 | oversion = RCS_getversion (rcsfile, symtag, (char *) NULL((void*)0), 1, |
713 | (int *) NULL((void*)0)); |
714 | if (oversion != NULL((void*)0)) |
715 | { |
716 | int isbranch = RCS_nodeisbranch (finfo->rcs, symtag); |
717 | |
718 | /* |
719 | * if versions the same and neither old or new are branches don't |
720 | * have to do anything |
721 | */ |
722 | if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch) |
723 | { |
724 | free (oversion); |
725 | free (version); |
726 | return (0); |
727 | } |
728 | |
729 | if (!force_tag_move) |
730 | { |
731 | /* we're NOT going to move the tag */ |
732 | (void) printf ("W %s", finfo->fullname); |
733 | |
734 | (void) printf (" : %s already exists on %s %s", |
735 | symtag, isbranch ? "branch" : "version", |
736 | oversion); |
737 | (void) printf (" : NOT MOVING tag to %s %s\n", |
738 | branch_mode ? "branch" : "version", rev); |
739 | free (oversion); |
740 | free (version); |
741 | return (0); |
742 | } |
743 | free (oversion); |
744 | } |
745 | retcode = RCS_settag(rcsfile, symtag, rev); |
746 | if (retcode == 0) |
747 | RCS_rewrite (rcsfile, NULL((void*)0), NULL((void*)0)); |
748 | } |
749 | |
750 | if (retcode != 0) |
751 | { |
752 | error (1, retcode == -1 ? errno(*__errno()) : 0, |
753 | "failed to set tag `%s' to revision `%s' in `%s'", |
754 | symtag, rev, rcsfile->path); |
755 | if (branch_mode) |
756 | free (rev); |
757 | free (version); |
758 | return (1); |
759 | } |
760 | if (branch_mode) |
761 | free (rev); |
762 | free (version); |
763 | return (0); |
764 | } |
765 | |
766 | /* |
767 | * If -d is specified, "force_tag_match" is set, so that this call to |
768 | * RCS_getversion() will return a NULL version string if the symbolic |
769 | * tag does not exist in the RCS file. |
770 | * |
771 | * If the -r flag was used, numtag is set, and we only delete the |
772 | * symtag from files that have numtag. |
773 | * |
774 | * This is done here because it's MUCH faster than just blindly calling |
775 | * "rcs" to remove the tag... trust me. |
776 | */ |
777 | static int |
778 | rtag_delete (rcsfile) |
779 | RCSNode *rcsfile; |
780 | { |
781 | char *version; |
782 | int retcode; |
783 | |
784 | if (numtag) |
785 | { |
786 | version = RCS_getversion (rcsfile, numtag, (char *) NULL((void*)0), 1, |
787 | (int *) NULL((void*)0)); |
788 | if (version == NULL((void*)0)) |
789 | return (0); |
790 | free (version); |
791 | } |
792 | |
793 | version = RCS_getversion (rcsfile, symtag, (char *) NULL((void*)0), 1, |
794 | (int *) NULL((void*)0)); |
795 | if (version == NULL((void*)0)) |
796 | return (0); |
797 | free (version); |
798 | |
799 | if ((retcode = RCS_deltag(rcsfile, symtag)) != 0) |
800 | { |
801 | if (!quiet) |
802 | error (0, retcode == -1 ? errno(*__errno()) : 0, |
803 | "failed to remove tag `%s' from `%s'", symtag, |
804 | rcsfile->path); |
805 | return (1); |
806 | } |
807 | RCS_rewrite (rcsfile, NULL((void*)0), NULL((void*)0)); |
808 | return (0); |
809 | } |
810 | |
811 | |
812 | /* |
813 | * Called to tag a particular file (the currently checked out version is |
814 | * tagged with the specified tag - or the specified tag is deleted). |
815 | */ |
816 | /* ARGSUSED */ |
817 | static int |
818 | tag_fileproc (callerdat, finfo) |
819 | void *callerdat; |
820 | struct file_info *finfo; |
821 | { |
822 | char *version, *oversion; |
823 | char *nversion = NULL((void*)0); |
824 | char *rev; |
825 | Vers_TS *vers; |
826 | int retcode = 0; |
827 | |
828 | vers = Version_TS (finfo, NULL((void*)0), NULL((void*)0), NULL((void*)0), 0, 0); |
829 | |
830 | if ((numtag != NULL((void*)0)) || (date != NULL((void*)0))) |
831 | { |
832 | nversion = RCS_getversion(vers->srcfile, |
833 | numtag, |
834 | date, |
835 | force_tag_match, |
836 | (int *) NULL((void*)0)); |
837 | if (nversion == NULL((void*)0)) |
838 | { |
839 | freevers_ts (&vers); |
840 | return (0); |
841 | } |
842 | } |
843 | if (delete_flag) |
844 | { |
845 | |
846 | /* |
847 | * If -d is specified, "force_tag_match" is set, so that this call to |
848 | * RCS_getversion() will return a NULL version string if the symbolic |
849 | * tag does not exist in the RCS file. |
850 | * |
851 | * This is done here because it's MUCH faster than just blindly calling |
852 | * "rcs" to remove the tag... trust me. |
853 | */ |
854 | |
855 | version = RCS_getversion (vers->srcfile, symtag, (char *) NULL((void*)0), 1, |
856 | (int *) NULL((void*)0)); |
857 | if (version == NULL((void*)0) || vers->srcfile == NULL((void*)0)) |
858 | { |
859 | freevers_ts (&vers); |
860 | return (0); |
861 | } |
862 | free (version); |
863 | |
864 | if ((retcode = RCS_deltag(vers->srcfile, symtag)) != 0) |
865 | { |
866 | if (!quiet) |
867 | error (0, retcode == -1 ? errno(*__errno()) : 0, |
868 | "failed to remove tag %s from %s", symtag, |
869 | vers->srcfile->path); |
870 | freevers_ts (&vers); |
871 | return (1); |
872 | } |
873 | RCS_rewrite (vers->srcfile, NULL((void*)0), NULL((void*)0)); |
874 | |
875 | /* warm fuzzies */ |
876 | if (!really_quiet) |
877 | { |
878 | cvs_output ("D ", 2); |
879 | cvs_output (finfo->fullname, 0); |
880 | cvs_output ("\n", 1); |
881 | } |
882 | |
883 | freevers_ts (&vers); |
884 | return (0); |
885 | } |
886 | |
887 | /* |
888 | * If we are adding a tag, we need to know which version we have checked |
889 | * out and we'll tag that version. |
890 | */ |
891 | if (nversion == NULL((void*)0)) |
892 | { |
893 | version = vers->vn_user; |
894 | } |
895 | else |
896 | { |
897 | version = nversion; |
898 | } |
899 | if (version == NULL((void*)0)) |
900 | { |
901 | freevers_ts (&vers); |
902 | return (0); |
903 | } |
904 | else if (strcmp (version, "0") == 0) |
905 | { |
906 | if (!quiet) |
907 | error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file); |
908 | freevers_ts (&vers); |
909 | return (0); |
910 | } |
911 | else if (version[0] == '-') |
912 | { |
913 | if (!quiet) |
914 | error (0, 0, "skipping removed but un-commited file `%s'", finfo->file); |
915 | freevers_ts (&vers); |
916 | return (0); |
917 | } |
918 | else if (vers->srcfile == NULL((void*)0)) |
919 | { |
920 | if (!quiet) |
921 | error (0, 0, "cannot find revision control file for `%s'", finfo->file); |
922 | freevers_ts (&vers); |
923 | return (0); |
924 | } |
925 | |
926 | /* |
927 | * As an enhancement for the case where a tag is being re-applied to a |
928 | * large number of files, make one extra call to RCS_getversion to see |
929 | * if the tag is already set in the RCS file. If so, check to see if it |
930 | * needs to be moved. If not, do nothing. This will likely save a lot of |
931 | * time when simply moving the tag to the "current" head revisions of a |
932 | * module -- which I have found to be a typical tagging operation. |
933 | */ |
934 | rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version; |
935 | oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL((void*)0), 1, |
936 | (int *) NULL((void*)0)); |
937 | if (oversion != NULL((void*)0)) |
938 | { |
939 | int isbranch = RCS_nodeisbranch (finfo->rcs, symtag); |
940 | |
941 | /* |
942 | * if versions the same and neither old or new are branches don't have |
943 | * to do anything |
944 | */ |
945 | if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch) |
946 | { |
947 | free (oversion); |
948 | if (branch_mode) |
949 | free (rev); |
950 | freevers_ts (&vers); |
951 | return (0); |
952 | } |
953 | |
954 | if (!force_tag_move) |
955 | { |
956 | /* we're NOT going to move the tag */ |
957 | cvs_output ("W ", 2); |
958 | cvs_output (finfo->fullname, 0); |
959 | cvs_output (" : ", 0); |
960 | cvs_output (symtag, 0); |
961 | cvs_output (" already exists on ", 0); |
962 | cvs_output (isbranch ? "branch" : "version", 0); |
963 | cvs_output (" ", 0); |
964 | cvs_output (oversion, 0); |
965 | cvs_output (" : NOT MOVING tag to ", 0); |
966 | cvs_output (branch_mode ? "branch" : "version", 0); |
967 | cvs_output (" ", 0); |
968 | cvs_output (rev, 0); |
969 | cvs_output ("\n", 1); |
970 | free (oversion); |
971 | if (branch_mode) |
972 | free (rev); |
973 | freevers_ts (&vers); |
974 | return (0); |
975 | } |
976 | free (oversion); |
977 | } |
978 | |
979 | if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0) |
980 | { |
981 | error (1, retcode == -1 ? errno(*__errno()) : 0, |
982 | "failed to set tag %s to revision %s in %s", |
983 | symtag, rev, vers->srcfile->path); |
984 | if (branch_mode) |
985 | free (rev); |
986 | freevers_ts (&vers); |
987 | return (1); |
988 | } |
989 | if (branch_mode) |
990 | free (rev); |
991 | RCS_rewrite (vers->srcfile, NULL((void*)0), NULL((void*)0)); |
992 | |
993 | /* more warm fuzzies */ |
994 | if (!really_quiet) |
995 | { |
996 | cvs_output ("T ", 2); |
997 | cvs_output (finfo->fullname, 0); |
998 | cvs_output ("\n", 1); |
999 | } |
1000 | |
1001 | if (nversion != NULL((void*)0)) |
1002 | { |
1003 | free (nversion); |
1004 | } |
1005 | freevers_ts (&vers); |
1006 | return (0); |
1007 | } |
1008 | |
1009 | /* |
1010 | * Print a warm fuzzy message |
1011 | */ |
1012 | /* ARGSUSED */ |
1013 | static Dtype |
1014 | tag_dirproc (callerdat, dir, repos, update_dir, entries) |
1015 | void *callerdat; |
1016 | char *dir; |
1017 | char *repos; |
1018 | char *update_dir; |
1019 | List *entries; |
1020 | { |
1021 | |
1022 | if (ignore_directory (update_dir)) |
1023 | { |
1024 | /* print the warm fuzzy message */ |
1025 | if (!quiet) |
1026 | error (0, 0, "Ignoring %s", update_dir); |
1027 | return R_SKIP_ALL; |
1028 | } |
1029 | |
1030 | if (!quiet) |
1031 | error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir); |
1032 | return (R_PROCESS); |
1033 | } |
1034 | |
1035 | /* Code relating to the val-tags file. Note that this file has no way |
1036 | of knowing when a tag has been deleted. The problem is that there |
1037 | is no way of knowing whether a tag still exists somewhere, when we |
1038 | delete it some places. Using per-directory val-tags files (in |
1039 | CVSREP) might be better, but that might slow down the process of |
1040 | verifying that a tag is correct (maybe not, for the likely cases, |
1041 | if carefully done), and/or be harder to implement correctly. */ |
1042 | |
1043 | struct val_args { |
1044 | char *name; |
1045 | int found; |
1046 | }; |
1047 | |
1048 | static int val_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo); |
1049 | |
1050 | static int |
1051 | val_fileproc (callerdat, finfo) |
1052 | void *callerdat; |
1053 | struct file_info *finfo; |
1054 | { |
1055 | RCSNode *rcsdata; |
1056 | struct val_args *args = (struct val_args *)callerdat; |
1057 | char *tag; |
1058 | |
1059 | if ((rcsdata = finfo->rcs) == NULL((void*)0)) |
1060 | /* Not sure this can happen, after all we passed only |
1061 | W_REPOS | W_ATTIC. */ |
1062 | return 0; |
1063 | |
1064 | tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL((void*)0)); |
1065 | if (tag != NULL((void*)0)) |
1066 | { |
1067 | /* FIXME: should find out a way to stop the search at this point. */ |
1068 | args->found = 1; |
1069 | free (tag); |
1070 | } |
1071 | return 0; |
1072 | } |
1073 | |
1074 | static Dtype val_direntproc PROTO ((void *, char *, char *, char *, List *))(void *, char *, char *, char *, List *); |
1075 | |
1076 | static Dtype |
1077 | val_direntproc (callerdat, dir, repository, update_dir, entries) |
1078 | void *callerdat; |
1079 | char *dir; |
1080 | char *repository; |
1081 | char *update_dir; |
1082 | List *entries; |
1083 | { |
1084 | /* This is not quite right--it doesn't get right the case of "cvs |
1085 | update -d -r foobar" where foobar is a tag which exists only in |
1086 | files in a directory which does not exist yet, but which is |
1087 | about to be created. */ |
1088 | if (isdir (dir)) |
1089 | return R_PROCESS; |
1090 | return R_SKIP_ALL; |
1091 | } |
1092 | |
1093 | /* Check to see whether NAME is a valid tag. If so, return. If not |
1094 | print an error message and exit. ARGC, ARGV, LOCAL, and AFLAG specify |
1095 | which files we will be operating on. |
1096 | |
1097 | REPOSITORY is the repository if we need to cd into it, or NULL if |
1098 | we are already there, or "" if we should do a W_LOCAL recursion. |
1099 | Sorry for three cases, but the "" case is needed in case the |
1100 | working directories come from diverse parts of the repository, the |
1101 | NULL case avoids an unneccesary chdir, and the non-NULL, non-"" |
1102 | case is needed for checkout, where we don't want to chdir if the |
1103 | tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any |
1104 | local directory. */ |
1105 | void |
1106 | tag_check_valid (name, argc, argv, local, aflag, repository) |
1107 | char *name; |
1108 | int argc; |
1109 | char **argv; |
1110 | int local; |
1111 | int aflag; |
1112 | char *repository; |
1113 | { |
1114 | DBM *db; |
1115 | char *valtags_filename; |
1116 | int err; |
1117 | int nowrite = 0; |
1118 | datum mytag; |
1119 | struct val_args the_val_args; |
1120 | struct saved_cwd cwd; |
1121 | int which; |
1122 | |
1123 | /* Numeric tags require only a syntactic check. */ |
1124 | if (isdigit ((unsigned char) name[0])) |
1125 | { |
1126 | char *p; |
1127 | for (p = name; *p != '\0'; ++p) |
1128 | { |
1129 | if (!(isdigit ((unsigned char) *p) || *p == '.')) |
1130 | error (1, 0, "\ |
1131 | Numeric tag %s contains characters other than digits and '.'", name); |
1132 | } |
1133 | return; |
1134 | } |
1135 | |
1136 | /* Special tags are always valid. */ |
1137 | if (strcmp (name, TAG_BASE"BASE") == 0 |
1138 | || strcmp (name, TAG_HEAD"HEAD") == 0) |
1139 | return; |
1140 | |
1141 | /* FIXME: This routine doesn't seem to do any locking whatsoever |
1142 | (and it is called from places which don't have locks in place). |
1143 | If two processes try to write val-tags at the same time, it would |
1144 | seem like we are in trouble. */ |
1145 | |
1146 | mytag.dptr = name; |
1147 | mytag.dsize = strlen (name); |
1148 | |
1149 | valtags_filename = xmalloc (strlen (current_parsed_root->directory) |
1150 | + sizeof CVSROOTADM"CVSROOT" |
1151 | + sizeof CVSROOTADM_VALTAGS"val-tags" + 3); |
1152 | sprintf (valtags_filename, "%s/%s/%s", current_parsed_root->directory, |
1153 | CVSROOTADM"CVSROOT", CVSROOTADM_VALTAGS"val-tags"); |
1154 | db = dbm_openmydbm_open (valtags_filename, O_RDWR0x0002, 0666); |
1155 | if (db == NULL((void*)0)) |
1156 | { |
1157 | if (!existence_error (errno)(((*__errno())) == 2)) |
1158 | { |
1159 | error (0, errno(*__errno()), "warning: cannot open %s read/write", |
1160 | valtags_filename); |
1161 | db = dbm_openmydbm_open (valtags_filename, O_RDONLY0x0000, 0666); |
1162 | if (db != NULL((void*)0)) |
1163 | nowrite = 1; |
1164 | else if (!existence_error (errno)(((*__errno())) == 2)) |
1165 | error (1, errno(*__errno()), "cannot read %s", valtags_filename); |
1166 | } |
1167 | /* If the file merely fails to exist, we just keep going and create |
1168 | it later if need be. */ |
1169 | } |
1170 | if (db != NULL((void*)0)) |
1171 | { |
1172 | datum val; |
1173 | |
1174 | val = dbm_fetchmydbm_fetch (db, mytag); |
1175 | if (val.dptr != NULL((void*)0)) |
1176 | { |
1177 | /* Found. The tag is valid. */ |
1178 | dbm_closemydbm_close (db); |
1179 | free (valtags_filename); |
1180 | return; |
1181 | } |
1182 | /* FIXME: should check errors somehow (add dbm_error to myndbm.c?). */ |
1183 | } |
1184 | |
1185 | /* We didn't find the tag in val-tags, so look through all the RCS files |
1186 | to see whether it exists there. Yes, this is expensive, but there |
1187 | is no other way to cope with a tag which might have been created |
1188 | by an old version of CVS, from before val-tags was invented. |
1189 | |
1190 | Since we need this code anyway, we also use it to create |
1191 | entries in val-tags in general (that is, the val-tags entry |
1192 | will get created the first time the tag is used, not when the |
1193 | tag is created). */ |
1194 | |
1195 | the_val_args.name = name; |
1196 | the_val_args.found = 0; |
1197 | |
1198 | which = W_REPOS0x02 | W_ATTIC0x04; |
1199 | |
1200 | if (repository != NULL((void*)0)) |
1201 | { |
1202 | if (repository[0] == '\0') |
1203 | which |= W_LOCAL0x01; |
1204 | else |
1205 | { |
1206 | if (save_cwd (&cwd)) |
1207 | error_exit (); |
1208 | if ( CVS_CHDIRchdir (repository) < 0) |
1209 | error (1, errno(*__errno()), "cannot change to %s directory", repository); |
1210 | } |
1211 | } |
1212 | |
1213 | err = start_recursion (val_fileproc, (FILESDONEPROC) NULL((void*)0), |
Value stored to 'err' is never read | |
1214 | val_direntproc, (DIRLEAVEPROC) NULL((void*)0), |
1215 | (void *)&the_val_args, |
1216 | argc, argv, local, which, aflag, |
1217 | 1, NULL((void*)0), 1); |
1218 | if (repository != NULL((void*)0) && repository[0] != '\0') |
1219 | { |
1220 | if (restore_cwd (&cwd, NULL((void*)0))) |
1221 | exit (EXIT_FAILURE1); |
1222 | free_cwd (&cwd); |
1223 | } |
1224 | |
1225 | if (!the_val_args.found) |
1226 | error (1, 0, "no such tag %s", name); |
1227 | else |
1228 | { |
1229 | /* The tags is valid but not mentioned in val-tags. Add it. */ |
1230 | datum value; |
1231 | |
1232 | if (noexec || nowrite) |
1233 | { |
1234 | if (db != NULL((void*)0)) |
1235 | dbm_closemydbm_close (db); |
1236 | free (valtags_filename); |
1237 | return; |
1238 | } |
1239 | |
1240 | if (db == NULL((void*)0)) |
1241 | { |
1242 | mode_t omask; |
1243 | omask = umask (cvsumask); |
1244 | db = dbm_openmydbm_open (valtags_filename, O_RDWR0x0002 | O_CREAT0x0200 | O_TRUNC0x0400, 0666); |
1245 | (void) umask (omask); |
1246 | |
1247 | if (db == NULL((void*)0)) |
1248 | { |
1249 | error (0, errno(*__errno()), "warning: cannot create %s", valtags_filename); |
1250 | free (valtags_filename); |
1251 | return; |
1252 | } |
1253 | } |
1254 | value.dptr = "y"; |
1255 | value.dsize = 1; |
1256 | if (dbm_storemydbm_store (db, mytag, value, DBM_REPLACE1) < 0) |
1257 | error (0, errno(*__errno()), "cannot store %s into %s", name, |
1258 | valtags_filename); |
1259 | dbm_closemydbm_close (db); |
1260 | } |
1261 | free (valtags_filename); |
1262 | } |
1263 | |
1264 | /* |
1265 | * Check whether a join tag is valid. This is just like |
1266 | * tag_check_valid, but we must stop before the colon if there is one. |
1267 | */ |
1268 | |
1269 | void |
1270 | tag_check_valid_join (join_tag, argc, argv, local, aflag, repository) |
1271 | char *join_tag; |
1272 | int argc; |
1273 | char **argv; |
1274 | int local; |
1275 | int aflag; |
1276 | char *repository; |
1277 | { |
1278 | char *c, *s; |
1279 | |
1280 | c = xstrdup (join_tag); |
1281 | s = strchr (c, ':'); |
1282 | if (s != NULL((void*)0)) |
1283 | { |
1284 | if (isdigit ((unsigned char) join_tag[0])) |
1285 | error (1, 0, |
1286 | "Numeric join tag %s may not contain a date specifier", |
1287 | join_tag); |
1288 | |
1289 | *s = '\0'; |
1290 | /* hmmm... I think it makes sense to allow -j:<date>, but |
1291 | * for now this fixes a bug where CVS just spins and spins (I |
1292 | * think in the RCS code) looking for a zero length tag. |
1293 | */ |
1294 | if (!*c) |
1295 | error (1, 0, |
1296 | "argument to join may not contain a date specifier without a tag"); |
1297 | } |
1298 | |
1299 | tag_check_valid (c, argc, argv, local, aflag, repository); |
1300 | |
1301 | free (c); |
1302 | } |