diff options
Diffstat (limited to 'contrib/cvs/src/tag.c')
-rw-r--r-- | contrib/cvs/src/tag.c | 557 |
1 files changed, 459 insertions, 98 deletions
diff --git a/contrib/cvs/src/tag.c b/contrib/cvs/src/tag.c index a9d8534..a2883c8 100644 --- a/contrib/cvs/src/tag.c +++ b/contrib/cvs/src/tag.c @@ -5,15 +5,19 @@ * You may distribute under the terms of the GNU General Public License as * specified in the README file that comes with the CVS source distribution. * - * Tag + * Tag and Rtag * * Add or delete a symbolic name to an RCS file, or a collection of RCS files. - * Uses the checked out revision in the current directory. + * Tag uses the checked out revision in the current directory, rtag uses + * the modules database, if necessary. */ #include "cvs.h" #include "savecwd.h" +static int rtag_proc PROTO((int argc, char **argv, char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg)); static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static int check_filesdoneproc PROTO ((void *callerdat, int err, char *repos, char *update_dir, @@ -26,20 +30,22 @@ static int pretag_list_proc PROTO((Node *p, void *closure)); static Dtype tag_dirproc PROTO ((void *callerdat, char *dir, char *repos, char *update_dir, List *entries)); +static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo)); +static int rtag_delete PROTO((RCSNode *rcsfile)); static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo)); -static int tag_filesdoneproc PROTO ((void *callerdat, int err, - char *repos, char *update_dir, - List *entries)); -static char *numtag; +static char *numtag; /* specific revision to tag */ +static int numtag_validated = 0; static char *date = NULL; -static char *symtag; +static char *symtag; /* tag to add or delete */ static int delete_flag; /* adding a tag by default */ static int branch_mode; /* make an automagic "branch" tag */ static int local; /* recursive by default */ -static int force_tag_match = 1; /* force tag to match by default */ +static int force_tag_match = 1; /* force tag to match by default */ static int force_tag_move; /* don't force tag to move by default */ static int check_uptodate; /* no uptodate-check by default */ +static int attic_too; /* remove tag from Attic files */ +static int is_rtag; struct tag_info { @@ -57,18 +63,37 @@ struct master_lists static List *mtlist; static List *tlist; -static const char *const tag_usage[] = +static const char rtag_opts[] = "+abdFflnQqRr:D:"; +static const char *const rtag_usage[] = { - "Usage: %s %s [-lRF] [-b] [-d] [-c] [-r rev|-D date] tag [files...]\n", + "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n", + "\t-a\tClear tag from removed files that would not otherwise be tagged.\n", + "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", + "\t-d\tDelete the given tag.\n", + "\t-F\tMove tag if it already exists.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", "\t-l\tLocal directory only, not recursive.\n", + "\t-n\tNo execution of 'tag program'.\n", "\t-R\tProcess directories recursively.\n", - "\t-d\tDelete the given tag.\n", "\t-r rev\tExisting revision/tag.\n", "\t-D\tExisting date.\n", - "\t-f\tForce a head revision if specified tag not found.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +static const char tag_opts[] = "+bcdFflQqRr:D:"; +static const char *const tag_usage[] = +{ + "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n", "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", - "\t-F\tMove tag if it already exists.\n", "\t-c\tCheck that working files are unmodified.\n", + "\t-d\tDelete the given tag.\n", + "\t-F\tMove tag if it already exists.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive.\n", + "\t-R\tProcess directories recursively.\n", + "\t-r rev\tExisting revision/tag.\n", + "\t-D\tExisting date.\n", "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -80,15 +105,42 @@ cvstag (argc, argv) { int c; int err = 0; + int run_module_prog = 1; + is_rtag = (strcmp (command_name, "rtag") == 0); + if (argc == -1) - usage (tag_usage); + usage (is_rtag ? rtag_usage : tag_usage); optind = 0; - while ((c = getopt (argc, argv, "+FQqlRcdr:D:bf")) != -1) + while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1) { switch (c) { + case 'a': + attic_too = 1; + break; + case 'b': + branch_mode = 1; + break; + case 'c': + check_uptodate = 1; + break; + case 'd': + delete_flag = 1; + break; + case 'F': + force_tag_move = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'l': + local = 1; + break; + case 'n': + run_module_prog = 0; + break; case 'Q': case 'q': #ifdef SERVER_SUPPORT @@ -100,18 +152,9 @@ cvstag (argc, argv) "-q or -Q must be specified before \"%s\"", command_name); break; - case 'l': - local = 1; - break; case 'R': local = 0; break; - case 'd': - delete_flag = 1; - break; - case 'c': - check_uptodate = 1; - break; case 'r': numtag = optarg; break; @@ -120,26 +163,17 @@ cvstag (argc, argv) free (date); date = Make_Date (optarg); break; - case 'f': - force_tag_match = 0; - break; - case 'b': - branch_mode = 1; - break; - case 'F': - force_tag_move = 1; - break; case '?': default: - usage (tag_usage); + usage (is_rtag ? rtag_usage : tag_usage); break; } } argc -= optind; argv += optind; - if (argc == 0) - usage (tag_usage); + if (argc < (is_rtag ? 2 : 1)) + usage (is_rtag ? rtag_usage : tag_usage); symtag = argv[0]; argc--; argv++; @@ -151,25 +185,29 @@ cvstag (argc, argv) RCS_check_tag (symtag); #ifdef CLIENT_SUPPORT - if (client_active) + if (current_parsed_root->isremote) { /* We're the client side. Fire up the remote server. */ start_server (); ign_setup (); - if (!force_tag_match) - send_arg ("-f"); - if (local) - send_arg("-l"); - if (delete_flag) - send_arg("-d"); - if (check_uptodate) - send_arg("-c"); + if (attic_too) + send_arg("-a"); if (branch_mode) send_arg("-b"); + if (check_uptodate) + send_arg("-c"); + if (delete_flag) + send_arg("-d"); if (force_tag_move) send_arg("-F"); + if (!force_tag_match) + send_arg ("-f"); + if (local) + send_arg("-l"); + if (!run_module_prog) + send_arg("-n"); if (numtag) option_with_arg ("-r", numtag); @@ -178,20 +216,155 @@ cvstag (argc, argv) send_arg (symtag); - send_files (argc, argv, local, 0, + if (is_rtag) + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + send_to_server ("rtag\012", 0); + } + else + { + + send_files (argc, argv, local, 0, /* I think the -c case is like "cvs status", in which we really better be correct rather than being fast; it is just too confusing otherwise. */ - check_uptodate ? 0 : SEND_NO_CONTENTS); - send_file_names (argc, argv, SEND_EXPAND_WILD); - send_to_server ("tag\012", 0); + check_uptodate ? 0 : SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server ("tag\012", 0); + } + return get_responses_and_close (); } #endif - if (numtag != NULL) - tag_check_valid (numtag, argc, argv, local, 0, ""); + if (is_rtag) + { + DBM *db; + int i; + db = open_module (); + for (i = 0; i < argc; i++) + { + /* XXX last arg should be repository, but doesn't make sense here */ + history_write ('T', (delete_flag ? "D" : (numtag ? numtag : + (date ? date : "A"))), symtag, argv[i], ""); + err += do_module (db, argv[i], TAG, + delete_flag ? "Untagging" : "Tagging", + rtag_proc, (char *) NULL, 0, 0, run_module_prog, + 0, symtag); + } + close_module (db); + } + else + { + err = rtag_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, 0, NULL, + NULL); + } + + return (err); +} + +/* + * callback proc for doing the real work of tagging + */ +/* ARGSUSED */ +static int +rtag_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int argc; + char **argv; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + /* Begin section which is identical to patch_proc--should this + be abstracted out somehow? */ + char *myargv[2]; + int err = 0; + int which; + char *repository; + char *where; + + if (is_rtag) + { + repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2); + (void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]); + where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1) + + 1); + (void) strcpy (where, argv[0]); + + /* if mfile isn't null, we need to set up to do only part of the module */ + if (mfile != NULL) + { + char *cp; + char *path; + + /* if the portion of the module is a path, put the dir part on repos */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = '\0'; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + path = xmalloc (strlen (repository) + strlen (mfile) + 5); + (void) sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void) strcpy (repository, path); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + } + else + { + myargv[0] = argv[0]; + myargv[1] = mfile; + argc = 2; + argv = myargv; + } + free (path); + } + + /* cd to the starting repository */ + if ( CVS_CHDIR (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + free (repository); + return (1); + } + free (repository); + /* End section which is identical to patch_proc. */ + + if (delete_flag || attic_too || (force_tag_match && numtag)) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + repository = NULL; + } + else + { + where = NULL; + which = W_LOCAL; + repository = ""; + } + + if (numtag != NULL && !numtag_validated) + { + tag_check_valid (numtag, argc - 1, argv + 1, local, 0, repository); + numtag_validated = 1; + } /* check to make sure they are authorized to tag all the specified files in the repository */ @@ -199,19 +372,32 @@ cvstag (argc, argv) mtlist = getlist(); err = start_recursion (check_fileproc, check_filesdoneproc, (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, - argc, argv, local, W_LOCAL, 0, 1, - (char *) NULL, 1); + argc - 1, argv + 1, local, which, 0, 1, + where, 1); if (err) { error (1, 0, "correct the above errors first!"); } + /* It would be nice to provide consistency with respect to + commits; however CVS lacks the infrastructure to do that (see + Concurrency in cvs.texinfo and comment in do_recursion). We + do need to ensure that the RCS file info that gets read and + cached in do_recursion isn't stale by the time we get around + to using it to rewrite the RCS file in the callback, and this + is the easiest way to accomplish that. */ + lock_tree_for_write (argc - 1, argv + 1, local, which, 0); + /* start the recursion processor */ - err = start_recursion (tag_fileproc, tag_filesdoneproc, tag_dirproc, - (DIRLEAVEPROC) NULL, NULL, argc, argv, local, - W_LOCAL, 0, 0, (char *) NULL, 1); - dellist(&mtlist); + err = start_recursion (is_rtag ? rtag_fileproc : tag_fileproc, + (FILESDONEPROC) NULL, tag_dirproc, + (DIRLEAVEPROC) NULL, NULL, argc - 1, argv + 1, + local, which, 0, 0, where, 1); + Lock_Cleanup (); + dellist (&mtlist); + if (where != NULL) + free (where); return (err); } @@ -231,7 +417,8 @@ check_fileproc (callerdat, finfo) { Ctype status = Classify_File (finfo, (char *) NULL, (char *) NULL, (char *) NULL, 1, 0, &vers, 0); - if ((status != T_UPTODATE) && (status != T_CHECKOUT)) + if ((status != T_UPTODATE) && (status != T_CHECKOUT) && + (status != T_PATCH)) { error (0, 0, "%s is locally modified", finfo->fullname); freevers_ts (&vers); @@ -282,7 +469,7 @@ check_fileproc (callerdat, finfo) version we are going to tag. There probably are some subtle races (e.g. numtag is "foo" which gets moved between here and tag_fileproc). */ - if (numtag == NULL && date == NULL) + if (!is_rtag && numtag == NULL && date == NULL) p->data = xstrdup (vers->vn_user); else p->data = RCS_getversion (vers->srcfile, numtag, date, @@ -327,7 +514,7 @@ check_fileproc (callerdat, finfo) p->data = NULL; } } - freevers_ts(&vers); + freevers_ts (&vers); (void) addnode (tlist, p); return (0); } @@ -437,6 +624,192 @@ pretag_list_proc(p, closure) /* + * Called to rtag a particular file, as appropriate with the options that were + * set above. + */ +/* ARGSUSED */ +static int +rtag_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; +{ + RCSNode *rcsfile; + char *version, *rev; + int retcode = 0; + + /* find the parsed RCS data */ + if ((rcsfile = finfo->rcs) == NULL) + return (1); + + /* + * For tagging an RCS file which is a symbolic link, you'd best be + * running with RCS 5.6, since it knows how to handle symbolic links + * correctly without breaking your link! + */ + + if (delete_flag) + return (rtag_delete (rcsfile)); + + /* + * If we get here, we are adding a tag. But, if -a was specified, we + * need to check to see if a -r or -D option was specified. If neither + * was specified and the file is in the Attic, remove the tag. + */ + if (attic_too && (!numtag && !date)) + { + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + return (rtag_delete (rcsfile)); + } + + version = RCS_getversion (rcsfile, numtag, date, force_tag_match, + (int *) NULL); + if (version == NULL) + { + /* If -a specified, clean up any old tags */ + if (attic_too) + (void) rtag_delete (rcsfile); + + if (!quiet && !force_tag_match) + { + error (0, 0, "cannot find tag `%s' in `%s'", + numtag ? numtag : "head", rcsfile->path); + return (1); + } + return (0); + } + if (numtag + && isdigit ((unsigned char) *numtag) + && strcmp (numtag, version) != 0) + { + + /* + * We didn't find a match for the numeric tag that was specified, but + * that's OK. just pass the numeric tag on to rcs, to be tagged as + * specified. Could get here if one tried to tag "1.1.1" and there + * was a 1.1.1 branch with some head revision. In this case, we want + * the tag to reference "1.1.1" and not the revision at the head of + * the branch. Use a symbolic tag for that. + */ + rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag; + retcode = RCS_settag(rcsfile, symtag, numtag); + if (retcode == 0) + RCS_rewrite (rcsfile, NULL, NULL); + } + else + { + char *oversion; + + /* + * As an enhancement for the case where a tag is being re-applied to + * a large body of a module, make one extra call to RCS_getversion to + * see if the tag is already set in the RCS file. If so, check to + * see if it needs to be moved. If not, do nothing. This will + * likely save a lot of time when simply moving the tag to the + * "current" head revisions of a module -- which I have found to be a + * typical tagging operation. + */ + rev = branch_mode ? RCS_magicrev (rcsfile, version) : version; + oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, + (int *) NULL); + if (oversion != NULL) + { + int isbranch = RCS_nodeisbranch (finfo->rcs, symtag); + + /* + * if versions the same and neither old or new are branches don't + * have to do anything + */ + if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch) + { + free (oversion); + free (version); + return (0); + } + + if (!force_tag_move) + { + /* we're NOT going to move the tag */ + (void) printf ("W %s", finfo->fullname); + + (void) printf (" : %s already exists on %s %s", + symtag, isbranch ? "branch" : "version", + oversion); + (void) printf (" : NOT MOVING tag to %s %s\n", + branch_mode ? "branch" : "version", rev); + free (oversion); + free (version); + return (0); + } + free (oversion); + } + retcode = RCS_settag(rcsfile, symtag, rev); + if (retcode == 0) + RCS_rewrite (rcsfile, NULL, NULL); + } + + if (retcode != 0) + { + error (1, retcode == -1 ? errno : 0, + "failed to set tag `%s' to revision `%s' in `%s'", + symtag, rev, rcsfile->path); + if (branch_mode) + free (rev); + free (version); + return (1); + } + if (branch_mode) + free (rev); + free (version); + return (0); +} + +/* + * If -d is specified, "force_tag_match" is set, so that this call to + * RCS_getversion() will return a NULL version string if the symbolic + * tag does not exist in the RCS file. + * + * If the -r flag was used, numtag is set, and we only delete the + * symtag from files that have numtag. + * + * This is done here because it's MUCH faster than just blindly calling + * "rcs" to remove the tag... trust me. + */ +static int +rtag_delete (rcsfile) + RCSNode *rcsfile; +{ + char *version; + int retcode; + + if (numtag) + { + version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1, + (int *) NULL); + if (version == NULL) + return (0); + free (version); + } + + version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, + (int *) NULL); + if (version == NULL) + return (0); + free (version); + + if ((retcode = RCS_deltag(rcsfile, symtag)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag `%s' from `%s'", symtag, + rcsfile->path); + return (1); + } + RCS_rewrite (rcsfile, NULL, NULL); + return (0); +} + + +/* * Called to tag a particular file (the currently checked out version is * tagged with the specified tag - or the specified tag is deleted). */ @@ -452,17 +825,6 @@ tag_fileproc (callerdat, finfo) Vers_TS *vers; int retcode = 0; - /* Lock the directory if it is not already locked. We can't rely - on tag_dirproc because it won't handle the case where the user - specifies a list of files on the command line. */ - /* We do not need to acquire a full write lock for the tag operation: - the revisions are obtained from the working directory, so we do not - require consistency across the entire repository. However, we do - need to prevent simultaneous tag operations from interfering with - each other. Therefore, we write lock each directory as we enter - it, and unlock it as we leave it. */ - lock_dir_for_write (finfo->repository); - vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0); if ((numtag != NULL) || (date != NULL)) @@ -644,21 +1006,6 @@ tag_fileproc (callerdat, finfo) return (0); } -/* Clear any lock we may hold on the current directory. */ - -static int -tag_filesdoneproc (callerdat, err, repos, update_dir, entries) - void *callerdat; - int err; - char *repos; - char *update_dir; - List *entries; -{ - Lock_Cleanup (); - - return (err); -} - /* * Print a warm fuzzy message */ @@ -671,6 +1018,15 @@ tag_dirproc (callerdat, dir, repos, update_dir, entries) char *update_dir; List *entries; { + + if (ignore_directory (update_dir)) + { + /* print the warm fuzzy message */ + if (!quiet) + error (0, 0, "Ignoring %s", update_dir); + return R_SKIP_ALL; + } + if (!quiet) error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir); return (R_PROCESS); @@ -758,6 +1114,7 @@ tag_check_valid (name, argc, argv, local, aflag, repository) DBM *db; char *valtags_filename; int err; + int nowrite = 0; datum mytag; struct val_args the_val_args; struct saved_cwd cwd; @@ -789,24 +1146,28 @@ Numeric tag %s contains characters other than digits and '.'", name); mytag.dptr = name; mytag.dsize = strlen (name); - valtags_filename = xmalloc (strlen (CVSroot_directory) + valtags_filename = xmalloc (strlen (current_parsed_root->directory) + sizeof CVSROOTADM - + sizeof CVSROOTADM_VALTAGS + 20); - strcpy (valtags_filename, CVSroot_directory); - strcat (valtags_filename, "/"); - strcat (valtags_filename, CVSROOTADM); - strcat (valtags_filename, "/"); - strcat (valtags_filename, CVSROOTADM_VALTAGS); + + sizeof CVSROOTADM_VALTAGS + 3); + sprintf (valtags_filename, "%s/%s/%s", current_parsed_root->directory, + CVSROOTADM, CVSROOTADM_VALTAGS); db = dbm_open (valtags_filename, O_RDWR, 0666); if (db == NULL) { if (!existence_error (errno)) - error (1, errno, "cannot read %s", valtags_filename); - + { + error (0, errno, "warning: cannot open %s read/write", + valtags_filename); + db = dbm_open (valtags_filename, O_RDONLY, 0666); + if (db != NULL) + nowrite = 1; + else if (!existence_error (errno)) + error (1, errno, "cannot read %s", valtags_filename); + } /* If the file merely fails to exist, we just keep going and create it later if need be. */ } - else + if (db != NULL) { datum val; @@ -868,7 +1229,7 @@ Numeric tag %s contains characters other than digits and '.'", name); /* The tags is valid but not mentioned in val-tags. Add it. */ datum value; - if (noexec) + if (noexec || nowrite) { if (db != NULL) dbm_close (db); @@ -885,7 +1246,7 @@ Numeric tag %s contains characters other than digits and '.'", name); if (db == NULL) { - error (0, errno, "cannot create %s", valtags_filename); + error (0, errno, "warning: cannot create %s", valtags_filename); free (valtags_filename); return; } |