diff options
author | peter <peter@FreeBSD.org> | 1997-05-15 22:52:26 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1997-05-15 22:52:26 +0000 |
commit | 6ff5202f154ca19bec5c77bfedbd9febafda6b23 (patch) | |
tree | 0c3be018c4b60cf53793c31cbb6e361daca83738 /contrib/cvs/src | |
parent | e99541e5084ccab931752735585261dc4727e69f (diff) | |
download | FreeBSD-src-6ff5202f154ca19bec5c77bfedbd9febafda6b23.zip FreeBSD-src-6ff5202f154ca19bec5c77bfedbd9febafda6b23.tar.gz |
Merge import conflicts
Diffstat (limited to 'contrib/cvs/src')
-rw-r--r-- | contrib/cvs/src/diff.c | 622 | ||||
-rw-r--r-- | contrib/cvs/src/main.c | 1000 | ||||
-rw-r--r-- | contrib/cvs/src/mkmodules.c | 233 | ||||
-rw-r--r-- | contrib/cvs/src/rcs.c | 2798 |
4 files changed, 3553 insertions, 1100 deletions
diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c index 7520cec..8253a2f 100644 --- a/contrib/cvs/src/diff.c +++ b/contrib/cvs/src/diff.c @@ -16,46 +16,159 @@ #include "cvs.h" -static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir)); -static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir)); -static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir)); -static int diff_file_nodiff PROTO((char *file, char *repository, List *entries, - RCSNode *rcs, Vers_TS *vers)); -static int diff_fileproc PROTO((struct file_info *finfo)); +enum diff_file +{ + DIFF_ERROR, + DIFF_ADDED, + DIFF_REMOVED, + DIFF_DIFFERENT, + DIFF_SAME +}; + +static Dtype diff_dirproc PROTO ((void *callerdat, char *dir, + char *pos_repos, char *update_dir, + List *entries)); +static int diff_filesdoneproc PROTO ((void *callerdat, int err, + char *repos, char *update_dir, + List *entries)); +static int diff_dirleaveproc PROTO ((void *callerdat, char *dir, + int err, char *update_dir, + List *entries)); +static enum diff_file diff_file_nodiff PROTO ((struct file_info *finfo, + Vers_TS *vers, + enum diff_file)); +static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static void diff_mark_errors PROTO((int err)); static char *diff_rev1, *diff_rev2; static char *diff_date1, *diff_date2; static char *use_rev1, *use_rev2; -#ifdef SERVER_SUPPORT /* Revision of the user file, if it is unchanged from something in the repository and we want to use that fact. */ static char *user_file_rev; -#endif static char *options; -static char opts[PATH_MAX]; +static char *opts; +static size_t opts_allocated = 1; static int diff_errors; static int empty_files = 0; +/* FIXME: should be documenting all the options here. They don't + perfectly match rcsdiff options (for example, we always support + --ifdef and --context, but rcsdiff only does if diff does). */ static const char *const diff_usage[] = { - "Usage: %s %s [-lN] [rcsdiff-options]\n", -#ifdef CVS_DIFFDATE + "Usage: %s %s [-lNR] [rcsdiff-options]\n", " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n", -#else - " [-r rev1 [-r rev2]] [files...] \n", -#endif "\t-l\tLocal directory only, not recursive\n", + "\t-R\tProcess directories recursively.\n", "\t-D d1\tDiff revision for date against working file.\n", "\t-D d2\tDiff rev1/date1 against date2.\n", "\t-N\tinclude diffs for added and removed files.\n", "\t-r rev1\tDiff revision for rev1 against working file.\n", "\t-r rev2\tDiff rev1/date1 against rev2.\n", + "\t--ifdef=arg\tOutput diffs in ifdef format.\n", + "(consult the documentation for your diff program for rcsdiff-options.\n", + "The most popular is -c for context diffs but there are many more).\n", NULL }; +/* I copied this array directly out of diff.c in diffutils 2.7, after + removing the following entries, none of which seem relevant to use + with CVS: + --help + --version + --recursive + --unidirectional-new-file + --starting-file + --exclude + --exclude-from + --sdiff-merge-assist + + I changed the options which take optional arguments (--context and + --unified) to return a number rather than a letter, so that the + optional argument could be handled more easily. I changed the + --paginate and --brief options to return a number, since -l and -q + mean something else to cvs diff. + + The numbers 129- that appear in the fourth element of some entries + tell the big switch in `diff' how to process those options. -- Ian + + The following options, which diff lists as "An alias, no longer + recommended" have been removed: --file-label --entire-new-file + --ascii --print. */ + +static struct option const longopts[] = +{ + {"ignore-blank-lines", 0, 0, 'B'}, + {"context", 2, 0, 143}, + {"ifdef", 1, 0, 147}, + {"show-function-line", 1, 0, 'F'}, + {"speed-large-files", 0, 0, 'H'}, + {"ignore-matching-lines", 1, 0, 'I'}, + {"label", 1, 0, 'L'}, + {"new-file", 0, 0, 'N'}, + {"initial-tab", 0, 0, 'T'}, + {"width", 1, 0, 'W'}, + {"text", 0, 0, 'a'}, + {"ignore-space-change", 0, 0, 'b'}, + {"minimal", 0, 0, 'd'}, + {"ed", 0, 0, 'e'}, + {"forward-ed", 0, 0, 'f'}, + {"ignore-case", 0, 0, 'i'}, + {"paginate", 0, 0, 144}, + {"rcs", 0, 0, 'n'}, + {"show-c-function", 0, 0, 'p'}, + + /* This is a potentially very useful option, except the output is so + silly. It would be much better for it to look like "cvs rdiff -s" + which displays all the same info, minus quite a few lines of + extraneous garbage. */ + {"brief", 0, 0, 145}, + + {"report-identical-files", 0, 0, 's'}, + {"expand-tabs", 0, 0, 't'}, + {"ignore-all-space", 0, 0, 'w'}, + {"side-by-side", 0, 0, 'y'}, + {"unified", 2, 0, 146}, + {"left-column", 0, 0, 129}, + {"suppress-common-lines", 0, 0, 130}, + {"old-line-format", 1, 0, 132}, + {"new-line-format", 1, 0, 133}, + {"unchanged-line-format", 1, 0, 134}, + {"line-format", 1, 0, 135}, + {"old-group-format", 1, 0, 136}, + {"new-group-format", 1, 0, 137}, + {"unchanged-group-format", 1, 0, 138}, + {"changed-group-format", 1, 0, 139}, + {"horizon-lines", 1, 0, 140}, + {"binary", 0, 0, 142}, + {0, 0, 0, 0} +}; + +static void strcat_and_allocate PROTO ((char **, size_t *, const char *)); + +/* *STR is a pointer to a malloc'd string. *LENP is its allocated + length. Add SRC to the end of it, reallocating if necessary. */ +static void +strcat_and_allocate (str, lenp, src) + char **str; + size_t *lenp; + const char *src; +{ + size_t new_size; + + new_size = strlen (*str) + strlen (src) + 1; + if (*str == NULL || new_size >= *lenp) + { + while (new_size >= *lenp) + *lenp *= 2; + *str = xrealloc (*str, *lenp); + } + strcat (*str, src); +} + int diff (argc, argv) int argc; @@ -65,6 +178,7 @@ diff (argc, argv) int c, err = 0; int local = 0; int which; + int option_index; if (argc == -1) usage (diff_usage); @@ -74,47 +188,63 @@ diff (argc, argv) * intercept the -r arguments for doing revision diffs; and -l/-R for a * non-recursive/recursive diff. */ -#ifdef SERVER_SUPPORT - /* Need to be able to do this command more than once (according to - the protocol spec, even if the current client doesn't use it). */ + + /* For server, need to be able to do this command more than once + (according to the protocol spec, even if the current client + doesn't use it). */ + if (opts == NULL) + { + opts_allocated = 1; + opts = xmalloc (opts_allocated); + } opts[0] = '\0'; -#endif + optind = 1; - while ((c = getopt (argc, argv, - "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1) + while ((c = getopt_long (argc, argv, + "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:V:W:k:r:", + longopts, &option_index)) != -1) { switch (c) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': - case 'h': case 'i': case 'n': case 'p': case 't': case 'u': - case 'w': case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': case 'B': - case 'H': case 'T': case 'Q': + case 'h': case 'i': case 'n': case 'p': case 's': case 't': + case 'u': case 'w': case 'y': case '0': case '1': case '2': + case '3': case '4': case '5': case '6': case '7': case '8': + case '9': case 'B': case 'H': case 'T': (void) sprintf (tmp, " -%c", (char) c); - (void) strcat (opts, tmp); - if (c == 'Q') + strcat_and_allocate (&opts, &opts_allocated, tmp); + break; + case 'C': case 'F': case 'I': case 'L': case 'U': case 'V': + case 'W': + (void) sprintf (tmp, " -%c", (char) c); + strcat_and_allocate (&opts, &opts_allocated, tmp); + strcat_and_allocate (&opts, &opts_allocated, optarg); + break; + case 147: + /* --ifdef. */ + strcat_and_allocate (&opts, &opts_allocated, " -D"); + strcat_and_allocate (&opts, &opts_allocated, optarg); + break; + case 129: case 130: case 131: case 132: case 133: case 134: + case 135: case 136: case 137: case 138: case 139: case 140: + case 141: case 142: case 143: case 144: case 145: case 146: + strcat_and_allocate (&opts, &opts_allocated, " --"); + strcat_and_allocate (&opts, &opts_allocated, + longopts[option_index].name); + if (longopts[option_index].has_arg == 1 + || (longopts[option_index].has_arg == 2 + && optarg != NULL)) { - quiet = 1; - really_quiet = 1; - c = 'q'; + strcat_and_allocate (&opts, &opts_allocated, "="); + strcat_and_allocate (&opts, &opts_allocated, optarg); } break; - case 'C': case 'F': case 'I': case 'L': case 'V': -#ifndef CVS_DIFFDATE - case 'D': -#endif - (void) sprintf (tmp, " -%c%s", (char) c, optarg); - (void) strcat (opts, tmp); - break; case 'R': local = 0; break; case 'l': local = 1; break; - case 'q': - quiet = 1; - break; case 'k': if (options) free (options); @@ -129,7 +259,6 @@ diff (argc, argv) else diff_rev1 = optarg; break; -#ifdef CVS_DIFFDATE case 'D': if (diff_rev2 != NULL || diff_date2 != NULL) error (1, 0, @@ -139,7 +268,6 @@ diff (argc, argv) else diff_date1 = Make_Date (optarg); break; -#endif case 'N': empty_files = 1; break; @@ -168,6 +296,8 @@ diff (argc, argv) if (empty_files) send_arg("-N"); send_option_string (opts); + if (options[0] != '\0') + send_arg (options); if (diff_rev1) option_with_arg ("-r", diff_rev1); if (diff_date1) @@ -178,15 +308,12 @@ diff (argc, argv) client_senddate (diff_date2); send_file_names (argc, argv, SEND_EXPAND_WILD); -#if 0 - /* FIXME: We shouldn't have to send current files to diff two - revs, but it doesn't work yet and I haven't debugged it. - So send the files -- it's slower but it works. - gnu@cygnus.com Apr94 */ + /* Send the current files unless diffing two revs from the archive */ if (diff_rev2 == NULL && diff_date2 == NULL) -#endif - send_files (argc, argv, local, 0); + send_files (argc, argv, local, 0, 0); + else + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); send_to_server ("diff\012", 0); err = get_responses_and_close (); @@ -201,15 +328,15 @@ diff (argc, argv) tag_check_valid (diff_rev2, argc, argv, local, 0, ""); which = W_LOCAL; - if (diff_rev2 != NULL || diff_date2 != NULL) + if (diff_rev1 != NULL || diff_date1 != NULL) which |= W_REPOS | W_ATTIC; wrap_setup (); /* start the recursion processor */ err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc, - diff_dirleaveproc, argc, argv, local, - which, 0, 1, (char *) NULL, 1, 0); + diff_dirleaveproc, NULL, argc, argv, local, + which, 0, 1, (char *) NULL, 1); /* clean up */ free (options); @@ -221,26 +348,19 @@ diff (argc, argv) */ /* ARGSUSED */ static int -diff_fileproc (finfo) +diff_fileproc (callerdat, finfo) + void *callerdat; struct file_info *finfo; { int status, err = 2; /* 2 == trouble, like rcsdiff */ Vers_TS *vers; - enum { - DIFF_ERROR, - DIFF_ADDED, - DIFF_REMOVED, - DIFF_NEITHER - } empty_file = DIFF_NEITHER; - char tmp[L_tmpnam+1]; + enum diff_file empty_file = DIFF_DIFFERENT; + char *tmp; char *tocvsPath; - char fname[PATH_MAX]; + char *fname; -#ifdef SERVER_SUPPORT user_file_rev = 0; -#endif - vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL, - finfo->file, 1, 0, finfo->entries, finfo->rcs); + vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); if (diff_rev2 != NULL || diff_date2 != NULL) { @@ -249,10 +369,46 @@ diff_fileproc (finfo) } else if (vers->vn_user == NULL) { - error (0, 0, "I know nothing about %s", finfo->fullname); - freevers_ts (&vers); - diff_mark_errors (err); - return (err); + /* The file does not exist in the working directory. */ + if ((diff_rev1 != NULL || diff_date1 != NULL) + && vers->srcfile != NULL) + { + /* The file does exist in the repository. */ + if (empty_files) + empty_file = DIFF_REMOVED; + else + { + int exists; + + exists = 0; + /* special handling for TAG_HEAD */ + if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) + exists = vers->vn_rcs != NULL; + else + { + Vers_TS *xvers; + + xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, + 1, 0); + exists = xvers->vn_rcs != NULL; + freevers_ts (&xvers); + } + if (exists) + error (0, 0, + "%s no longer exists, no comparison available", + finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + } + else + { + error (0, 0, "I know nothing about %s", finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } } else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') { @@ -299,7 +455,6 @@ diff_fileproc (finfo) diff_mark_errors (err); return (err); } -#ifdef SERVER_SUPPORT else if (!strcmp (vers->ts_user, vers->ts_rcs)) { /* The user file matches some revision in the repository @@ -307,18 +462,76 @@ diff_fileproc (finfo) have a copy of the user file around). */ user_file_rev = vers->vn_user; } -#endif } } - if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers)) + empty_file = diff_file_nodiff (finfo, vers, empty_file); + if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR) { freevers_ts (&vers); - return (0); + if (empty_file == DIFF_SAME) + { + /* In the server case, would be nice to send a "Checked-in" + response, so that the client can rewrite its timestamp. + server_checked_in by itself isn't the right thing (it + needs a server_register), but I'm not sure what is. + It isn't clear to me how "cvs status" handles this (that + is, for a client which sends Modified not Is-modified to + "cvs status"), but it does. */ + return (0); + } + else + { + diff_mark_errors (err); + return (err); + } } - /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal - accordingly. */ + if (empty_file == DIFF_DIFFERENT) + { + int dead1, dead2; + + if (use_rev1 == NULL) + dead1 = 0; + else + dead1 = RCS_isdead (vers->srcfile, use_rev1); + if (use_rev2 == NULL) + dead2 = 0; + else + dead2 = RCS_isdead (vers->srcfile, use_rev2); + + if (dead1 && dead2) + { + freevers_ts (&vers); + return (0); + } + else if (dead1) + { + if (empty_files) + empty_file = DIFF_ADDED; + else + { + error (0, 0, "%s is a new entry, no comparison available", + finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + } + else if (dead2) + { + if (empty_files) + empty_file = DIFF_REMOVED; + else + { + error (0, 0, "%s was removed, no comparison available", + finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + } + } /* Output an "Index:" line for patch to use */ (void) fflush (stdout); @@ -329,10 +542,14 @@ diff_fileproc (finfo) if (tocvsPath) { /* Backup the current version of the file to CVS/,,filename */ + fname = xmalloc (strlen (finfo->file) + + sizeof CVSADM + + sizeof CVSPREFIX + + 10); sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file); if (unlink_file_dir (fname) < 0) if (! existence_error (errno)) - error (1, errno, "cannot remove %s", finfo->file); + error (1, errno, "cannot remove %s", fname); rename_file (finfo->file, fname); /* Copy the wrapped file to the current directory then go to work */ copy_file (tocvsPath, finfo->file); @@ -348,23 +565,44 @@ diff_fileproc (finfo) if (empty_file == DIFF_ADDED) { - run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file); + if (use_rev2 == NULL) + run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file); + else + { + int retcode; + + tmp = cvs_temp_name (); + retcode = RCS_checkout (vers->srcfile, (char *) NULL, + use_rev2, (char *) NULL, + (*options + ? options + : vers->options), + tmp, (RCSCHECKOUTPROC) NULL, + (void *) NULL); + if (retcode == -1) + { + (void) CVS_UNLINK (tmp); + error (1, errno, "fork failed during checkout of %s", + vers->srcfile->path); + } + /* FIXME: what if retcode > 0? */ + + run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, tmp); + } } else { int retcode; - /* - * FIXME: Should be setting use_rev1 using the logic in - * diff_file_nodiff, and using that revision. This code - * is broken for "cvs diff -N -r foo". - */ - retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs, - *options ? options : vers->options, tmpnam (tmp), - 0, 0); + tmp = cvs_temp_name (); + retcode = RCS_checkout (vers->srcfile, (char *) NULL, + use_rev1, (char *) NULL, + *options ? options : vers->options, + tmp, (RCSCHECKOUTPROC) NULL, + (void *) NULL); if (retcode == -1) { - (void) unlink (tmp); + (void) CVS_UNLINK (tmp); error (1, errno, "fork failed during checkout of %s", vers->srcfile->path); } @@ -409,13 +647,18 @@ diff_fileproc (finfo) if (! existence_error (errno)) error (1, errno, "cannot remove %s", finfo->file); - rename_file (fname,finfo->file); + rename_file (fname, finfo->file); if (unlink_file (tocvsPath) < 0) - error (1, errno, "cannot remove %s", finfo->file); + error (1, errno, "cannot remove %s", tocvsPath); + free (fname); } - if (empty_file == DIFF_REMOVED) - (void) unlink (tmp); + if (empty_file == DIFF_REMOVED + || (empty_file == DIFF_ADDED && use_rev2 != NULL)) + { + (void) CVS_UNLINK (tmp); + free (tmp); + } (void) fflush (stdout); freevers_ts (&vers); @@ -441,10 +684,12 @@ diff_mark_errors (err) */ /* ARGSUSED */ static Dtype -diff_dirproc (dir, pos_repos, update_dir) +diff_dirproc (callerdat, dir, pos_repos, update_dir, entries) + void *callerdat; char *dir; char *pos_repos; char *update_dir; + List *entries; { /* XXX - check for dirs we don't want to process??? */ @@ -462,10 +707,12 @@ diff_dirproc (dir, pos_repos, update_dir) */ /* ARGSUSED */ static int -diff_filesdoneproc (err, repos, update_dir) +diff_filesdoneproc (callerdat, err, repos, update_dir, entries) + void *callerdat; int err; char *repos; char *update_dir; + List *entries; { return (diff_errors); } @@ -475,27 +722,26 @@ diff_filesdoneproc (err, repos, update_dir) */ /* ARGSUSED */ static int -diff_dirleaveproc (dir, err, update_dir) +diff_dirleaveproc (callerdat, dir, err, update_dir, entries) + void *callerdat; char *dir; int err; char *update_dir; + List *entries; { return (diff_errors); } /* - * verify that a file is different 0=same 1=different + * verify that a file is different */ -static int -diff_file_nodiff (file, repository, entries, rcs, vers) - char *file; - char *repository; - List *entries; - RCSNode *rcs; +static enum diff_file +diff_file_nodiff (finfo, vers, empty_file) + struct file_info *finfo; Vers_TS *vers; + enum diff_file empty_file; { Vers_TS *xvers; - char tmp[L_tmpnam+1]; int retcode; /* free up any old use_rev* variables and reset 'em */ @@ -512,23 +758,9 @@ diff_file_nodiff (file, repository, entries, rcs, vers) use_rev1 = xstrdup (vers->vn_rcs); else { - xvers = Version_TS (repository, (char *) NULL, diff_rev1, - diff_date1, file, 1, 0, entries, rcs); - if (xvers->vn_rcs == NULL) - { - /* Don't gripe if it doesn't exist, just ignore! */ - if (! isfile (file)) - /* null statement */ ; - else if (diff_rev1) - error (0, 0, "tag %s is not in file %s", diff_rev1, file); - else - error (0, 0, "no revision for date %s in file %s", - diff_date1, file); - - freevers_ts (&xvers); - return (1); - } - use_rev1 = xstrdup (xvers->vn_rcs); + xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0); + if (xvers->vn_rcs != NULL) + use_rev1 = xstrdup (xvers->vn_rcs); freevers_ts (&xvers); } } @@ -539,36 +771,85 @@ diff_file_nodiff (file, repository, entries, rcs, vers) use_rev2 = xstrdup (vers->vn_rcs); else { - xvers = Version_TS (repository, (char *) NULL, diff_rev2, - diff_date2, file, 1, 0, entries, rcs); - if (xvers->vn_rcs == NULL) - { - /* Don't gripe if it doesn't exist, just ignore! */ - if (! isfile (file)) - /* null statement */ ; - else if (diff_rev1) - error (0, 0, "tag %s is not in file %s", diff_rev2, file); - else - error (0, 0, "no revision for date %s in file %s", - diff_date2, file); - - freevers_ts (&xvers); - return (1); - } - use_rev2 = xstrdup (xvers->vn_rcs); + xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0); + if (xvers->vn_rcs != NULL) + use_rev2 = xstrdup (xvers->vn_rcs); freevers_ts (&xvers); } + if (use_rev1 == NULL) + { + /* The first revision does not exist. If EMPTY_FILES is + true, treat this as an added file. Otherwise, warn + about the missing tag. */ + if (use_rev2 == NULL) + /* At least in the case where DIFF_REV1 and DIFF_REV2 + are both numeric, we should be returning some kind + of error (see basicb-8a0 in testsuite). The symbolic + case may be more complicated. */ + return DIFF_SAME; + else if (empty_files) + return DIFF_ADDED; + else if (diff_rev1) + error (0, 0, "tag %s is not in file %s", diff_rev1, + finfo->fullname); + else + error (0, 0, "no revision for date %s in file %s", + diff_date1, finfo->fullname); + return DIFF_ERROR; + } + + if (use_rev2 == NULL) + { + /* The second revision does not exist. If EMPTY_FILES is + true, treat this as a removed file. Otherwise warn + about the missing tag. */ + if (empty_files) + return DIFF_REMOVED; + else if (diff_rev2) + error (0, 0, "tag %s is not in file %s", diff_rev2, + finfo->fullname); + else + error (0, 0, "no revision for date %s in file %s", + diff_date2, finfo->fullname); + return DIFF_ERROR; + } + /* now, see if we really need to do the diff */ - if (use_rev1 && use_rev2) { - return (strcmp (use_rev1, use_rev2) == 0); - } else { - error(0, 0, "No HEAD revision for file %s", file); - return (1); + if (strcmp (use_rev1, use_rev2) == 0) + return DIFF_SAME; + else + return DIFF_DIFFERENT; + } + + if ((diff_rev1 || diff_date1) && use_rev1 == NULL) + { + /* The first revision does not exist, and no second revision + was given. */ + if (empty_files) + { + if (empty_file == DIFF_REMOVED) + return DIFF_SAME; + else + { + if (user_file_rev && use_rev2 == NULL) + use_rev2 = xstrdup (user_file_rev); + return DIFF_ADDED; + } + } + else + { + if (diff_rev1) + error (0, 0, "tag %s is not in file %s", diff_rev1, + finfo->fullname); + else + error (0, 0, "no revision for date %s in file %s", + diff_date1, finfo->fullname); + return DIFF_ERROR; } } -#ifdef SERVER_SUPPORT - if (user_file_rev) + + if (user_file_rev) { /* drop user_file_rev into first unused use_rev */ if (!use_rev1) @@ -582,42 +863,45 @@ diff_file_nodiff (file, repository, entries, rcs, vers) /* now, see if we really need to do the diff */ if (use_rev1 && use_rev2) { - return (strcmp (use_rev1, use_rev2) == 0); + if (strcmp (use_rev1, use_rev2) == 0) + return DIFF_SAME; + else + return DIFF_DIFFERENT; } -#endif /* SERVER_SUPPORT */ - if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0) + + if (use_rev1 == NULL + || (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0)) { - if (strcmp (vers->ts_rcs, vers->ts_user) == 0 && - (!(*options) || strcmp (options, vers->options) == 0)) + if (empty_file == DIFF_DIFFERENT + && vers->ts_user != NULL + && strcmp (vers->ts_rcs, vers->ts_user) == 0 + && (!(*options) || strcmp (options, vers->options) == 0)) { - return (1); + return DIFF_SAME; + } + if (use_rev1 == NULL + && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0')) + { + if (vers->vn_user[0] == '-') + use_rev1 = xstrdup (vers->vn_user + 1); + else + use_rev1 = xstrdup (vers->vn_user); } - if (use_rev1 == NULL) - use_rev1 = xstrdup (vers->vn_user); } + /* If we already know that the file is being added or removed, + then we don't want to do an actual file comparison here. */ + if (empty_file != DIFF_DIFFERENT) + return empty_file; + /* * with 0 or 1 -r option specified, run a quick diff to see if we * should bother with it at all. */ - retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1, - *options ? options : vers->options, tmpnam (tmp), 0, 0); - switch (retcode) - { - case 0: /* everything ok */ - if (xcmp (file, tmp) == 0) - { - (void) unlink (tmp); - return (1); - } - break; - case -1: /* fork failed */ - (void) unlink (tmp); - error (1, errno, "fork failed during checkout of %s", - vers->srcfile->path); - default: - break; - } - (void) unlink (tmp); - return (0); + + retcode = RCS_cmp_file (vers->srcfile, use_rev1, + *options ? options : vers->options, + finfo->file); + + return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT; } diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c index e7ffe56..21400ea 100644 --- a/contrib/cvs/src/main.c +++ b/contrib/cvs/src/main.c @@ -10,27 +10,6 @@ * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing * the shell-script CVS system that this is based on. * - * Usage: - * cvs [options] command [options] [files/modules...] - * - * Where "command" is composed of: - * admin RCS command - * checkout Check out a module/dir/file - * export Like checkout, but used for exporting sources - * update Brings work tree in sync with repository - * commit Checks files into the repository - * diff Runs diffs between revisions - * log Prints "rlog" information for files - * login Record user, host, repos, password - * add Adds an entry to the repository - * remove Removes an entry from the repository - * status Status info on the revisions - * rdiff "patch" format diff listing between releases - * tag Add/delete a symbolic tag to the RCS file - * rtag Add/delete a symbolic tag to the RCS file - * import Import sources into CVS, using vendor branches - * release Indicate that Module is no longer in use. - * history Display history of Users and Modules. */ #include "cvs.h" @@ -41,35 +20,19 @@ extern int gethostname (); #endif -#if HAVE_KERBEROS -#include <sys/socket.h> -#include <netinet/in.h> -#include <krb.h> -#ifndef HAVE_KRB_GET_ERR_TEXT -#define krb_get_err_text(status) krb_err_txt[status] -#endif -#endif - char *program_name; char *program_path; -/* - * Initialize comamnd_name to "cvs" so that the first call to - * read_cvsrc tries to find global cvs options. - */ -char *command_name = ""; +char *command_name; -/* - * Since some systems don't define this... - */ +/* I'd dynamically allocate this, but it seems like gethostname + requires a fixed size array. If I'm remembering the RFCs right, + 256 should be enough. */ #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 256 #endif char hostname[MAXHOSTNAMELEN]; -#ifdef AUTH_CLIENT_SUPPORT -int use_authenticating_server = FALSE; -#endif /* AUTH_CLIENT_SUPPORT */ int use_editor = TRUE; int use_cvsrc = TRUE; int cvswrite = !CVSREAD_DFLT; @@ -86,87 +49,77 @@ char *CurDir; * Defaults, for the environment variables that are not set */ char *Rcsbin = RCSBIN_DFLT; +char *Tmpdir = TMPDIR_DFLT; char *Editor = EDITOR_DFLT; -char *CVSroot = CVSROOT_DFLT; -/* - * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root' - */ -char *CVSADM_Root = CVSROOT_DFLT; - -int add PROTO((int argc, char **argv)); -int admin PROTO((int argc, char **argv)); -int checkout PROTO((int argc, char **argv)); -int commit PROTO((int argc, char **argv)); -int diff PROTO((int argc, char **argv)); -int history PROTO((int argc, char **argv)); -int import PROTO((int argc, char **argv)); -int cvslog PROTO((int argc, char **argv)); -#ifdef AUTH_CLIENT_SUPPORT -int login PROTO((int argc, char **argv)); -#endif /* AUTH_CLIENT_SUPPORT */ -int patch PROTO((int argc, char **argv)); -int release PROTO((int argc, char **argv)); -int cvsremove PROTO((int argc, char **argv)); -int rtag PROTO((int argc, char **argv)); -int status PROTO((int argc, char **argv)); -int tag PROTO((int argc, char **argv)); -int update PROTO((int argc, char **argv)); - -const struct cmd + +static const struct cmd { char *fullname; /* Full name of the function (e.g. "commit") */ - char *nick1; /* alternate name (e.g. "ci") */ - char *nick2; /* another alternate names (e.g. "ci") */ + + /* Synonyms for the command, nick1 and nick2. We supply them + mostly for two reasons: (1) CVS has always supported them, and + we need to maintain compatibility, (2) if there is a need for a + version which is shorter than the fullname, for ease in typing. + Synonyms have the disadvantage that people will see "new" and + then have to think about it, or look it up, to realize that is + the operation they know as "add". Also, this means that one + cannot create a command "cvs new" with a different meaning. So + new synonyms are probably best used sparingly, and where used + should be abbreviations of the fullname (preferably consisting + of the first 2 or 3 or so letters). + + One thing that some systems do is to recognize any unique + abbreviation, for example "annotat" "annota", etc., for + "annotate". The problem with this is that scripts and user + habits will expect a certain abbreviation to be unique, and in + a future release of CVS it may not be. So it is better to + accept only an explicit list of abbreviations and plan on + supporting them in the future as well as now. */ + + char *nick1; + char *nick2; + int (*func) (); /* Function takes (argc, argv) arguments. */ -#ifdef CLIENT_SUPPORT - int (*client_func) (); /* Function to do it via the protocol. */ -#endif } cmds[] = { -#ifdef CLIENT_SUPPORT -#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1, f2 } -#else -#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1 } + { "add", "ad", "new", add }, + { "admin", "adm", "rcs", admin }, + { "annotate", "ann", NULL, annotate }, + { "checkout", "co", "get", checkout }, + { "commit", "ci", "com", commit }, + { "diff", "di", "dif", diff }, + { "edit", NULL, NULL, edit }, + { "editors", NULL, NULL, editors }, + { "export", "exp", "ex", checkout }, + { "history", "hi", "his", history }, + { "import", "im", "imp", import }, + { "init", NULL, NULL, init }, +#ifdef SERVER_SUPPORT + { "kserver", NULL, NULL, server }, /* placeholder */ #endif - - CMD_ENTRY("add", "ad", "new", add, client_add), - CMD_ENTRY("admin", "adm", "rcs", admin, client_admin), - CMD_ENTRY("annotate", NULL, NULL, annotate, client_annotate), - CMD_ENTRY("checkout", "co", "get", checkout, client_checkout), - CMD_ENTRY("commit", "ci", "com", commit, client_commit), - CMD_ENTRY("diff", "di", "dif", diff, client_diff), - CMD_ENTRY("edit", "edit", "edit", edit, client_edit), - CMD_ENTRY("editors", "editors","editors",editors, client_editors), - CMD_ENTRY("export", "exp", "ex", checkout, client_export), - CMD_ENTRY("history", "hi", "his", history, client_history), - CMD_ENTRY("import", "im", "imp", import, client_import), - CMD_ENTRY("init", NULL, NULL, init, client_init), - CMD_ENTRY("log", "lo", "rlog", cvslog, client_log), + { "log", "lo", "rlog", cvslog }, #ifdef AUTH_CLIENT_SUPPORT - CMD_ENTRY("login", "logon", "lgn", login, login), + { "login", "logon", "lgn", login }, + { "logout", NULL, NULL, logout }, +#ifdef SERVER_SUPPORT + { "pserver", NULL, NULL, server }, /* placeholder */ +#endif #endif /* AUTH_CLIENT_SUPPORT */ - CMD_ENTRY("rdiff", "patch", "pa", patch, client_rdiff), - CMD_ENTRY("release", "re", "rel", release, client_release), - CMD_ENTRY("remove", "rm", "delete", cvsremove, client_remove), - CMD_ENTRY("status", "st", "stat", status, client_status), - CMD_ENTRY("rtag", "rt", "rfreeze", rtag, client_rtag), - CMD_ENTRY("tag", "ta", "freeze", tag, client_tag), - CMD_ENTRY("unedit", "unedit","unedit", unedit, client_unedit), - CMD_ENTRY("update", "up", "upd", update, client_update), - CMD_ENTRY("watch", "watch", "watch", watch, client_watch), - CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers), + { "rdiff", "patch", "pa", patch }, + { "release", "re", "rel", release }, + { "remove", "rm", "delete", cvsremove }, + { "status", "st", "stat", status }, + { "rtag", "rt", "rfreeze", rtag }, + { "tag", "ta", "freeze", cvstag }, + { "unedit", NULL, NULL, unedit }, + { "update", "up", "upd", update }, + { "watch", NULL, NULL, watch }, + { "watchers", NULL, NULL, watchers }, #ifdef SERVER_SUPPORT - /* - * The client_func is also server because we might have picked up a - * CVSROOT environment variable containing a colon. The client will send - * the real root later. - */ - CMD_ENTRY("server", "server", "server", server, server), + { "server", NULL, NULL, server }, #endif - CMD_ENTRY(NULL, NULL, NULL, NULL, NULL), - -#undef CMD_ENTRY + { NULL, NULL, NULL, NULL }, }; static const char *const usg[] = @@ -183,11 +136,15 @@ static const char *const usg[] = " -t Show trace of program execution -- Try with -n\n", " -v CVS version and copyright\n", " -b bindir Find RCS programs in 'bindir'\n", + " -T tmpdir Use 'tmpdir' for temporary files\n", " -e editor Use 'editor' for editing log information\n", " -d CVS_root Overrides $CVSROOT as the root of the CVS tree\n", " -f Do not use the ~/.cvsrc file\n", #ifdef CLIENT_SUPPORT - " -z # Use 'gzip -#' for net traffic if possible.\n", + " -z # Use compression level '#' for net traffic.\n", +#ifdef ENCRYPTION + " -x Encrypt all net traffic.\n", +#endif #endif " -s VAR=VAL Set CVS user variable.\n", "\n", @@ -199,49 +156,165 @@ static const char *const usg[] = static const char *const cmd_usage[] = { "CVS commands are:\n", - " add Adds a new file/directory to the repository\n", + " add Add a new file/directory to the repository\n", " admin Administration front end for rcs\n", - " annotate Show revision where each line was modified\n", + " annotate Show last revision where each line was modified\n", " checkout Checkout sources for editing\n", - " commit Checks files into the repository\n", - " diff Runs diffs between revisions\n", + " commit Check files into the repository\n", + " diff Show differences between revisions\n", " edit Get ready to edit a watched file\n", " editors See who is editing a watched file\n", - " history Shows status of files and users\n", - " import Import sources into CVS, using vendor branches\n", " export Export sources from CVS, similar to checkout\n", - " init Initialize a new CVS repository\n", - " log Prints out 'rlog' information for files\n", + " history Show repository access history\n", + " import Import sources into CVS, using vendor branches\n", + " init Create a CVS repository if it doesn't exist\n", + " log Print out history information for files\n", #ifdef AUTH_CLIENT_SUPPORT " login Prompt for password for authenticating server.\n", + " logout Removes entry in .cvspass for remote repository.\n", #endif /* AUTH_CLIENT_SUPPORT */ - " rdiff 'patch' format diffs between releases\n", + " rdiff Create 'patch' format diffs between releases\n", " release Indicate that a Module is no longer in use\n", - " remove Removes an entry from the repository\n", - " status Status info on the revisions\n", - " tag Add a symbolic tag to checked out version of RCS file\n", + " remove Remove an entry from the repository\n", + " rtag Add a symbolic tag to a module\n", + " status Display status information on checked out files\n", + " tag Add a symbolic tag to checked out version of files\n", " unedit Undo an edit command\n", - " rtag Add a symbolic tag to the RCS file\n", - " update Brings work tree in sync with repository\n", + " update Bring work tree in sync with repository\n", " watch Set watches\n", " watchers See who is watching a file\n", + "(Use the --help-synonyms option for a list of alternate command names)\n", NULL, }; -static RETSIGTYPE -main_cleanup () +static const char * const* +cmd_synonyms () { - exit (EXIT_FAILURE); + char ** synonyms; + char ** line; + const struct cmd *c = &cmds[0]; + int numcmds = 2; /* two more for title and end */ + + while (c->fullname != NULL) + { + numcmds++; + c++; + } + + synonyms = (char **) xmalloc(numcmds * sizeof(char *)); + line = synonyms; + *line++ = "CVS command synonyms are:\n"; + for (c = &cmds[0]; c->fullname != NULL; c++) + { + if (c->nick1 || c->nick2) + { + *line = xmalloc (strlen (c->fullname) + + (c->nick1 != NULL ? strlen (c->nick1) : 0) + + (c->nick2 != NULL ? strlen (c->nick2) : 0) + + 40); + sprintf(*line, " %-12s %s %s\n", c->fullname, + c->nick1 ? c->nick1 : "", + c->nick2 ? c->nick2 : ""); + line++; + } + } + *line = NULL; + + return (const char * const*) synonyms; /* will never be freed */ } -static void -error_cleanup PROTO((void)) + +unsigned long int +lookup_command_attribute (cmd_name) + char *cmd_name; { - Lock_Cleanup(); -#ifdef SERVER_SUPPORT - if (server_active) - server_cleanup (0); + unsigned long int ret = 0; + + if (strcmp (cmd_name, "import") != 0) + { + ret |= CVS_CMD_IGNORE_ADMROOT; + } + + + if ((strcmp (cmd_name, "checkout") != 0) && + (strcmp (cmd_name, "init") != 0) && + (strcmp (cmd_name, "login") != 0) && + (strcmp (cmd_name, "logout") != 0) && + (strcmp (cmd_name, "rdiff") != 0) && + (strcmp (cmd_name, "release") != 0) && + (strcmp (cmd_name, "rtag") != 0)) + { + ret |= CVS_CMD_USES_WORK_DIR; + } + + + /* The following commands do not modify the repository; we + conservatively assume that everything else does. Feel free to + add to this list if you are _certain_ something is safe. */ + if ((strcmp (cmd_name, "checkout") != 0) && + (strcmp (cmd_name, "diff") != 0) && + (strcmp (cmd_name, "update") != 0) && + (strcmp (cmd_name, "history") != 0) && + (strcmp (cmd_name, "editors") != 0) && + (strcmp (cmd_name, "export") != 0) && + (strcmp (cmd_name, "history") != 0) && + (strcmp (cmd_name, "log") != 0) && + (strcmp (cmd_name, "noop") != 0) && + (strcmp (cmd_name, "watchers") != 0) && + (strcmp (cmd_name, "status") != 0)) + { + ret |= CVS_CMD_MODIFIES_REPOSITORY; + } + + return ret; +} + + +static RETSIGTYPE +main_cleanup (sig) + int sig; +{ +#ifndef DONT_USE_SIGNALS + const char *name; + char temp[10]; + + switch (sig) + { +#ifdef SIGHUP + case SIGHUP: + name = "hangup"; + break; +#endif +#ifdef SIGINT + case SIGINT: + name = "interrupt"; + break; #endif +#ifdef SIGQUIT + case SIGQUIT: + name = "quit"; + break; +#endif +#ifdef SIGPIPE + case SIGPIPE: + name = "broken pipe"; + break; +#endif +#ifdef SIGTERM + case SIGTERM: + name = "termination"; + break; +#endif + default: + /* This case should never be reached, because we list above all + the signals for which we actually establish a signal handler. */ + sprintf (temp, "%d", sig); + name = temp; + break; + } + + error (1, 0, "received %s signal", name); +#endif /* !DONT_USE_SIGNALS */ } int @@ -249,56 +322,70 @@ main (argc, argv) int argc; char **argv; { + char *CVSroot = CVSROOT_DFLT; extern char *version_string; extern char *config_string; char *cp, *end; const struct cmd *cm; int c, err = 0; - static int help = FALSE; - static int version_flag = FALSE; - static int help_commands = FALSE; - int rcsbin_update_env, cvs_update_env = 0; + int rcsbin_update_env, tmpdir_update_env, cvs_update_env; + int help = 0; /* Has the user asked for help? This + lets us support the `cvs -H cmd' + convention to give help for cmd. */ static struct option long_options[] = { - {"help", 0, &help, TRUE}, - {"version", 0, &version_flag, TRUE}, - {"help-commands", 0, &help_commands, TRUE}, + {"help", 0, NULL, 'H'}, + {"version", 0, NULL, 'v'}, + {"help-commands", 0, NULL, 1}, + {"help-synonyms", 0, NULL, 2}, {0, 0, 0, 0} }; /* `getopt_long' stores the option index here, but right now we don't use it. */ int option_index = 0; + int need_to_create_root = 0; - error_set_cleanup (error_cleanup); +#ifdef SYSTEM_INITIALIZE + /* Hook for OS-specific behavior, for example socket subsystems on + NT and OS2 or dealing with windows and arguments on Mac. */ + SYSTEM_INITIALIZE (&argc, &argv); +#endif -/* The socket subsystems on NT and OS2 must be initialized before use */ -#ifdef INITIALIZE_SOCKET_SUBSYSTEM - INITIALIZE_SOCKET_SUBSYSTEM(); -#endif /* INITIALIZE_SOCKET_SUBSYSTEM */ +#ifdef HAVE_TZSET + /* On systems that have tzset (which is almost all the ones I know + of), it's a good idea to call it. */ + tzset (); +#endif /* * Just save the last component of the path for error messages */ program_path = xstrdup (argv[0]); +#ifdef ARGV0_NOT_PROGRAM_NAME + /* On some systems, e.g. VMS, argv[0] is not the name of the command + which the user types to invoke the program. */ + program_name = "cvs"; +#else program_name = last_component (argv[0]); - - CurDir = xmalloc (PATH_MAX); -#ifndef SERVER_SUPPORT - if (!getwd (CurDir)) - error (1, 0, "cannot get working directory: %s", CurDir); #endif /* * Query the environment variables up-front, so that * they can be overridden by command line arguments */ - rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */ cvs_update_env = 0; + rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */ if ((cp = getenv (RCSBIN_ENV)) != NULL) { Rcsbin = cp; rcsbin_update_env = 0; /* it's already there */ } + tmpdir_update_env = *Tmpdir; /* TMPDIR_DFLT must be set */ + if ((cp = getenv (TMPDIR_ENV)) != NULL) + { + Tmpdir = cp; + tmpdir_update_env = 0; /* it's already there */ + } if ((cp = getenv (EDITOR1_ENV)) != NULL) Editor = cp; else if ((cp = getenv (EDITOR2_ENV)) != NULL) @@ -312,20 +399,10 @@ main (argc, argv) } if (getenv (CVSREAD_ENV) != NULL) cvswrite = FALSE; - if ((cp = getenv (CVSUMASK_ENV)) != NULL) - { - /* FIXME: Should be accepting symbolic as well as numeric mask. */ - cvsumask = strtol (cp, &end, 8) & 0777; - if (*end != '\0') - error (1, errno, "invalid umask value in %s (%s)", - CVSUMASK_ENV, cp); - } - /* This has the effect of setting getopt's ordering to REQUIRE_ORDER, - which is what we need to distinguish between global options and - command options. FIXME: It would appear to be possible to do this - much less kludgily by passing "+" as the first character to the - option string we pass to getopt_long. */ + /* I'm not sure whether this needs to be 1 instead of 0 anymore. Using + 1 used to accomplish what passing "+" as the first character to + the option string does, but that reason doesn't exist anymore. */ optind = 1; @@ -336,13 +413,13 @@ main (argc, argv) opterr = 0; while ((c = getopt_long - (argc, argv, "f", NULL, NULL)) + (argc, argv, "+f", NULL, NULL)) != EOF) - { + { if (c == 'f') use_cvsrc = FALSE; - } - + } + /* * Scan cvsrc file for global options. */ @@ -353,13 +430,18 @@ main (argc, argv) opterr = 1; while ((c = getopt_long - (argc, argv, "Qqrwtnlvb:e:d:Hfz:s:", long_options, &option_index)) + (argc, argv, "+Qqrwtnlvb:T:e:d:Hfz:s:x", long_options, &option_index)) != EOF) { switch (c) { - case 0: - /* getopt_long took care of setting the flag. */ + case 1: + /* --help-commands */ + usage (cmd_usage); + break; + case 2: + /* --help-synonyms */ + usage (cmd_synonyms()); break; case 'Q': really_quiet = TRUE; @@ -382,12 +464,26 @@ main (argc, argv) logoff = TRUE; break; case 'v': - version_flag = TRUE; + (void) fputs (version_string, stdout); + (void) fputs (config_string, stdout); + (void) fputs ("\n", stdout); + (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout); + (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout); + (void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout); + (void) fputs ("Copyright (c) 1989-1992, Brian Berliner\n", stdout); + (void) fputs ("\n", stdout); + (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout); + (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout); + exit (0); break; case 'b': Rcsbin = optarg; rcsbin_update_env = 1; /* need to update environment */ break; + case 'T': + Tmpdir = optarg; + tmpdir_update_env = 1; /* need to update environment */ + break; case 'e': Editor = optarg; break; @@ -396,11 +492,10 @@ main (argc, argv) cvs_update_env = 1; /* need to update environment */ break; case 'H': - use_cvsrc = FALSE; /* this ensure that cvs -H works */ - help = TRUE; + help = 1; break; case 'f': - use_cvsrc = FALSE; + use_cvsrc = FALSE; /* unnecessary, since we've done it above */ break; case 'z': #ifdef CLIENT_SUPPORT @@ -416,313 +511,307 @@ main (argc, argv) case 's': variable_set (optarg); break; + case 'x': +#ifdef CLIENT_SUPPORT + cvsencrypt = 1; +#endif /* CLIENT_SUPPORT */ + /* If no CLIENT_SUPPORT, ignore -x, so that users can + have it in their .cvsrc and not cause any trouble. + If no ENCRYPTION, we still accept -x, but issue an + error if we are being run as a client. */ + break; case '?': default: usage (usg); } } - if (version_flag == TRUE) - { - (void) fputs (version_string, stdout); - (void) fputs (config_string, stdout); - (void) fputs ("\n", stdout); - (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout); - (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout); - (void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout); - (void) fputs ("Copyright (c) 1989-1992, Brian Berliner\n", stdout); - (void) fputs ("\n", stdout); - (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout); - (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout); - exit (0); - } - else if (help_commands) - usage (cmd_usage); - argc -= optind; argv += optind; if (argc < 1) usage (usg); -#ifdef HAVE_KERBEROS - /* If we are invoked with a single argument "kserver", then we are - running as Kerberos server as root. Do the authentication as - the very first thing, to minimize the amount of time we are - running as root. */ - if (strcmp (argv[0], "kserver") == 0) + + /* Look up the command name. */ + + command_name = argv[0]; + for (cm = cmds; cm->fullname; cm++) { - int status; - char instance[INST_SZ]; - struct sockaddr_in peer; - struct sockaddr_in laddr; - int len; - KTEXT_ST ticket; - AUTH_DAT auth; - char version[KRB_SENDAUTH_VLEN]; - Key_schedule sched; - char user[ANAME_SZ]; - struct passwd *pw; - - strcpy (instance, "*"); - len = sizeof peer; - if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0 - || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr, - &len) < 0) - { - printf ("E Fatal error, aborting.\n\ -error %s getpeername or getsockname failed\n", strerror (errno)); - exit (EXIT_FAILURE); - } + if (cm->nick1 && !strcmp (command_name, cm->nick1)) + break; + if (cm->nick2 && !strcmp (command_name, cm->nick2)) + break; + if (!strcmp (command_name, cm->fullname)) + break; + } - status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd", - instance, &peer, &laddr, &auth, "", sched, - version); - if (status != KSUCCESS) - { - printf ("E Fatal error, aborting.\n\ -error 0 kerberos: %s\n", krb_get_err_text(status)); - exit (EXIT_FAILURE); - } + if (!cm->fullname) + usage (cmd_usage); /* no match */ + else + command_name = cm->fullname; /* Global pointer for later use */ - /* Get the local name. */ - status = krb_kntoln (&auth, user); - if (status != KSUCCESS) - { - printf ("E Fatal error, aborting.\n\ -error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status)); - exit (EXIT_FAILURE); - } + if (strcmp (argv[0], "rlog") == 0) + { + error (0, 0, "warning: the rlog command is deprecated"); + error (0, 0, "use the synonymous log command instead"); + } - pw = getpwnam (user); - if (pw == NULL) - { - printf ("E Fatal error, aborting.\n\ -error 0 %s: no such user\n", user); - exit (EXIT_FAILURE); - } + if (help) + argc = -1; /* some functions only check for this */ + else + { + /* The user didn't ask for help, so go ahead and authenticate, + set up CVSROOT, and the rest of it. */ - initgroups (pw->pw_name, pw->pw_gid); - setgid (pw->pw_gid); - setuid (pw->pw_uid); - /* Inhibit access by randoms. Don't want people randomly - changing our temporary tree before we check things in. */ - umask (077); + /* The UMASK environment variable isn't handled with the + others above, since we don't want to signal errors if the + user has asked for help. This won't work if somebody adds + a command-line flag to set the umask, since we'll have to + parse it before we get here. */ -#if HAVE_PUTENV - /* Set LOGNAME and USER in the environment, in case they are - already set to something else. */ + if ((cp = getenv (CVSUMASK_ENV)) != NULL) { - char *env; + /* FIXME: Should be accepting symbolic as well as numeric mask. */ + cvsumask = strtol (cp, &end, 8) & 0777; + if (*end != '\0') + error (1, errno, "invalid umask value in %s (%s)", + CVSUMASK_ENV, cp); + } - env = xmalloc (sizeof "LOGNAME=" + strlen (user)); - (void) sprintf (env, "LOGNAME=%s", user); - (void) putenv (env); +#if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT) + /* If we are invoked with a single argument "kserver", then we are + running as Kerberos server as root. Do the authentication as + the very first thing, to minimize the amount of time we are + running as root. */ + if (strcmp (command_name, "kserver") == 0) + { + kserver_authenticate_connection (); - env = xmalloc (sizeof "USER=" + strlen (user)); - (void) sprintf (env, "USER=%s", user); - (void) putenv (env); + /* Pretend we were invoked as a plain server. */ + command_name = "server"; } -#endif - - /* Pretend we were invoked as a plain server. */ - argv[0] = "server"; - } #endif /* HAVE_KERBEROS */ #if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT) - if (strcmp (argv[0], "pserver") == 0) - { - /* Gets username and password from client, authenticates, then - switches to run as that user and sends an ACK back to the - client. */ - authenticate_connection (); + if (strcmp (command_name, "pserver") == 0) + { + /* Gets username and password from client, authenticates, then + switches to run as that user and sends an ACK back to the + client. */ + pserver_authenticate_connection (); - /* Pretend we were invoked as a plain server. */ - argv[0] = "server"; - } + /* Pretend we were invoked as a plain server. */ + command_name = "server"; + } #endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */ - /* - * See if we are able to find a 'better' value for CVSroot in the - * CVSADM_ROOT directory. - */ -#ifdef SERVER_SUPPORT - if (strcmp (argv[0], "server") == 0 && CVSroot == NULL) - CVSADM_Root = NULL; - else - CVSADM_Root = Name_Root((char *) NULL, (char *) NULL); -#else /* No SERVER_SUPPORT */ - CVSADM_Root = Name_Root((char *) NULL, (char *) NULL); -#endif /* No SERVER_SUPPORT */ - if (CVSADM_Root != NULL) - { - if (CVSroot == NULL || !cvs_update_env) - { - CVSroot = CVSADM_Root; - cvs_update_env = 1; /* need to update environment */ - } -#ifdef CLIENT_SUPPORT - else if (!getenv ("CVS_IGNORE_REMOTE_ROOT")) -#else /* ! CLIENT_SUPPORT */ - else -#endif /* CLIENT_SUPPORT */ - { - /* - * Now for the hard part, compare the two directories. If they - * are not identical, then abort this command. - */ - if ((fncmp (CVSroot, CVSADM_Root) != 0) && - !same_directories(CVSroot, CVSADM_Root)) - { - error (0, 0, "%s value for CVS Root found in %s", - CVSADM_Root, CVSADM_ROOT); - error (0, 0, "does not match command line -d %s setting", - CVSroot); - error (1, 0, - "you may wish to try the cvs command again without the -d option "); - } - } - } - - /* CVSroot may need fixing up, if an access-method was specified, - * but not a user. Later code assumes that if CVSroot contains an - * access-method, then it also has a user. We print a warning and - * die if we can't guarantee that. - */ - if (CVSroot - && *CVSroot - && (CVSroot[0] == ':') - && (strchr (CVSroot, '@') == NULL)) - { - error (1, 0, - "must also give a username if specifying access method"); - } + /* Fiddling with CVSROOT doesn't make sense if we're running + in server mode, since the client will send the repository + directory after the connection is made. */ - /* - * Specifying just the '-H' flag to the sub-command causes a Usage - * message to be displayed. - */ - command_name = cp = argv[0]; - if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0)) - argc = -1; - else - { - /* - * Check to see if we can write into the history file. If not, - * we assume that we can't work in the repository. - * BUT, only if the history file exists. - */ #ifdef SERVER_SUPPORT - if (strcmp (command_name, "server") != 0 || CVSroot != NULL) + if (strcmp (command_name, "server") != 0) #endif { - char path[PATH_MAX]; - int save_errno; + char *CVSADM_Root; + + /* See if we are able to find a 'better' value for CVSroot + in the CVSADM_ROOT directory. */ - if (!CVSroot || !*CVSroot) - error (1, 0, "You don't have a %s environment variable", - CVSROOT_ENV); - (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM); - if (!isaccessible (path, R_OK | X_OK)) + CVSADM_Root = NULL; + + /* "cvs import" shouldn't check CVS/Root; in general it + ignores CVS directories and CVS/Root is likely to + specify a different repository than the one we are + importing to. */ + + if (lookup_command_attribute (command_name) + & CVS_CMD_IGNORE_ADMROOT) + { + CVSADM_Root = Name_Root((char *) NULL, (char *) NULL); + } + + if (CVSADM_Root != NULL) { - save_errno = errno; - /* If this is "cvs init", the root need not exist yet. */ - if (strcmp (command_name, "init") != 0 + if (CVSroot == NULL || !cvs_update_env) + { + CVSroot = CVSADM_Root; + cvs_update_env = 1; /* need to update environment */ + } + /* Let -d override CVS/Root file. The user might want + to change the access method, use a different server + (if there are two server machines which share the + repository using a networked file system), etc. */ + else if ( #ifdef CLIENT_SUPPORT - /* If we are a remote client, the root need not exist - on the client machine (FIXME: we should also skip - the check for CVSROOTADM_HISTORY being writable; - it shouldn't matter if there is a read-only file - which happens to have the same name on the client - machine). */ - && strchr (CVSroot, ':') == NULL) + !getenv ("CVS_IGNORE_REMOTE_ROOT") && #endif + strcmp (CVSroot, CVSADM_Root) != 0) { - error (0, 0, - "Sorry, you don't have sufficient access to %s", CVSroot); - error (1, save_errno, "%s", path); + /* Once we have verified that this root is usable, + we will want to write it into CVS/Root. + + Don't do it for the "login" command, however. + Consider: if the user executes "cvs login" with + the working directory inside an already checked + out module, we'd incorrectly change the + CVS/Root file to reflect the CVSROOT of the + "cvs login" command. Ahh, the things one + discovers. */ + + if (lookup_command_attribute (command_name) + & CVS_CMD_USES_WORK_DIR) + { + need_to_create_root = 1; + } + } } - (void) strcat (path, "/"); - (void) strcat (path, CVSROOTADM_HISTORY); - if (isfile (path) && !isaccessible (path, R_OK | W_OK)) + + /* Now we've reconciled CVSROOT from the command line, the + CVS/Root file, and the environment variable. Do the + last sanity checks on the variable. */ + + if (! CVSroot) { - save_errno = errno; error (0, 0, - "Sorry, you don't have read/write access to the history file"); - error (1, save_errno, "%s", path); + "No CVSROOT specified! Please use the `-d' option"); + error (1, 0, + "or set the %s environment variable.", CVSROOT_ENV); + } + + if (! *CVSroot) + { + error (0, 0, + "CVSROOT is set but empty! Make sure that the"); + error (0, 0, + "specification of CVSROOT is legal, either via the"); + error (0, 0, + "`-d' option, the %s environment variable, or the", + CVSROOT_ENV); + error (1, 0, + "CVS/Root file (if any)."); } - } - } -#ifdef SERVER_SUPPORT - if (strcmp (command_name, "server") == 0) - /* This is only used for writing into the history file. Might - be nice to have hostname and/or remote path, on the other hand - I'm not sure whether it is worth the trouble. */ - strcpy (CurDir, "<remote>"); - else if (!getwd (CurDir)) - error (1, 0, "cannot get working directory: %s", CurDir); -#endif + /* Now we're 100% sure that we have a valid CVSROOT + variable. Parse it to see if we're supposed to do + remote accesses or use a special access method. */ + + if (parse_cvsroot (CVSroot)) + error (1, 0, "Bad CVSROOT."); + + /* + * Check to see if we can write into the history file. If not, + * we assume that we can't work in the repository. + * BUT, only if the history file exists. + */ + + if (!client_active) + { + char *path; + int save_errno; + + path = xmalloc (strlen (CVSroot_directory) + + sizeof (CVSROOTADM) + + 20 + + sizeof (CVSROOTADM_HISTORY)); + (void) sprintf (path, "%s/%s", CVSroot_directory, CVSROOTADM); + if (!isaccessible (path, R_OK | X_OK)) + { + save_errno = errno; + /* If this is "cvs init", the root need not exist yet. */ + if (strcmp (command_name, "init") != 0) + { + error (1, save_errno, "%s", path); + } + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && !isaccessible (path, R_OK | W_OK)) + { + save_errno = errno; + error (0, 0, "Sorry, you don't have read/write access to the history file"); + error (1, save_errno, "%s", path); + } + free (path); + } #ifdef HAVE_PUTENV - /* Now, see if we should update the environment with the Rcsbin value */ - if (cvs_update_env) - { - char *env; + /* Update the CVSROOT environment variable if necessary. */ - env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); - (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot); - (void) putenv (env); - /* do not free env, as putenv has control of it */ - } - if (rcsbin_update_env) - { - char *env; + if (cvs_update_env) + { + char *env; + env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + + 1 + 1); + (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } +#endif + } + + /* This is only used for writing into the history file. For + remote connections, it might be nice to have hostname + and/or remote path, on the other hand I'm not sure whether + it is worth the trouble. */ - env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1); - (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin); - (void) putenv (env); - /* do not free env, as putenv has control of it */ - } +#ifdef SERVER_SUPPORT + if (strcmp (command_name, "server") == 0) + CurDir = xstrdup ("<remote>"); + else #endif + { + CurDir = xgetwd (); + if (CurDir == NULL) + error (1, errno, "cannot get working directory"); + } - /* - * If Rcsbin is set to something, make sure it is terminated with - * a slash character. If not, add one. - */ - if (*Rcsbin) - { - int len = strlen (Rcsbin); - char *rcsbin; + if (Tmpdir == NULL || Tmpdir[0] == '\0') + Tmpdir = "/tmp"; - if (Rcsbin[len - 1] != '/') +#ifdef HAVE_PUTENV + /* Now, see if we should update the environment with the + Rcsbin value */ + if (rcsbin_update_env) { - rcsbin = Rcsbin; - Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ - (void) strcpy (Rcsbin, rcsbin); - (void) strcat (Rcsbin, "/"); + char *env; + env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1); + (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin); + (void) putenv (env); + /* do not free env, as putenv has control of it */ } - } + if (tmpdir_update_env) + { + char *env; + env = xmalloc (strlen (TMPDIR_ENV) + strlen (Tmpdir) + 1 + 1); + (void) sprintf (env, "%s=%s", TMPDIR_ENV, Tmpdir); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } +#endif - for (cm = cmds; cm->fullname; cm++) - { - if (cm->nick1 && !strcmp (cp, cm->nick1)) - break; - if (cm->nick2 && !strcmp (cp, cm->nick2)) - break; - if (!strcmp (cp, cm->fullname)) - break; - } + /* + * If Rcsbin is set to something, make sure it is terminated with + * a slash character. If not, add one. + */ + if (*Rcsbin) + { + int len = strlen (Rcsbin); + char *rcsbin; - if (!cm->fullname) - usage (usg); /* no match */ - else - { - command_name = cm->fullname; /* Global pointer for later use */ + if (Rcsbin[len - 1] != '/') + { + rcsbin = Rcsbin; + Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ + (void) strcpy (Rcsbin, rcsbin); + (void) strcat (Rcsbin, "/"); + } + } +#ifndef DONT_USE_SIGNALS /* make sure we clean up on error */ #ifdef SIGHUP (void) SIG_register (SIGHUP, main_cleanup); @@ -744,39 +833,44 @@ error 0 %s: no such user\n", user); (void) SIG_register (SIGTERM, main_cleanup); (void) SIG_register (SIGTERM, Lock_Cleanup); #endif +#endif /* !DONT_USE_SIGNALS */ gethostname(hostname, sizeof (hostname)); -#ifdef HAVE_SETVBUF - /* - * Make stdout line buffered, so 'tail -f' can monitor progress. - * Patch creates too much output to monitor and it runs slowly. - */ - if (strcmp (cm->fullname, "patch")) - (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0); -#endif +#ifdef KLUDGE_FOR_WNT_TESTSUITE + /* Probably the need for this will go away at some point once + we call fflush enough places (e.g. fflush (stdout) in + cvs_outerr). */ + (void) setvbuf (stdout, (char *) NULL, _IONBF, 0); + (void) setvbuf (stderr, (char *) NULL, _IONBF, 0); +#endif /* KLUDGE_FOR_WNT_TESTSUITE */ if (use_cvsrc) - read_cvsrc (&argc, &argv, command_name); + read_cvsrc (&argc, &argv, command_name); -#ifdef CLIENT_SUPPORT - /* If cvsroot contains a colon, try to do it via the protocol. */ - { - char *p = CVSroot == NULL ? NULL : strchr (CVSroot, ':'); - if (p) - err = (*(cm->client_func)) (argc, argv); - else - err = (*(cm->func)) (argc, argv); - } -#else /* No CLIENT_SUPPORT */ - err = (*(cm->func)) (argc, argv); + } /* end of stuff that gets done if the user DOESN'T ask for help */ + + err = (*(cm->func)) (argc, argv); -#endif /* No CLIENT_SUPPORT */ + if (need_to_create_root) + { + /* Update the CVS/Root file. We might want to do this in + all directories that we recurse into, but currently we + don't. */ + Create_Root (NULL, CVSroot); } + Lock_Cleanup (); - if (err) - return (EXIT_FAILURE); - return 0; + +#ifdef SYSTEM_CLEANUP + /* Hook for OS-specific behavior, for example socket subsystems on + NT and OS2 or dealing with windows and arguments on Mac. */ + SYSTEM_CLEANUP (); +#endif + + /* This is exit rather than return because apparently that keeps + some tools which check for memory leaks happier. */ + exit (err ? EXIT_FAILURE : 0); } char * @@ -785,7 +879,7 @@ Make_Date (rawdate) { struct tm *ftm; time_t unixtime; - char date[256]; /* XXX bigger than we'll ever need? */ + char date[MAXDATELEN]; char *ret; unixtime = get_date (rawdate, (struct timeb *) NULL); @@ -811,5 +905,5 @@ usage (cpp) (void) fprintf (stderr, *cpp++, program_name, command_name); for (; *cpp; cpp++) (void) fprintf (stderr, *cpp); - exit (EXIT_FAILURE); + error_exit (); } diff --git a/contrib/cvs/src/mkmodules.c b/contrib/cvs/src/mkmodules.c index 890c3a4..cff993a 100644 --- a/contrib/cvs/src/mkmodules.c +++ b/contrib/cvs/src/mkmodules.c @@ -7,13 +7,14 @@ #include "cvs.h" #include "savecwd.h" +#include "getline.h" #ifndef DBLKSIZ #define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ #endif static int checkout_file PROTO((char *file, char *temp)); -static void make_tempfile PROTO((char *temp)); +static char *make_tempfile PROTO((void)); static void rename_rcsfile PROTO((char *temp, char *real)); #ifndef MY_NDBM @@ -28,38 +29,50 @@ struct admin_file { /* This is a one line description of what the file is for. It is not currently used, although one wonders whether it should be, somehow. - If NULL, then don't process this file in mkmodules (FIXME: a bit of + If NULL, then don't process this file in mkmodules (FIXME?: a bit of a kludge; probably should replace this with a flags field). */ char *errormsg; /* Contents which the file should have in a new repository. To avoid problems with brain-dead compilers which choke on long string constants, this is a pointer to an array of char * terminated by NULL--each of - the strings is concatenated. */ + the strings is concatenated. + + If this field is NULL, the file is not created in a new + repository, but it can be added with "cvs add" (just as if one + had created the repository with a version of CVS which didn't + know about the file) and the checked-out copy will be updated + without having to add it to checkoutlist. */ const char * const *contents; }; static const char *const loginfo_contents[] = { - "# The \"loginfo\" file is used to control where \"cvs commit\" log information\n", - "# is sent. The first entry on a line is a regular expression which is tested\n", - "# against the directory that the change is being made to, relative to the\n", - "# $CVSROOT. For the first match that is found, then the remainder of the\n", - "# line is a filter program that should expect log information on its standard\n", - "# input.\n", + "# The \"loginfo\" file controls where \"cvs commit\" log information\n", + "# is sent. The first entry on a line is a regular expression which must match\n", + "# the directory that the change is being made to, relative to the\n", + "# $CVSROOT. If a match is found, then the remainder of the line is a filter\n", + "# program that should expect log information on its standard input.\n", "#\n", - "# If the repository name does not match any of the regular expressions in the\n", - "# first field of this file, the \"DEFAULT\" line is used, if it is specified.\n", + "# If the repository name does not match any of the regular expressions in this\n", + "# file, the \"DEFAULT\" line is used, if it is specified.\n", "#\n", - "# If the name \"ALL\" appears as a regular expression it is always used\n", - "# in addition to the first matching regex or \"DEFAULT\".\n", + "# If the name ALL appears as a regular expression it is always used\n", + "# in addition to the first matching regex or DEFAULT.\n", "#\n", - "# The filter program may use one and only one \"%s\" modifier (ala printf). If\n", - "# such a \"%s\" is specified in the filter program, a brief title is included\n", - "# (as one argument, enclosed in single quotes) showing the relative directory\n", - "# name and listing the modified file names.\n", + "# You may specify a format string as part of the\n", + "# filter. The string is composed of a `%' followed\n", + "# by a single format character, or followed by a set of format\n", + "# characters surrounded by `{' and `}' as separators. The format\n", + "# characters are:\n", + "#\n", + "# s = file name\n", + "# V = old version number (pre-checkin)\n", + "# v = new version number (post-checkin)\n", "#\n", "# For example:\n", - "#DEFAULT (echo \"\"; who am i; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", + "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", + "# or\n", + "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", NULL }; @@ -99,8 +112,33 @@ static const char *const editinfo_contents[] = { "# Actions such as mailing a copy of the report to each reviewer are\n", "# better handled by an entry in the loginfo file.\n", "#\n", - "# One thing that should be noted is the the ALL keyword is not\n", - "# supported. There can be only one entry that matches a given\n", + "# One thing that should be noted is the the ALL keyword is not\n", + "# supported. There can be only one entry that matches a given\n", + "# repository.\n", + NULL +}; + +static const char *const verifymsg_contents[] = { + "# The \"verifymsg\" file is used to allow verification of logging\n", + "# information. It works best when a template (as specified in the\n", + "# rcsinfo file) is provided for the logging procedure. Given a\n", + "# template with locations for, a bug-id number, a list of people who\n", + "# reviewed the code before it can be checked in, and an external\n", + "# process to catalog the differences that were code reviewed, the\n", + "# following test can be applied to the code:\n", + "#\n", + "# Making sure that the entered bug-id number is correct.\n", + "# Validating that the code that was reviewed is indeed the code being\n", + "# checked in (using the bug-id number or a seperate review\n", + "# number to identify this particular code set.).\n", + "#\n", + "# If any of the above test failed, then the commit would be aborted.\n", + "#\n", + "# Actions such as mailing a copy of the report to each reviewer are\n", + "# better handled by an entry in the loginfo file.\n", + "#\n", + "# One thing that should be noted is the the ALL keyword is not\n", + "# supported. There can be only one entry that matches a given\n", "# repository.\n", NULL }; @@ -222,6 +260,9 @@ static const char *const modules_contents[] = { "# -d dir Place module in directory \"dir\" instead of module name.\n", "# -l Top-level directory only -- do not recurse.\n", "#\n", + "# NOTE: If you change any of the \"Run\" options above, you'll have to\n", + "# release and re-checkout any working directories of these modules.\n", + "#\n", "# And \"directory\" is a path to a directory relative to $CVSROOT.\n", "#\n", "# The \"-a\" option specifies an alias. An alias is interpreted as if\n", @@ -244,6 +285,9 @@ static const struct admin_file filelist[] = { {CVSROOTADM_EDITINFO, "a %s file can be used to validate log messages", editinfo_contents}, + {CVSROOTADM_VERIFYMSG, + "a %s file can be used to validate log messages", + verifymsg_contents}, {CVSROOTADM_COMMITINFO, "a %s file can be used to configure 'cvs commit' checking", commitinfo_contents}, @@ -266,6 +310,20 @@ static const struct admin_file filelist[] = { /* modules is special-cased in mkmodules. */ NULL, modules_contents}, + {CVSROOTADM_READERS, + "a %s file specifies read-only users", + NULL}, + {CVSROOTADM_WRITERS, + "a %s file specifies read/write users", + NULL}, + /* Some have suggested listing CVSROOTADM_PASSWD here too. The + security implications of transmitting hashed passwords over the + net are no worse than transmitting cleartext passwords which pserver + does, so this isn't a problem. But I'm worried about the implications + of storing old passwords--if someone used a password in the past + they might be using it elsewhere, using a similar password, etc, + and so it doesn't seem to me like we should be saving old passwords, + even hashed. */ {NULL, NULL} }; @@ -275,27 +333,26 @@ mkmodules (dir) char *dir; { struct saved_cwd cwd; - /* FIXME: arbitrary limit */ - char temp[PATH_MAX]; + char *temp; char *cp, *last, *fname; #ifdef MY_NDBM DBM *db; #endif FILE *fp; - /* FIXME: arbitrary limit */ - char line[512]; + char *line = NULL; + size_t line_allocated = 0; const struct admin_file *fileptr; if (save_cwd (&cwd)) - exit (EXIT_FAILURE); + error_exit (); - if (chdir (dir) < 0) + if ( CVS_CHDIR (dir) < 0) error (1, errno, "cannot chdir to %s", dir); /* * First, do the work necessary to update the "modules" database. */ - make_tempfile (temp); + temp = make_tempfile (); switch (checkout_file (CVSROOTADM_MODULES, temp)) { @@ -313,7 +370,7 @@ mkmodules (dir) case -1: /* fork failed */ (void) unlink_file (temp); - exit (EXIT_FAILURE); + error (1, errno, "cannot check out %s", CVSROOTADM_MODULES); /* NOTREACHED */ default: @@ -324,12 +381,13 @@ mkmodules (dir) } /* switch on checkout_file() */ (void) unlink_file (temp); + free (temp); /* Checkout the files that need it in CVSROOT dir */ for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) { if (fileptr->errormsg == NULL) continue; - make_tempfile (temp); + temp = make_tempfile (); if (checkout_file (fileptr->filename, temp) == 0) rename_rcsfile (temp, fileptr->filename); #if 0 @@ -344,10 +402,10 @@ mkmodules (dir) error (0, 0, fileptr->errormsg, fileptr->filename); #endif (void) unlink_file (temp); + free (temp); } - /* Use 'fopen' instead of 'open_file' because we want to ignore error */ - fp = fopen (CVSROOTADM_CHECKOUTLIST, "r"); + fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r"); if (fp) { /* @@ -356,7 +414,7 @@ mkmodules (dir) * * comment lines begin with '#' */ - while (fgets (line, sizeof (line), fp) != NULL) + while (getline (&line, &line_allocated, fp) >= 0) { /* skip lines starting with # */ if (line[0] == '#') @@ -374,7 +432,7 @@ mkmodules (dir) ; *cp = '\0'; - make_tempfile (temp); + temp = make_tempfile (); if (checkout_file (fname, temp) == 0) { rename_rcsfile (temp, fname); @@ -386,12 +444,24 @@ mkmodules (dir) if (cp < last && *cp) error (0, 0, cp, fname); } + free (temp); } - (void) fclose (fp); + if (line) + free (line); + if (ferror (fp)) + error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST); + } + else + { + /* Error from CVS_FOPEN. */ + if (!existence_error (errno)) + error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST); } if (restore_cwd (&cwd, NULL)) - exit (EXIT_FAILURE); + error_exit (); free_cwd (&cwd); return (0); @@ -400,25 +470,27 @@ mkmodules (dir) /* * Yeah, I know, there are NFS race conditions here. */ -static void -make_tempfile (temp) - char *temp; +static char * +make_tempfile () { static int seed = 0; int fd; + char *temp; if (seed == 0) seed = getpid (); + temp = xmalloc (sizeof (BAKPREFIX) + 40); while (1) { (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); - if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) + if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) break; if (errno != EEXIST) error (1, errno, "cannot create temporary file %s", temp); } if (close(fd) < 0) error(1, errno, "cannot close temporary file %s", temp); + return temp; } static int @@ -426,18 +498,31 @@ checkout_file (file, temp) char *file; char *temp; { - char rcs[PATH_MAX]; + char *rcs; + RCSNode *rcsnode; int retcode = 0; - (void) sprintf (rcs, "%s%s", file, RCSEXT); + if (noexec) + return 0; + + rcs = xmalloc (strlen (file) + 5); + strcpy (rcs, file); + strcat (rcs, RCSEXT); if (!isfile (rcs)) + { + free (rcs); return (1); - run_setup ("%s%s -x,v/ -q -p", Rcsbin, RCS_CO); - run_arg (rcs); - if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0) + } + rcsnode = RCS_parsercsfile (rcs); + retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp, + (RCSCHECKOUTPROC) NULL, (void *) NULL); + if (retcode != 0) { - error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file); + error (0, retcode == -1 ? errno : 0, "failed to check out %s file", + file); } + freercsnode (&rcsnode); + free (rcs); return (retcode); } @@ -568,12 +653,12 @@ rename_dbmfile (temp) (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */ (void) unlink_file (bakpag); (void) unlink_file (bakdb); - (void) rename (dotdir, bakdir); /* mv modules.dir .#modules.dir */ - (void) rename (dotpag, bakpag); /* mv modules.pag .#modules.pag */ - (void) rename (dotdb, bakdb); /* mv modules.db .#modules.db */ - (void) rename (newdir, dotdir); /* mv "temp".dir modules.dir */ - (void) rename (newpag, dotpag); /* mv "temp".pag modules.pag */ - (void) rename (newdb, dotdb); /* mv "temp".db modules.db */ + (void) CVS_RENAME (dotdir, bakdir); /* mv modules.dir .#modules.dir */ + (void) CVS_RENAME (dotpag, bakpag); /* mv modules.pag .#modules.pag */ + (void) CVS_RENAME (dotdb, bakdb); /* mv modules.db .#modules.db */ + (void) CVS_RENAME (newdir, dotdir); /* mv "temp".dir modules.dir */ + (void) CVS_RENAME (newpag, dotpag); /* mv "temp".pag modules.pag */ + (void) CVS_RENAME (newdb, dotdb); /* mv "temp".db modules.db */ /* OK -- make my day */ SIG_endCrSect (); @@ -586,21 +671,25 @@ rename_rcsfile (temp, real) char *temp; char *real; { - char bak[50]; + char *bak; struct stat statbuf; - char rcs[PATH_MAX]; - + char *rcs; + /* Set "x" bits if set in original. */ + rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10); (void) sprintf (rcs, "%s%s", real, RCSEXT); statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */ - (void) stat (rcs, &statbuf); + (void) CVS_STAT (rcs, &statbuf); + free (rcs); if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0) error (0, errno, "warning: cannot chmod %s", temp); + bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10); (void) sprintf (bak, "%s%s", BAKPREFIX, real); (void) unlink_file (bak); /* rm .#loginfo */ - (void) rename (real, bak); /* mv loginfo .#loginfo */ - (void) rename (temp, real); /* mv "temp" loginfo */ + (void) CVS_RENAME (real, bak); /* mv loginfo .#loginfo */ + (void) CVS_RENAME (temp, real); /* mv "temp" loginfo */ + free (bak); } const char *const init_usage[] = { @@ -608,26 +697,6 @@ const char *const init_usage[] = { NULL }; -/* Create directory NAME if it does not already exist; fatal error for - other errors. FIXME: This should be in filesubr.c or thereabouts, - probably. Perhaps it should be further abstracted, though (for example - to handle CVSUMASK where appropriate?). */ -static void -mkdir_if_needed (name) - char *name; -{ - if (CVS_MKDIR (name, 0777) < 0) - { - if (errno != EEXIST -#ifdef EACCESS - /* OS/2; see longer comment in client.c. */ - && errno != EACCESS -#endif - ) - error (1, errno, "cannot mkdir %s", name); - } -} - int init (argc, argv) int argc; @@ -647,6 +716,7 @@ init (argc, argv) if (argc == -1 || argc > 1) usage (init_usage); +#ifdef CLIENT_SUPPORT if (client_active) { start_server (); @@ -655,21 +725,22 @@ init (argc, argv) send_init_command (); return get_responses_and_close (); } +#endif /* CLIENT_SUPPORT */ /* Note: we do *not* create parent directories as needed like the old cvsinit.sh script did. Few utilities do that, and a non-existent parent directory is as likely to be a typo as something which needs to be created. */ - mkdir_if_needed (CVSroot); + mkdir_if_needed (CVSroot_directory); - adm = xmalloc (strlen (CVSroot) + sizeof (CVSROOTADM) + 10); - strcpy (adm, CVSroot); + adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10); + strcpy (adm, CVSroot_directory); strcat (adm, "/"); strcat (adm, CVSROOTADM); mkdir_if_needed (adm); /* This is needed by the call to "ci" below. */ - if (chdir (adm) < 0) + if ( CVS_CHDIR (adm) < 0) error (1, errno, "cannot change to directory %s", adm); /* 80 is long enough for all the administrative file names, plus diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c index c4be6fd..1f73c82 100644 --- a/contrib/cvs/src/rcs.c +++ b/contrib/cvs/src/rcs.c @@ -11,13 +11,35 @@ #include <assert.h> #include "cvs.h" +/* The RCS -k options, and a set of enums that must match the array. + These come first so that we can use enum kflag in function + prototypes. */ +static const char *const kflags[] = + {"kv", "kvl", "k", "v", "o", "b", (char *) NULL}; +enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B }; + static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile)); +static void RCS_reparsercsfile PROTO((RCSNode *, int, FILE **)); static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch)); -static int getrcskey PROTO((FILE * fp, char **keyp, char **valp)); +static int getrcskey PROTO((FILE * fp, char **keyp, char **valp, + size_t *lenp)); +static void getrcsrev PROTO ((FILE *fp, char **revp)); static int checkmagic_proc PROTO((Node *p, void *closure)); static void do_branches PROTO((List * list, char *val)); static void do_symbols PROTO((List * list, char *val)); +static void free_rcsnode_contents PROTO((RCSNode *)); static void rcsvers_delproc PROTO((Node * p)); +static char *translate_symtag PROTO((RCSNode *, const char *)); +static char *printable_date PROTO((const char *)); +static char *escape_keyword_value PROTO ((const char *, int *)); +static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *, + const char *, size_t, enum kflag, char *, + size_t, char **, size_t *)); +static void cmp_file_buffer PROTO((void *, const char *, size_t)); + +enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH}; +static void RCS_deltas PROTO ((RCSNode *, FILE *, char *, enum rcs_delta_op, + char **, size_t *, char **, size_t *)); /* * We don't want to use isspace() from the C library because: @@ -49,9 +71,11 @@ static const char spacetab[] = { #define whitespace(c) (spacetab[(unsigned char)c] != 0) -/* - * Parse an rcsfile given a user file name and a repository - */ +/* Parse an rcsfile given a user file name and a repository. If there is + an error, we print an error message and return NULL. If the file + does not exist, we return NULL without printing anything (I'm not + sure this allows the caller to do anything reasonable, but it is + the current behavior). */ RCSNode * RCS_parse (file, repos) const char *file; @@ -59,26 +83,31 @@ RCS_parse (file, repos) { RCSNode *rcs; FILE *fp; - char rcsfile[PATH_MAX]; + RCSNode *retval; + char *rcsfile; + rcsfile = xmalloc (strlen (repos) + strlen (file) + + sizeof (RCSEXT) + sizeof (CVSATTIC) + 10); (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); - if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL) { rcs = RCS_parsercsfile_i(fp, rcsfile); if (rcs != NULL) rcs->flags |= VALID; fclose (fp); - return (rcs); + retval = rcs; + goto out; } else if (! existence_error (errno)) { error (0, errno, "cannot open %s", rcsfile); - return NULL; + retval = NULL; + goto out; } (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT); - if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL) { rcs = RCS_parsercsfile_i(fp, rcsfile); if (rcs != NULL) @@ -88,15 +117,78 @@ RCS_parse (file, repos) } fclose (fp); - return (rcs); + retval = rcs; + goto out; } else if (! existence_error (errno)) { error (0, errno, "cannot open %s", rcsfile); - return NULL; + retval = NULL; + goto out; } +#if defined (SERVER_SUPPORT) && !defined (FILENAMES_CASE_INSENSITIVE) + else if (ign_case) + { + int status; + char *found_path; + + /* The client might be asking for a file which we do have + (which the client doesn't know about), but for which the + filename case differs. We only consider this case if the + regular CVS_FOPENs fail, because fopen_case is such an + expensive call. */ + (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); + status = fopen_case (rcsfile, "rb", &fp, &found_path); + if (status == 0) + { + rcs = RCS_parsercsfile_i (fp, rcsfile); + if (rcs != NULL) + rcs->flags |= VALID; + + fclose (fp); + free (rcs->path); + rcs->path = found_path; + retval = rcs; + goto out; + } + else if (! existence_error (status)) + { + error (0, status, "cannot open %s", rcsfile); + retval = NULL; + goto out; + } - return (NULL); + (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT); + status = fopen_case (rcsfile, "rb", &fp, &found_path); + if (status == 0) + { + rcs = RCS_parsercsfile_i (fp, rcsfile); + if (rcs != NULL) + { + rcs->flags |= INATTIC; + rcs->flags |= VALID; + } + + fclose (fp); + free (rcs->path); + rcs->path = found_path; + retval = rcs; + goto out; + } + else if (! existence_error (status)) + { + error (0, status, "cannot open %s", rcsfile); + retval = NULL; + goto out; + } + } +#endif + retval = NULL; + + out: + free (rcsfile); + + return retval; } /* @@ -110,7 +202,7 @@ RCS_parsercsfile (rcsfile) RCSNode *rcs; /* open the rcsfile */ - if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) == NULL) + if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL) { error (0, errno, "Couldn't open rcs file `%s'", rcsfile); return (NULL); @@ -145,7 +237,7 @@ RCS_parsercsfile_i (fp, rcsfile) * information. Those that do call XXX to completely parse the * RCS file. */ - if (getrcskey (fp, &key, &value) == -1 || key == NULL) + if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) goto l_error; if (strcmp (key, RCSDESC) == 0) goto l_error; @@ -153,7 +245,7 @@ RCS_parsercsfile_i (fp, rcsfile) if (strcmp (RCSHEAD, key) == 0 && value != NULL) rdata->head = xstrdup (value); - if (getrcskey (fp, &key, &value) == -1 || key == NULL) + if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) goto l_error; if (strcmp (key, RCSDESC) == 0) goto l_error; @@ -196,11 +288,15 @@ l_error: On error, die with a fatal error; if it returns at all it was successful. + If ALL is nonzero, remember all keywords and values. Otherwise + only keep the ones we will need. + If PFP is NULL, close the file when done. Otherwise, leave it open and store the FILE * in *PFP. */ static void -RCS_reparsercsfile (rdata, pfp) +RCS_reparsercsfile (rdata, all, pfp) RCSNode *rdata; + int all; FILE **pfp; { FILE *fp; @@ -215,7 +311,7 @@ RCS_reparsercsfile (rdata, pfp) assert (rdata != NULL); rcsfile = rdata->path; - fp = fopen(rcsfile, FOPEN_BINARY_READ); + fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ); if (fp == NULL) error (1, 0, "unable to reopen `%s'", rcsfile); @@ -232,7 +328,7 @@ RCS_reparsercsfile (rdata, pfp) /* if key is NULL here, then the file is missing some headers or we had trouble reading the file. */ - if (getrcskey (fp, &key, &value) == -1 || key == NULL + if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL || strcmp (key, RCSDESC) == 0) { if (ferror(fp)) @@ -249,10 +345,8 @@ RCS_reparsercsfile (rdata, pfp) if (strcmp (RCSSYMBOLS, key) == 0) { if (value != NULL) - { rdata->symbols_data = xstrdup(value); - continue; - } + continue; } if (strcmp (RCSEXPAND, key) == 0) @@ -271,6 +365,28 @@ RCS_reparsercsfile (rdata, pfp) if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) break; + /* We always save lock information, so that we can handle + -kkvl correctly when checking out a file. We don't use a + special field for this information, since it will normally + not be set for a CVS file. */ + if (all || strcmp (key, "locks") == 0) + { + Node *kv; + + if (rdata->other == NULL) + rdata->other = getlist (); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (key); + kv->data = xstrdup (value); + if (addnode (rdata->other, kv) != 0) + { + error (0, 0, "warning: duplicate key `%s' in RCS file `%s'", + key, rcsfile); + freenode (kv); + } + } + /* if we haven't grabbed it yet, we didn't want it */ } @@ -297,7 +413,7 @@ RCS_reparsercsfile (rdata, pfp) vnode->date = xstrdup (valp); /* Get author field. */ - (void) getrcskey (fp, &key, &value); + (void) getrcskey (fp, &key, &value, NULL); /* FIXME: should be using errno in case of ferror. */ if (key == NULL || strcmp (key, "author") != 0) error (1, 0, "\ @@ -305,18 +421,19 @@ unable to parse rcs file; `author' not in the expected place"); vnode->author = xstrdup (value); /* Get state field. */ - (void) getrcskey (fp, &key, &value); + (void) getrcskey (fp, &key, &value, NULL); /* FIXME: should be using errno in case of ferror. */ if (key == NULL || strcmp (key, "state") != 0) error (1, 0, "\ unable to parse rcs file; `state' not in the expected place"); + vnode->state = xstrdup (value); if (strcmp (value, "dead") == 0) { vnode->dead = 1; } /* fill in the branch list (if any branches exist) */ - (void) getrcskey (fp, &key, &value); + (void) getrcskey (fp, &key, &value, NULL); /* FIXME: should be handling various error conditions better. */ if (key != NULL && strcmp (key, RCSDESC) == 0) value = NULL; @@ -327,7 +444,7 @@ unable to parse rcs file; `state' not in the expected place"); } /* fill in the next field if there is a next revision */ - (void) getrcskey (fp, &key, &value); + (void) getrcskey (fp, &key, &value, NULL); /* FIXME: should be handling various error conditions better. */ if (key != NULL && strcmp (key, RCSDESC) == 0) value = NULL; @@ -339,7 +456,7 @@ unable to parse rcs file; `state' not in the expected place"); * we put the symbolic link stuff??? */ /* FIXME: Does not correctly handle errors, e.g. from stdio. */ - while ((n = getrcskey (fp, &key, &value)) >= 0) + while ((n = getrcskey (fp, &key, &value, NULL)) >= 0) { assert (key != NULL); @@ -356,6 +473,9 @@ unable to parse rcs file; `state' not in the expected place"); if (strcmp(key, RCSDEAD) == 0) { vnode->dead = 1; + if (vnode->state != NULL) + free (vnode->state); + vnode->state = xstrdup ("dead"); continue; } /* if we have a revision, break and do it */ @@ -363,6 +483,26 @@ unable to parse rcs file; `state' not in the expected place"); /* do nothing */ ; if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) break; + + if (all) + { + Node *kv; + + if (vnode->other == NULL) + vnode->other = getlist (); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (key); + kv->data = xstrdup (value); + if (addnode (vnode->other, kv) != 0) + { + error (0, 0, + "\ +warning: duplicate key `%s' in version `%s' of RCS file `%s'", + key, vnode->version, rcsfile); + freenode (kv); + } + } } /* get the node */ @@ -390,6 +530,28 @@ unable to parse rcs file; `state' not in the expected place"); break; } + if (all && key != NULL && strcmp (key, RCSDESC) == 0) + { + Node *kv; + + if (rdata->other == NULL) + rdata->other = getlist (); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (key); + kv->data = xstrdup (value); + if (addnode (rdata->other, kv) != 0) + { + error (0, 0, + "warning: duplicate key `%s' in RCS file `%s'", + key, rcsfile); + freenode (kv); + } + } + + rdata->delta_pos = ftell (fp); + rdata->flags &= ~NODELTA; + if (pfp == NULL) { if (fclose (fp) < 0) @@ -403,6 +565,177 @@ unable to parse rcs file; `state' not in the expected place"); } /* + * Fully parse the RCS file. Store all keyword/value pairs, fetch the + * log messages for each revision, and fetch add and delete counts for + * each revision (we could fetch the entire text for each revision, + * but the only caller, log_fileproc, doesn't need that information, + * so we don't waste the memory required to store it). The add and + * delete counts are stored on the OTHER field of the RCSVERSNODE + * structure, under the names ";add" and ";delete", so that we don't + * waste the memory space of extra fields in RCSVERSNODE for code + * which doesn't need this information. + */ + +void +RCS_fully_parse (rcs) + RCSNode *rcs; +{ + FILE *fp; + + RCS_reparsercsfile (rcs, 1, &fp); + + while (1) + { + int c; + char *key, *value; + size_t vallen; + Node *vers; + RCSVers *vnode; + + /* Rather than try to keep track of how much information we + have read, just read to the end of the file. */ + do + { + c = getc (fp); + if (c == EOF) + break; + } while (whitespace (c)); + if (c == EOF) + break; + if (ungetc (c, fp) == EOF) + error (1, errno, "ungetc failed"); + + getrcsrev (fp, &key); + vers = findnode (rcs->versions, key); + if (vers == NULL) + error (1, 0, + "mismatch in rcs file %s between deltas and deltatexts", + rcs->path); + + vnode = (RCSVers *) vers->data; + + while (getrcskey (fp, &key, &value, &vallen) >= 0) + { + if (strcmp (key, "text") != 0) + { + Node *kv; + + if (vnode->other == NULL) + vnode->other = getlist (); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (key); + kv->data = xstrdup (value); + if (addnode (vnode->other, kv) != 0) + { + error (0, 0, + "\ +warning: duplicate key `%s' in version `%s' of RCS file `%s'", + key, vnode->version, rcs->path); + freenode (kv); + } + + continue; + } + + if (strcmp (vnode->version, rcs->head) != 0) + { + unsigned long add, del; + char buf[50]; + Node *kv; + + /* This is a change text. Store the add and delete + counts. */ + add = 0; + del = 0; + if (value != NULL) + { + const char *cp; + + cp = value; + while (cp < value + vallen) + { + char op; + unsigned long count; + + op = *cp++; + if (op != 'a' && op != 'd') + error (1, 0, "unrecognized operation '%c' in %s", + op, rcs->path); + (void) strtoul (cp, (char **) &cp, 10); + if (*cp++ != ' ') + error (1, 0, "space expected in %s", + rcs->path); + count = strtoul (cp, (char **) &cp, 10); + if (*cp++ != '\012') + error (1, 0, "linefeed expected in %s", + rcs->path); + + if (op == 'd') + del += count; + else + { + add += count; + while (count != 0) + { + if (*cp == '\012') + --count; + else if (cp == value + vallen) + { + if (count != 1) + error (1, 0, "\ +invalid rcs file %s: premature end of value", + rcs->path); + else + break; + } + ++cp; + } + } + } + } + + sprintf (buf, "%lu", add); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (";add"); + kv->data = xstrdup (buf); + if (addnode (vnode->other, kv) != 0) + { + error (0, 0, + "\ +warning: duplicate key `%s' in version `%s' of RCS file `%s'", + key, vnode->version, rcs->path); + freenode (kv); + } + + sprintf (buf, "%lu", del); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (";delete"); + kv->data = xstrdup (buf); + if (addnode (vnode->other, kv) != 0) + { + error (0, 0, + "\ +warning: duplicate key `%s' in version `%s' of RCS file `%s'", + key, vnode->version, rcs->path); + freenode (kv); + } + } + + /* We have found the "text" key which ends the data for + this revision. Break out of the loop and go on to the + next revision. */ + break; + } + } + + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", rcs->path); +} + +/* * freercsnode - free up the info for an RCSNode */ void @@ -419,22 +752,37 @@ freercsnode (rnodep) return; } free ((*rnodep)->path); - dellist (&(*rnodep)->versions); - if ((*rnodep)->symbols != (List *) NULL) - dellist (&(*rnodep)->symbols); - if ((*rnodep)->symbols_data != (char *) NULL) - free ((*rnodep)->symbols_data); - if ((*rnodep)->expand != NULL) - free ((*rnodep)->expand); if ((*rnodep)->head != (char *) NULL) free ((*rnodep)->head); if ((*rnodep)->branch != (char *) NULL) free ((*rnodep)->branch); + free_rcsnode_contents (*rnodep); free ((char *) *rnodep); *rnodep = (RCSNode *) NULL; } /* + * free_rcsnode_contents - free up the contents of an RCSNode without + * freeing the node itself, or the file name, or the head, or the + * path. This returns the RCSNode to the state it is in immediately + * after a call to RCS_parse. + */ +static void +free_rcsnode_contents (rnode) + RCSNode *rnode; +{ + dellist (&rnode->versions); + if (rnode->symbols != (List *) NULL) + dellist (&rnode->symbols); + if (rnode->symbols_data != (char *) NULL) + free (rnode->symbols_data); + if (rnode->expand != NULL) + free (rnode->expand); + if (rnode->other != (List *) NULL) + dellist (&rnode->other); +} + +/* * rcsvers_delproc - free up an RCSVers type node */ static void @@ -451,6 +799,12 @@ rcsvers_delproc (p) free (rnode->date); if (rnode->next != (char *) NULL) free (rnode->next); + if (rnode->author != (char *) NULL) + free (rnode->author); + if (rnode->state != (char *) NULL) + free (rnode->state); + if (rnode->other != (List *) NULL) + dellist (&rnode->other); free ((char *) rnode); } @@ -469,8 +823,10 @@ rcsvers_delproc (p) * o return 0 since we found something besides "desc" * * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey - * function; the contents are only valid until the next call to getrcskey - * or getrcsrev. + * function; the contents are only valid until the next call to + * getrcskey or getrcsrev. If LENP is not NULL, this sets *LENP to + * the length of *VALUEP; this is needed if the string might contain + * binary data. */ static char *key = NULL; @@ -478,16 +834,19 @@ static char *value = NULL; static size_t keysize = 0; static size_t valsize = 0; -#define ALLOCINCR 1024 - static int -getrcskey (fp, keyp, valp) +getrcskey (fp, keyp, valp, lenp) FILE *fp; char **keyp; char **valp; + size_t *lenp; { char *cur, *max; int c; + int just_string; + + if (lenp != NULL) + *lenp = 0; /* skip leading whitespace */ do @@ -508,9 +867,9 @@ getrcskey (fp, keyp, valp) { if (cur >= max) { - key = xrealloc (key, keysize + ALLOCINCR); - cur = key + keysize; - keysize += ALLOCINCR; + size_t curoff = cur - key; + expand_string (&key, &keysize, keysize + 1); + cur = key + curoff; max = key + keysize; } *cur++ = c; @@ -525,9 +884,9 @@ getrcskey (fp, keyp, valp) } if (cur >= max) { - key = xrealloc (key, keysize + ALLOCINCR); - cur = key + keysize; - keysize += ALLOCINCR; + size_t curoff = cur - key; + expand_string (&key, &keysize, keysize + 1); + cur = key + curoff; max = key + keysize; } *cur = '\0'; @@ -556,6 +915,10 @@ getrcskey (fp, keyp, valp) cur = value; max = value + valsize; + just_string = (strcmp (key, RCSDESC) == 0 + || strcmp (key, "text") == 0 + || strcmp (key, "log") == 0); + /* process the value */ for (;;) { @@ -588,9 +951,9 @@ getrcskey (fp, keyp, valp) if (cur >= max) { - value = xrealloc (value, valsize + ALLOCINCR); - cur = value + valsize; - valsize += ALLOCINCR; + size_t curoff = cur - value; + expand_string (&value, &valsize, valsize + 1); + cur = value + curoff; max = value + valsize; } *cur++ = c; @@ -599,9 +962,7 @@ getrcskey (fp, keyp, valp) /* The syntax for some key-value pairs is different; they don't end with a semicolon. */ - if (strcmp (key, RCSDESC) == 0 - || strcmp (key, "text") == 0 - || strcmp (key, "log") == 0) + if (just_string) break; /* compress whitespace down to a single space */ @@ -619,9 +980,9 @@ getrcskey (fp, keyp, valp) if (cur >= max) { - value = xrealloc (value, valsize + ALLOCINCR); - cur = value + valsize; - valsize += ALLOCINCR; + size_t curoff = cur - value; + expand_string (&value, &valsize, valsize + 1); + cur = value + curoff; max = value + valsize; } *cur++ = ' '; @@ -633,9 +994,9 @@ getrcskey (fp, keyp, valp) if (cur >= max) { - value = xrealloc (value, valsize + ALLOCINCR); - cur = value + valsize; - valsize += ALLOCINCR; + size_t curoff = cur - value; + expand_string (&value, &valsize, valsize + 1); + cur = value + curoff; max = value + valsize; } *cur++ = c; @@ -652,24 +1013,26 @@ getrcskey (fp, keyp, valp) /* terminate the string */ if (cur >= max) { - value = xrealloc (value, valsize + ALLOCINCR); - cur = value + valsize; - valsize += ALLOCINCR; + size_t curoff = cur - value; + expand_string (&value, &valsize, valsize + 1); + cur = value + curoff; max = value + valsize; } *cur = '\0'; /* if the string is empty, make it null */ - if (value && *value != '\0') + if (value && cur != value) + { *valp = value; + if (lenp != NULL) + *lenp = cur - value; + } else *valp = NULL; *keyp = key; return (0); } -static void getrcsrev PROTO ((FILE *fp, char **revp)); - /* Read an RCS revision number from FP. Put a pointer to it in *REVP; it points to space managed by getrcsrev which is only good until the next call to getrcskey or getrcsrev. */ @@ -699,9 +1062,9 @@ getrcsrev (fp, revp) { if (cur >= max) { - key = xrealloc (key, keysize + ALLOCINCR); - cur = key + keysize; - keysize += ALLOCINCR; + size_t curoff = cur - key; + expand_string (&key, &keysize, keysize + 1); + cur = key + curoff; max = key + keysize; } *cur++ = c; @@ -716,9 +1079,9 @@ getrcsrev (fp, revp) if (cur >= max) { - key = xrealloc (key, keysize + ALLOCINCR); - cur = key + keysize; - keysize += ALLOCINCR; + size_t curoff = cur - key; + expand_string (&key, &keysize, keysize + 1); + cur = key + curoff; max = key + keysize; } *cur = '\0'; @@ -810,36 +1173,43 @@ do_branches (list, val) * The result is returned; null-string if error. */ char * -RCS_getversion (rcs, tag, date, force_tag_match, return_both) +RCS_getversion (rcs, tag, date, force_tag_match, simple_tag) RCSNode *rcs; char *tag; char *date; int force_tag_match; - int return_both; + int *simple_tag; { + if (simple_tag != NULL) + *simple_tag = 0; + /* make sure we have something to look at... */ assert (rcs != NULL); if (tag && date) { - char *cp, *rev, *tagrev; + char *branch, *rev; - /* - * first lookup the tag; if that works, turn the revision into - * a branch and lookup the date. - */ - tagrev = RCS_gettag (rcs, tag, force_tag_match, 0); - if (tagrev == NULL) - return ((char *) NULL); + if (! RCS_isbranch (rcs, tag)) + { + /* We can't get a particular date if the tag is not a + branch. */ + return NULL; + } - if ((cp = strrchr (tagrev, '.')) != NULL) - *cp = '\0'; - rev = RCS_getdatebranch (rcs, date, tagrev); - free (tagrev); + /* Work out the branch. */ + if (! isdigit (tag[0])) + branch = RCS_whatbranch (rcs, tag); + else + branch = xstrdup (tag); + + /* Fetch the revision of branch as of date. */ + rev = RCS_getdatebranch (rcs, date, branch); + free (branch); return (rev); } else if (tag) - return (RCS_gettag (rcs, tag, force_tag_match, return_both)); + return (RCS_gettag (rcs, tag, force_tag_match, simple_tag)); else if (date) return (RCS_getdate (rcs, date, force_tag_match)); else @@ -856,21 +1226,24 @@ RCS_getversion (rcs, tag, date, force_tag_match, return_both) * If the matched tag is a branch tag, find the head of the branch. */ char * -RCS_gettag (rcs, symtag, force_tag_match, return_both) +RCS_gettag (rcs, symtag, force_tag_match, simple_tag) RCSNode *rcs; char *symtag; int force_tag_match; - int return_both; + int *simple_tag; { - Node *p; char *tag = symtag; + int tag_allocated = 0; + + if (simple_tag != NULL) + *simple_tag = 0; /* make sure we have something to look at... */ assert (rcs != NULL); /* XXX this is probably not necessary, --jtc */ if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* If tag is "HEAD", special case to get head RCS revision */ if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0')) @@ -883,18 +1256,17 @@ RCS_gettag (rcs, symtag, force_tag_match, return_both) if (!isdigit (tag[0])) { + char *version; + /* If we got a symbolic tag, resolve it to a numeric */ - if (rcs == NULL) - p = NULL; - else { - p = findnode (RCS_symbols(rcs), tag); - } - if (p != NULL) + version = translate_symtag (rcs, tag); + if (version != NULL) { int dots; char *magic, *branch, *cp; - tag = p->data; + tag = version; + tag_allocated = 1; /* * If this is a magic revision, we turn it into either its @@ -914,21 +1286,17 @@ RCS_gettag (rcs, symtag, force_tag_match, return_both) (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); if (strncmp (magic, cp, strlen (magic)) == 0) { - char *xtag; - /* it's magic. See if the branch exists */ *cp = '\0'; /* turn it into a revision */ - xtag = xstrdup (tag); - *cp = '.'; /* and back again */ - (void) sprintf (magic, "%s.%s", xtag, branch); + (void) sprintf (magic, "%s.%s", tag, branch); branch = RCS_getbranch (rcs, magic, 1); free (magic); if (branch != NULL) { - free (xtag); + free (tag); return (branch); } - return (xtag); + return (tag); } free (magic); } @@ -955,39 +1323,40 @@ RCS_gettag (rcs, symtag, force_tag_match, return_both) if ((numdots (tag) & 1) == 0) { + char *branch; + /* we have a branch tag, so we need to walk the branch */ - return (RCS_getbranch (rcs, tag, force_tag_match)); + branch = RCS_getbranch (rcs, tag, force_tag_match); + if (tag_allocated) + free (tag); + return branch; } else { + Node *p; + /* we have a revision tag, so make sure it exists */ - if (rcs == NULL) - p = NULL; - else - p = findnode (rcs->versions, tag); + p = findnode (rcs->versions, tag); if (p != NULL) { - /* - * we have found a numeric revision for the revision tag. - * To support expanding the RCS keyword Name, return both - * the numeric tag and the supplied tag (which might be - * symbolic). They are separated with a ':' which is not - * a valid tag char. The variable return_both is only set - * if this function is called through Version_TS -> - * RCS_getversion. - */ - if (return_both) - { - char *both = xmalloc(strlen(tag) + 2 + strlen(symtag)); - sprintf(both, "%s:%s", tag, symtag); - return both; - } - else - return (xstrdup (tag)); + /* We have found a numeric revision for the revision tag. + To support expanding the RCS keyword Name, if + SIMPLE_TAG is not NULL, tell the the caller that this + is a simple tag which co will recognize. FIXME: Are + there other cases in which we should set this? In + particular, what if we expand RCS keywords internally + without calling co? */ + if (simple_tag != NULL) + *simple_tag = 1; + if (! tag_allocated) + tag = xstrdup (tag); + return (tag); } else { /* The revision wasn't there, so return the head or NULL */ + if (tag_allocated) + free (tag); if (force_tag_match) return (NULL); else @@ -1116,37 +1485,42 @@ RCS_nodeisbranch (rcs, rev) const char *rev; { int dots; - Node *p; + char *version; /* numeric revisions are easy -- even number of dots is a branch */ if (isdigit (*rev)) return ((numdots (rev) & 1) == 0); - p = findnode (RCS_symbols(rcs), rev); - if (p == NULL) + version = translate_symtag (rcs, rev); + if (version == NULL) return (0); - dots = numdots (p->data); + dots = numdots (version); if ((dots & 1) == 0) + { + free (version); return (1); + } /* got a symbolic tag match, but it's not a branch; see if it's magic */ if (dots > 2) { char *magic; - char *branch = strrchr (p->data, '.'); + char *branch = strrchr (version, '.'); char *cp = branch - 1; while (*cp != '.') cp--; /* see if we have .magic-branch. (".0.") */ - magic = xmalloc (strlen (p->data) + 1); + magic = xmalloc (strlen (version) + 1); (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); if (strncmp (magic, cp, strlen (magic)) == 0) { free (magic); + free (version); return (1); } free (magic); + free (version); } return (0); } @@ -1160,7 +1534,7 @@ RCS_whatbranch (rcs, rev) RCSNode *rcs; const char *rev; { - Node *p; + char *version; int dots; /* assume no branch if you can't find the RCS info */ @@ -1168,34 +1542,35 @@ RCS_whatbranch (rcs, rev) return ((char *) NULL); /* now, look for a match in the symbols list */ - p = findnode (RCS_symbols(rcs), rev); - if (p == NULL) + version = translate_symtag (rcs, rev); + if (version == NULL) return ((char *) NULL); - dots = numdots (p->data); + dots = numdots (version); if ((dots & 1) == 0) - return (xstrdup (p->data)); + return (version); /* got a symbolic tag match, but it's not a branch; see if it's magic */ if (dots > 2) { char *magic; - char *branch = strrchr (p->data, '.'); + char *branch = strrchr (version, '.'); char *cp = branch++ - 1; while (*cp != '.') cp--; /* see if we have .magic-branch. (".0.") */ - magic = xmalloc (strlen (p->data) + 1); + magic = xmalloc (strlen (version) + 1); (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); if (strncmp (magic, cp, strlen (magic)) == 0) { /* yep. it's magic. now, construct the real branch */ *cp = '\0'; /* turn it into a revision */ - (void) sprintf (magic, "%s.%s", p->data, branch); - *cp = '.'; /* and turn it back */ + (void) sprintf (magic, "%s.%s", version, branch); + free (version); return (magic); } free (magic); + free (version); } return ((char *) NULL); } @@ -1220,7 +1595,7 @@ RCS_getbranch (rcs, tag, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* find out if the tag contains a dot, or is on the trunk */ cp = strrchr (tag, '.'); @@ -1359,7 +1734,7 @@ RCS_getdate (rcs, date, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* if the head is on a branch, try the branch first */ if (rcs->branch != NULL) @@ -1446,7 +1821,7 @@ RCS_getdatebranch (rcs, date, branch) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); p = findnode (rcs->versions, xrev); free (xrev); @@ -1454,9 +1829,13 @@ RCS_getdatebranch (rcs, date, branch) return (NULL); vers = (RCSVers *) p->data; - /* if no branches list, return NULL */ + /* Tentatively use this revision, if it is early enough. */ + if (RCS_datecmp (vers->date, date) <= 0) + cur_rev = vers->version; + + /* if no branches list, return now */ if (vers->branches == NULL) - return (NULL); + return xstrdup (cur_rev); /* walk the branches list looking for the branch number */ xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */ @@ -1467,7 +1846,11 @@ RCS_getdatebranch (rcs, date, branch) break; free (xbranch); if (p == vers->branches->list) + { + /* FIXME: This case would seem to imply that the RCS file is + somehow invalid. Should we give an error message? */ return (NULL); + } p = findnode (rcs->versions, p->key); @@ -1487,11 +1870,8 @@ RCS_getdatebranch (rcs, date, branch) p = (Node *) NULL; } - /* if we found something acceptable, return it - otherwise NULL */ - if (cur_rev != NULL) - return (xstrdup (cur_rev)); - else - return (NULL); + /* Return whatever we found, which may be NULL. */ + return xstrdup (cur_rev); } /* @@ -1507,13 +1887,17 @@ RCS_datecmp (date1, date2) return (length_diff ? length_diff : strcmp (date1, date2)); } -/* - * Lookup the specified revision in the ,v file and return, in the date - * argument, the date specified for the revision *minus one second*, so that - * the logically previous revision will be found later. - * - * Returns zero on failure, RCS revision time as a Unix "time_t" on success. - */ +/* Look up revision REV in RCS and return the date specified for the + revision minus FUDGE seconds (FUDGE will generally be one, so that the + logically previous revision will be found later, or zero, if we want + the exact date). + + The return value is the date being returned as a time_t, or (time_t)-1 + on error (previously was documented as zero on error; I haven't checked + the callers to make sure that they really check for (time_t)-1, but + the latter is what this function really returns). If DATE is non-NULL, + then it must point to MAXDATELEN characters, and we store the same + return value there in DATEFORM format. */ time_t RCS_getrevtime (rcs, rev, date, fudge) RCSNode *rcs; @@ -1531,7 +1915,7 @@ RCS_getrevtime (rcs, rev, date, fudge) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); /* look up the revision */ p = findnode (rcs->versions, rev); @@ -1556,11 +1940,11 @@ RCS_getrevtime (rcs, rev, date, fudge) /* put the date in a form getdate can grok */ #ifdef HAVE_RCS5 (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon, - ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_mday, ftm->tm_year + 1900, ftm->tm_hour, ftm->tm_min, ftm->tm_sec); #else (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon, - ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_mday, ftm->tm_year + 1900, ftm->tm_hour, ftm->tm_min, ftm->tm_sec); #endif @@ -1593,7 +1977,7 @@ RCS_symbols(rcs) assert(rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); if (rcs->symbols_data) { rcs->symbols = getlist (); @@ -1606,6 +1990,69 @@ RCS_symbols(rcs) } /* + * Return the version associated with a particular symbolic tag. + */ +static char * +translate_symtag (rcs, tag) + RCSNode *rcs; + const char *tag; +{ + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, 0, NULL); + + if (rcs->symbols != NULL) + { + Node *p; + + /* The symbols have already been converted into a list. */ + p = findnode (rcs->symbols, tag); + if (p == NULL) + return NULL; + + return xstrdup (p->data); + } + + if (rcs->symbols_data != NULL) + { + size_t len; + char *cp; + + /* Look through the RCS symbols information. This is like + do_symbols, but we don't add the information to a list. In + most cases, we will only be called once for this file, so + generating the list is unnecessary overhead. */ + + len = strlen (tag); + cp = rcs->symbols_data; + while ((cp = strchr (cp, tag[0])) != NULL) + { + if ((cp == rcs->symbols_data || whitespace (cp[-1])) + && strncmp (cp, tag, len) == 0 + && cp[len] == ':') + { + char *v, *r; + + /* We found the tag. Return the version number. */ + + cp += len + 1; + v = cp; + while (! whitespace (*cp) && *cp != '\0') + ++cp; + r = xmalloc (cp - v + 1); + strncpy (r, v, cp - v); + r[cp - v] = '\0'; + return r; + } + + while (! whitespace (*cp) && *cp != '\0') + ++cp; + } + } + + return NULL; +} + +/* * The argument ARG is the getopt remainder of the -k option specified on the * command line. This function returns malloc'ed space that can be used * directly in calls to RCS V5, with the -k flag munged correctly. @@ -1614,8 +2061,6 @@ char * RCS_check_kflag (arg) const char *arg; { - static const char *const kflags[] = - {"kv", "kvl", "k", "v", "o", "b", (char *) NULL}; static const char *const keyword_usage[] = { "%s %s: invalid RCS keyword expansion mode\n", @@ -1628,6 +2073,7 @@ RCS_check_kflag (arg) " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n", NULL, }; + /* Big enough to hold any of the strings from kflags. */ char karg[10]; char const *const *cpp = NULL; @@ -1698,7 +2144,7 @@ RCS_isdead (rcs, tag) RCSVers *version; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); p = findnode (rcs->versions, tag); if (p == NULL) @@ -1719,13 +2165,1200 @@ RCS_getexpand (rcs) { assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, 0, NULL); return rcs->expand; } + +/* RCS keywords, and a matching enum. */ +struct rcs_keyword +{ + const char *string; + size_t len; +}; +#define KEYWORD_INIT(s) (s), sizeof (s) - 1 +static const struct rcs_keyword keywords[] = +{ + { KEYWORD_INIT ("Author") }, + { KEYWORD_INIT ("Date") }, + { KEYWORD_INIT ("Header") }, + { KEYWORD_INIT ("Id") }, + { KEYWORD_INIT ("Locker") }, + { KEYWORD_INIT ("Log") }, + { KEYWORD_INIT ("Name") }, + { KEYWORD_INIT ("RCSfile") }, + { KEYWORD_INIT ("Revision") }, + { KEYWORD_INIT ("Source") }, + { KEYWORD_INIT ("State") }, + { NULL, 0 } +}; +enum keyword +{ + KEYWORD_AUTHOR = 0, + KEYWORD_DATE, + KEYWORD_HEADER, + KEYWORD_ID, + KEYWORD_LOCKER, + KEYWORD_LOG, + KEYWORD_NAME, + KEYWORD_RCSFILE, + KEYWORD_REVISION, + KEYWORD_SOURCE, + KEYWORD_STATE +}; + +/* Convert an RCS date string into a readable string. This is like + the RCS date2str function. */ + +static char * +printable_date (rcs_date) + const char *rcs_date; +{ + int year, mon, mday, hour, min, sec; + char buf[100]; + + (void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min, + &sec); + if (year < 1900) + year += 1900; + sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday, + hour, min, sec); + return xstrdup (buf); +} + +/* Escape the characters in a string so that it can be included in an + RCS value. */ + +static char * +escape_keyword_value (value, free_value) + const char *value; + int *free_value; +{ + char *ret, *t; + const char *s; + + for (s = value; *s != '\0'; s++) + { + char c; + + c = *s; + if (c == '\t' + || c == '\n' + || c == '\\' + || c == ' ' + || c == '$') + { + break; + } + } + + if (*s == '\0') + { + *free_value = 0; + return (char *) value; + } + + ret = xmalloc (strlen (value) * 4 + 1); + *free_value = 1; + + for (s = value, t = ret; *s != '\0'; s++, t++) + { + switch (*s) + { + default: + *t = *s; + break; + case '\t': + *t++ = '\\'; + *t = 't'; + break; + case '\n': + *t++ = '\\'; + *t = 'n'; + break; + case '\\': + *t++ = '\\'; + *t = '\\'; + break; + case ' ': + *t++ = '\\'; + *t++ = '0'; + *t++ = '4'; + *t = '0'; + break; + case '$': + *t++ = '\\'; + *t++ = '0'; + *t++ = '4'; + *t = '4'; + break; + } + } + + *t = '\0'; + + return ret; +} + +/* Expand RCS keywords in the memory buffer BUF of length LEN. This + applies to file RCS and version VERS. If NAME is not NULL, and is + not a numeric revision, then it is the symbolic tag used for the + checkout. EXPAND indicates how to expand the keywords. This + function sets *RETBUF and *RETLEN to the new buffer and length. + This function may modify the buffer BUF. If BUF != *RETBUF, then + RETBUF is a newly allocated buffer. */ + +static void +expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen) + RCSNode *rcs; + RCSVers *ver; + const char *name; + const char *log; + size_t loglen; + enum kflag expand; + char *buf; + size_t len; + char **retbuf; + size_t *retlen; +{ + struct expand_buffer + { + struct expand_buffer *next; + char *data; + size_t len; + int free_data; + } *ebufs = NULL; + struct expand_buffer *ebuf_last = NULL; + size_t ebuf_len = 0; + char *locker; + char *srch, *srch_next; + size_t srch_len; + + if (expand == KFLAG_O || expand == KFLAG_B) + { + *retbuf = buf; + *retlen = len; + return; + } + + /* If we are using -kkvl, dig out the locker information if any. */ + locker = NULL; + if (expand == KFLAG_KVL && rcs->other != NULL) + { + Node *p; + + p = findnode (rcs->other, "locks"); + if (p != NULL) + { + char *cp; + size_t verlen; + + /* The format of the locking information is + USER:VERSION USER:VERSION ... + If we find our version on the list, we set LOCKER to + the corresponding user name. */ + + verlen = strlen (ver->version); + cp = p->data; + while ((cp = strstr (cp, ver->version)) != NULL) + { + if (cp > p->data + && cp[-1] == ':' + && (cp[verlen] == '\0' + || whitespace (cp[verlen]))) + { + char *cpend; + + --cp; + cpend = cp; + while (cp > p->data && ! whitespace (*cp)) + --cp; + locker = xmalloc (cpend - cp + 1); + memcpy (locker, cp, cpend - cp); + locker[cpend - cp] = '\0'; + break; + } + + ++cp; + } + } + } + + /* RCS keywords look like $STRING$ or $STRING: VALUE$. */ + srch = buf; + srch_len = len; + while ((srch_next = memchr (srch, '$', srch_len)) != NULL) + { + char *s, *send; + size_t slen; + const struct rcs_keyword *keyword; + enum keyword kw; + char *value; + int free_value; + char *sub; + size_t sublen; + + srch_len -= (srch_next + 1) - srch; + srch = srch_next + 1; + + /* Look for the first non alphabetic character after the '$'. */ + send = srch + srch_len; + for (s = srch; s < send; s++) + if (! isalpha (*s)) + break; + + /* If the first non alphabetic character is not '$' or ':', + then this is not an RCS keyword. */ + if (s == send || (*s != '$' && *s != ':')) + continue; + + /* See if this is one of the keywords. */ + slen = s - srch; + for (keyword = keywords; keyword->string != NULL; keyword++) + { + if (keyword->len == slen + && strncmp (keyword->string, srch, slen) == 0) + { + break; + } + } + if (keyword->string == NULL) + continue; + + kw = (enum keyword) (keyword - keywords); + + /* If the keyword ends with a ':', then the old value consists + of the characters up to the next '$'. If there is no '$' + before the end of the line, though, then this wasn't an RCS + keyword after all. */ + if (*s == ':') + { + for (; s < send; s++) + if (*s == '$' || *s == '\n') + break; + if (s == send || *s != '$') + continue; + } + + /* At this point we must replace the string from SRCH to S + with the expansion of the keyword KW. */ + + /* Get the value to use. */ + free_value = 0; + if (expand == KFLAG_K) + value = NULL; + else + { + switch (kw) + { + default: + abort (); + + case KEYWORD_AUTHOR: + value = ver->author; + break; + + case KEYWORD_DATE: + value = printable_date (ver->date); + free_value = 1; + break; + + case KEYWORD_HEADER: + case KEYWORD_ID: + { + char *path; + int free_path; + char *date; + + if (kw == KEYWORD_HEADER) + path = rcs->path; + else + path = last_component (rcs->path); + path = escape_keyword_value (path, &free_path); + date = printable_date (ver->date); + value = xmalloc (strlen (path) + + strlen (ver->version) + + strlen (date) + + strlen (ver->author) + + strlen (ver->state) + + (locker == NULL ? 0 : strlen (locker)) + + 20); + + sprintf (value, "%s %s %s %s %s%s%s", + path, ver->version, date, ver->author, + ver->state, + locker != NULL ? " " : "", + locker != NULL ? locker : ""); + if (free_path) + free (path); + free (date); + free_value = 1; + } + break; + + case KEYWORD_LOCKER: + value = locker; + break; + + case KEYWORD_LOG: + case KEYWORD_RCSFILE: + value = escape_keyword_value (last_component (rcs->path), + &free_value); + break; + + case KEYWORD_NAME: + if (name != NULL && ! isdigit (*name)) + value = (char *) name; + else + value = NULL; + break; + + case KEYWORD_REVISION: + value = ver->version; + break; + + case KEYWORD_SOURCE: + value = escape_keyword_value (rcs->path, &free_value); + break; + + case KEYWORD_STATE: + value = ver->state; + break; + } + } + + sub = xmalloc (keyword->len + + (value == NULL ? 0 : strlen (value)) + + 10); + if (expand == KFLAG_V) + { + /* Decrement SRCH and increment S to remove the $ + characters. */ + --srch; + ++srch_len; + ++s; + sublen = 0; + } + else + { + strcpy (sub, keyword->string); + sublen = strlen (keyword->string); + if (expand != KFLAG_K) + { + sub[sublen] = ':'; + sub[sublen + 1] = ' '; + sublen += 2; + } + } + if (value != NULL) + { + strcpy (sub + sublen, value); + sublen += strlen (value); + } + if (expand != KFLAG_V && expand != KFLAG_K) + { + sub[sublen] = ' '; + ++sublen; + sub[sublen] = '\0'; + } + + if (free_value) + free (value); + + /* The Log keyword requires special handling. This behaviour + is taken from RCS 5.7. The special log message is what RCS + uses for ci -k. */ + if (kw == KEYWORD_LOG + && (sizeof "checked in with -k by " <= loglen + || strncmp (log, "checked in with -k by ", + sizeof "checked in with -k by " - 1) != 0)) + { + char *start; + char *leader; + size_t leader_len, leader_sp_len; + const char *logend; + const char *snl; + int cnl; + char *date; + const char *sl; + + /* We are going to insert the trailing $ ourselves, before + the log message, so we must remove it from S, if we + haven't done so already. */ + if (expand != KFLAG_V) + ++s; + + /* Find the start of the line. */ + start = srch; + while (start > buf && start[-1] != '\n') + --start; + + /* Copy the start of the line to use as a comment leader. */ + leader_len = srch - start; + if (expand != KFLAG_V) + --leader_len; + leader = xmalloc (leader_len); + memcpy (leader, start, leader_len); + leader_sp_len = leader_len; + while (leader_sp_len > 0 && leader[leader_sp_len - 1] == ' ') + --leader_sp_len; + + /* RCS does some checking for an old style of Log here, + but we don't bother. RCS issues a warning if it + changes anything. */ + + /* Count the number of newlines in the log message so that + we know how many copies of the leader we will need. */ + cnl = 0; + logend = log + loglen; + for (snl = log; snl < logend; snl++) + if (*snl == '\n') + ++cnl; + + date = printable_date (ver->date); + sub = xrealloc (sub, + (sublen + + sizeof "Revision" + + strlen (ver->version) + + strlen (date) + + strlen (ver->author) + + loglen + + (cnl + 2) * leader_len + + 20)); + if (expand != KFLAG_V) + { + sub[sublen] = '$'; + ++sublen; + } + sub[sublen] = '\n'; + ++sublen; + memcpy (sub + sublen, leader, leader_len); + sublen += leader_len; + sprintf (sub + sublen, "Revision %s %s %s\n", + ver->version, date, ver->author); + sublen += strlen (sub + sublen); + free (date); + + sl = log; + while (sl < logend) + { + if (*sl == '\n') + { + memcpy (sub + sublen, leader, leader_sp_len); + sublen += leader_sp_len; + sub[sublen] = '\n'; + ++sublen; + ++sl; + } + else + { + const char *slnl; + + memcpy (sub + sublen, leader, leader_len); + sublen += leader_len; + for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl) + ; + if (slnl < logend) + ++slnl; + memcpy (sub + sublen, sl, slnl - sl); + sublen += slnl - sl; + sl = slnl; + } + } + + memcpy (sub + sublen, leader, leader_sp_len); + sublen += leader_sp_len; + + free (leader); + } + + /* Now SUB contains a string which is to replace the string + from SRCH to S. SUBLEN is the length of SUB. */ + + if (srch + sublen == s) + { + memcpy (srch, sub, sublen); + free (sub); + } + else + { + struct expand_buffer *ebuf; + + /* We need to change the size of the buffer. We build a + list of expand_buffer structures. Each expand_buffer + structure represents a portion of the final output. We + concatenate them back into a single buffer when we are + done. This minimizes the number of potentially large + buffer copies we must do. */ + + if (ebufs == NULL) + { + ebufs = (struct expand_buffer *) xmalloc (sizeof *ebuf); + ebufs->next = NULL; + ebufs->data = buf; + ebufs->free_data = 0; + ebuf_len = srch - buf; + ebufs->len = ebuf_len; + ebuf_last = ebufs; + } + else + { + assert (srch >= ebuf_last->data); + assert (srch <= ebuf_last->data + ebuf_last->len); + ebuf_len -= ebuf_last->len - (srch - ebuf_last->data); + ebuf_last->len = srch - ebuf_last->data; + } + + ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf); + ebuf->data = sub; + ebuf->len = sublen; + ebuf->free_data = 1; + ebuf->next = NULL; + ebuf_last->next = ebuf; + ebuf_last = ebuf; + ebuf_len += sublen; + + ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf); + ebuf->data = s; + ebuf->len = srch_len - (s - srch); + ebuf->free_data = 0; + ebuf->next = NULL; + ebuf_last->next = ebuf; + ebuf_last = ebuf; + ebuf_len += srch_len - (s - srch); + } + + srch_len -= (s - srch); + srch = s; + } + + if (locker != NULL) + free (locker); + + if (ebufs == NULL) + { + *retbuf = buf; + *retlen = len; + } + else + { + char *ret; + + ret = xmalloc (ebuf_len); + *retbuf = ret; + *retlen = ebuf_len; + while (ebufs != NULL) + { + struct expand_buffer *next; + + memcpy (ret, ebufs->data, ebufs->len); + ret += ebufs->len; + if (ebufs->free_data) + free (ebufs->data); + next = ebufs->next; + free (ebufs); + ebufs = next; + } + } +} + +/* Check out a revision from an RCS file. + + If PFN is not NULL, then ignore WORKFILE and SOUT. Call PFN zero + or more times with the contents of the file. CALLERDAT is passed, + uninterpreted, to PFN. (The current code will always call PFN + exactly once for a non empty file; however, the current code + assumes that it can hold the entire file contents in memory, which + is not a good assumption, and might change in the future). + + Otherwise, if WORKFILE is not NULL, check out the revision to + WORKFILE. However, if WORKFILE is not NULL, and noexec is set, + then don't do anything. + + Otherwise, if WORKFILE is NULL, check out the revision to SOUT. If + SOUT is RUN_TTY, then write the contents of the revision to + standard output. When using SOUT, the output is generally a + temporary file; don't bother to get the file modes correct. + + REV is the numeric revision to check out. It may be NULL, which + means to check out the head of the default branch. + + If NAMETAG is not NULL, and is not a numeric revision, then it is + the tag that should be used when expanding the RCS Name keyword. + + OPTIONS is a string such as "-kb" or "-kv" for keyword expansion + options. It may be NULL to use the default expansion mode of the + file, typically "-kkv". */ + +int +RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) + RCSNode *rcs; + char *workfile; + char *rev; + char *nametag; + char *options; + char *sout; + RCSCHECKOUTPROC pfn; + void *callerdat; +{ + int free_rev = 0; + enum kflag expand; + FILE *fp; + struct stat sb; + char *key; + char *value; + size_t len; + int free_value = 0; + char *log = NULL; + size_t loglen; + FILE *ofp; + + if (trace) + { + (void) fprintf (stderr, "%s-> checkout (%s, %s, %s, %s)\n", +#ifdef SERVER_SUPPORT + server_active ? "S" : " ", +#else + "", +#endif + rcs->path, + rev != NULL ? rev : "", + options != NULL ? options : "", + (pfn != NULL ? "(function)" + : (workfile != NULL + ? workfile + : (sout != RUN_TTY ? sout : "(stdout)")))); + } + + assert (rev == NULL || isdigit (*rev)); + + if (noexec && workfile != NULL) + return 0; + + assert (sout == RUN_TTY || workfile == NULL); + assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL)); + + /* Some callers, such as Checkin or remove_file, will pass us a + branch. */ + if (rev != NULL && (numdots (rev) & 1) == 0) + { + rev = RCS_getbranch (rcs, rev, 1); + if (rev == NULL) + error (1, 0, "internal error: bad branch tag in checkout"); + free_rev = 1; + } + + if (rev == NULL || strcmp (rev, rcs->head) == 0) + { + int gothead; + + /* We want the head revision. Try to read it directly. */ + + if (rcs->flags & NODELTA) + { + free_rcsnode_contents (rcs); + rcs->flags |= PARTIAL; + } + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, 0, &fp); + else + { + fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); + if (fp == NULL) + error (1, 0, "unable to reopen `%s'", rcs->path); + if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0) + error (1, 0, "cannot fseek RCS file"); + } + + gothead = 0; + getrcsrev (fp, &key); + while (getrcskey (fp, &key, &value, &len) >= 0) + { + if (strcmp (key, "log") == 0) + { + log = xmalloc (len); + memcpy (log, value, len); + loglen = len; + } + if (strcmp (key, "text") == 0) + { + gothead = 1; + break; + } + } + + if (! gothead) + { + error (0, 0, "internal error: cannot find head text"); + if (free_rev) + free (rev); + return 1; + } + + if (fstat (fileno (fp), &sb) < 0) + error (1, errno, "cannot fstat %s", rcs->path); + + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", rcs->path); + } + else + { + /* It isn't the head revision of the trunk. We'll need to + walk through the deltas. */ + + fp = NULL; + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, 0, &fp); + + if (fp == NULL) + { + /* If RCS_deltas didn't close the file, we could use fstat + here too. Probably should change it thusly.... */ + if (stat (rcs->path, &sb) < 0) + error (1, errno, "cannot stat %s", rcs->path); + } + else + { + if (fstat (fileno (fp), &sb) < 0) + error (1, errno, "cannot fstat %s", rcs->path); + } + + RCS_deltas (rcs, fp, rev, RCS_FETCH, &value, &len, &log, &loglen); + free_value = 1; + } + + /* If OPTIONS is NULL or the empty string, then the old code would + invoke the RCS co program with no -k option, which means that + co would use the string we have stored in rcs->expand. */ + if ((options == NULL || options[0] == '\0') && rcs->expand == NULL) + expand = KFLAG_KV; + else + { + const char *ouroptions; + const char * const *cpp; + + if (options != NULL && options[0] != '\0') + { + assert (options[0] == '-' && options[1] == 'k'); + ouroptions = options + 2; + } + else + ouroptions = rcs->expand; + + for (cpp = kflags; *cpp != NULL; cpp++) + if (strcmp (*cpp, ouroptions) == 0) + break; + + if (*cpp != NULL) + expand = (enum kflag) (cpp - kflags); + else + { + error (0, 0, + "internal error: unsupported substitution string -k%s", + ouroptions); + expand = KFLAG_KV; + } + } + + if (expand != KFLAG_O && expand != KFLAG_B) + { + Node *p; + char *newvalue; + + p = findnode (rcs->versions, rev == NULL ? rcs->head : rev); + if (p == NULL) + error (1, 0, "internal error: no revision information for %s", + rev == NULL ? rcs->head : rev); + + expand_keywords (rcs, (RCSVers *) p->data, nametag, log, loglen, + expand, value, len, &newvalue, &len); + + if (newvalue != value) + { + if (free_value) + free (value); + value = newvalue; + free_value = 1; + } + } + + if (log != NULL) + { + free (log); + log = NULL; + } + + if (pfn != NULL) + { + /* The PFN interface is very simple to implement right now, as + we always have the entire file in memory. */ + if (len != 0) + pfn (callerdat, value, len); + } + else + { + if (workfile == NULL) + { + if (sout == RUN_TTY) + ofp = stdout; + else + { + ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w"); + if (ofp == NULL) + error (1, errno, "cannot open %s", sout); + } + } + else + { + ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w"); + if (ofp == NULL) + error (1, errno, "cannot open %s", workfile); + } + + if (workfile == NULL && sout == RUN_TTY) + { + if (len > 0) + cvs_output (value, len); + } + else + { + if (fwrite (value, 1, len, ofp) != len) + error (1, errno, "cannot write %s", + (workfile != NULL + ? workfile + : (sout != RUN_TTY ? sout : "stdout"))); + } + + if (workfile != NULL) + { + if (fclose (ofp) < 0) + error (1, errno, "cannot close %s", workfile); + if (chmod (workfile, + sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0) + error (0, errno, "cannot change mode of file %s", + workfile); + } + else if (sout != RUN_TTY) + { + if (fclose (ofp) < 0) + error (1, errno, "cannot close %s", sout); + } + } + + if (free_value) + free (value); + if (free_rev) + free (rev); + + return 0; +} + +/* This structure is passed between RCS_cmp_file and cmp_file_buffer. */ + +struct cmp_file_data +{ + const char *filename; + FILE *fp; + int different; +}; + +/* Compare the contents of revision REV of RCS file RCS with the + contents of the file FILENAME. OPTIONS is a string for the keyword + expansion options. Return 0 if the contents of the revision are + the same as the contents of the file, 1 if they are different. */ + +int +RCS_cmp_file (rcs, rev, options, filename) + RCSNode *rcs; + char *rev; + char *options; + const char *filename; +{ + int binary; + FILE *fp; + struct cmp_file_data data; + int retcode; + + if (options != NULL && options[0] != '\0') + binary = (strcmp (options, "-kb") == 0); + else + { + char *expand; + + expand = RCS_getexpand (rcs); + if (expand != NULL && strcmp (expand, "b") == 0) + binary = 1; + else + binary = 0; + } + + fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r"); + + data.filename = filename; + data.fp = fp; + data.different = 0; + + retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL, + options, RUN_TTY, cmp_file_buffer, + (void *) &data); + + /* If we have not yet found a difference, make sure that we are at + the end of the file. */ + if (! data.different) + { + if (getc (fp) != EOF) + data.different = 1; + } + + fclose (fp); + + if (retcode != 0) + return 1; + + return data.different; +} + +/* This is a subroutine of RCS_cmp_file. It is passed to + RCS_checkout. */ + +#define CMP_BUF_SIZE (8 * 1024) + +static void +cmp_file_buffer (callerdat, buffer, len) + void *callerdat; + const char *buffer; + size_t len; +{ + struct cmp_file_data *data = (struct cmp_file_data *) callerdat; + char *filebuf; + + /* If we've already found a difference, we don't need to check + further. */ + if (data->different) + return; + + filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len); + + while (len > 0) + { + size_t checklen; + + checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len; + if (fread (filebuf, 1, checklen, data->fp) != checklen) + { + if (ferror (data->fp)) + error (1, errno, "cannot read file %s for comparing", + data->filename); + data->different = 1; + free (filebuf); + return; + } + + if (memcmp (filebuf, buffer, checklen) != 0) + { + data->different = 1; + free (filebuf); + return; + } + + buffer += checklen; + len -= checklen; + } + + free (filebuf); +} + +/* For RCS file RCS, make symbolic tag TAG point to revision REV. + This validates that TAG is OK for a user to use. Return value is + -1 for error (and errno is set to indicate the error), positive for + error (and an error message has been printed), or zero for success. */ + +int +RCS_settag (rcs, tag, rev) + RCSNode *rcs; + const char *tag; + const char *rev; +{ + int ret; + + /* FIXME: This check should be moved to RCS_check_tag. There is no + reason for it to be here. */ + if (strcmp (tag, TAG_BASE) == 0 + || strcmp (tag, TAG_HEAD) == 0) + { + /* Print the name of the tag might be considered redundant + with the caller, which also prints it. Perhaps this helps + clarify why the tag name is considered reserved, I don't + know. */ + error (0, 0, "Attempt to add reserved tag name %s", tag); + return 1; + } + + ret = RCS_exec_settag (rcs->path, tag, rev); + if (ret != 0) + return ret; + + /* If we have already parsed the RCS file, update the tag + information. If we have not yet parsed it (i.e., the PARTIAL + flag is set), the new tag information will be read when and if + we do parse it. */ + if ((rcs->flags & PARTIAL) == 0) + { + List *symbols; + Node *node; + + /* At this point rcs->symbol_data may not have been parsed. + Calling RCS_symbols will force it to be parsed into a list + which we can easily manipulate. */ + symbols = RCS_symbols (rcs); + if (symbols == NULL) + { + symbols = getlist (); + rcs->symbols = symbols; + } + node = findnode (symbols, tag); + if (node != NULL) + { + free (node->data); + node->data = xstrdup (rev); + } + else + { + node = getnode (); + node->key = xstrdup (tag); + node->data = xstrdup (rev); + (void) addnode (symbols, node); + } + } + + /* Setting the tag will most likely have invalidated delta_pos. */ + rcs->flags |= NODELTA; + + return 0; +} + +/* Delete the symbolic tag TAG from the RCS file RCS. NOERR is 1 to + suppress errors--FIXME it would be better to avoid the errors or + some cleaner solution. */ + +int +RCS_deltag (rcs, tag, noerr) + RCSNode *rcs; + const char *tag; + int noerr; +{ + int ret; + + ret = RCS_exec_deltag (rcs->path, tag, noerr); + if (ret != 0) + return ret; + + /* If we have already parsed the RCS file, update the tag + information. If we have not yet parsed it (i.e., the PARTIAL + flag is set), the new tag information will be read when and if + we do parse it. */ + if ((rcs->flags & PARTIAL) == 0) + { + List *symbols; + + /* At this point rcs->symbol_data may not have been parsed. + Calling RCS_symbols will force it to be parsed into a list + which we can easily manipulate. */ + symbols = RCS_symbols (rcs); + if (symbols != NULL) + { + Node *node; + + node = findnode (symbols, tag); + if (node != NULL) + delnode (node); + } + } + + /* Deleting the tag will most likely have invalidated delta_pos. */ + rcs->flags |= NODELTA; + + return 0; +} + +/* Set the default branch of RCS to REV. */ + +int +RCS_setbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + int ret; + + if (rev == NULL && rcs->branch == NULL) + return 0; + if (rev != NULL && rcs->branch != NULL && strcmp (rev, rcs->branch) == 0) + return 0; + + ret = RCS_exec_setbranch (rcs->path, rev); + if (ret != 0) + return ret; + + if (rcs->branch != NULL) + free (rcs->branch); + rcs->branch = xstrdup (rev); + + /* Changing the branch will have changed the data in the file, so + delta_pos will no longer be correct. */ + rcs->flags |= NODELTA; + + return 0; +} + +/* Lock revision REV. NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. FIXME: + This is only required because the RCS ci program requires a lock. + If we eventually do the checkin ourselves, this can become a no-op. */ + +int +RCS_lock (rcs, rev, noerr) + RCSNode *rcs; + const char *rev; + int noerr; +{ + int ret; + + ret = RCS_exec_lock (rcs->path, rev, noerr); + if (ret != 0) + return ret; + + /* Setting a lock will have changed the data in the file, so + delta_pos will no longer be correct. */ + rcs->flags |= NODELTA; + + return 0; +} + +/* Unlock revision REV. NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. FIXME: + Like RCS_lock, this can become a no-op if we do the checkin + ourselves. */ + +int +RCS_unlock (rcs, rev, noerr) + RCSNode *rcs; + const char *rev; + int noerr; +{ + int ret; + + ret = RCS_exec_unlock (rcs->path, rev, noerr); + if (ret != 0) + return ret; + + /* Setting a lock will have changed the data in the file, so + delta_pos will no longer be correct. */ + rcs->flags |= NODELTA; + + return 0; +} -/* Stuff related to annotate command. This should perhaps be split - into the stuff which knows about the guts of RCS files, and the - command parsing type stuff. */ +/* RCS_deltas and friends. Processing of the deltas in RCS files. */ /* Linked list of allocated blocks. Seems kind of silly to reinvent the obstack wheel, and this isn't as nice as obstacks @@ -1772,8 +3405,10 @@ block_free () struct line { - /* Text of this line, terminated by \n or \0. */ + /* Text of this line. */ char *text; + /* Length of this line, not counting \n if has_newline is true. */ + size_t len; /* Version in which it was introduced. */ RCSVers *vers; /* Nonzero if this line ends with \n. This will always be true @@ -1798,37 +3433,45 @@ static void linevector_init (vec) struct linevector *vec; { - vec->lines_alloced = 10; + vec->lines_alloced = 0; vec->nlines = 0; - vec->vector = (struct line **) - xmalloc (vec->lines_alloced * sizeof (*vec->vector)); + vec->vector = NULL; } -static void linevector_add PROTO ((struct linevector *vec, char *text, - RCSVers *vers, unsigned int pos)); +static int linevector_add PROTO ((struct linevector *vec, char *text, + size_t len, RCSVers *vers, + unsigned int pos)); /* Given some text TEXT, add each of its lines to VEC before line POS (where line 0 is the first line). The last line in TEXT may or may - not be \n terminated. All \n in TEXT are changed to \0. Set the - version for each of the new lines to VERS. */ -static void -linevector_add (vec, text, vers, pos) + not be \n terminated. All \n in TEXT are changed to \0 (FIXME: I + don't think this is needed, or used, now that we have the ->len + field). Set the version for each of the new lines to VERS. This + function returns non-zero for success. It returns zero if the line + number is out of range. */ +static int +linevector_add (vec, text, len, vers, pos) struct linevector *vec; char *text; + size_t len; RCSVers *vers; unsigned int pos; { + char *textend; unsigned int i; unsigned int nnew; char *p; struct line *lines; - assert (vec->lines_alloced > 0); + if (len == 0) + return 1; + + textend = text + len; /* Count the number of lines we will need to add. */ nnew = 1; - for (p = text; *p != '\0'; ++p) - if (*p == '\n' && p[1] != '\0') + for (p = text; p < textend; ++p) + if (*p == '\n' && p + 1 < textend) ++nnew; /* Allocate the struct line's. */ lines = block_alloc (nnew * sizeof (struct line)); @@ -1836,6 +3479,8 @@ linevector_add (vec, text, vers, pos) /* Expand VEC->VECTOR if needed. */ if (vec->nlines + nnew >= vec->lines_alloced) { + if (vec->lines_alloced == 0) + vec->lines_alloced = 10; while (vec->nlines + nnew >= vec->lines_alloced) vec->lines_alloced *= 2; vec->vector = xrealloc (vec->vector, @@ -1847,7 +3492,7 @@ linevector_add (vec, text, vers, pos) vec->vector[i] = vec->vector[i - nnew]; if (pos > vec->nlines) - error (1, 0, "invalid rcs file: line to add out of range"); + return 0; /* Actually add the lines, to LINES and VEC->VECTOR. */ i = pos; @@ -1855,22 +3500,26 @@ linevector_add (vec, text, vers, pos) lines[0].vers = vers; lines[0].has_newline = 0; vec->vector[i++] = &lines[0]; - for (p = text; *p != '\0'; ++p) + for (p = text; p < textend; ++p) if (*p == '\n') { *p = '\0'; lines[i - pos - 1].has_newline = 1; - if (p[1] == '\0') + if (p + 1 == textend) /* If there are no characters beyond the last newline, we don't consider it another line. */ break; + lines[i - pos - 1].len = p - lines[i - pos - 1].text; lines[i - pos].text = p + 1; lines[i - pos].vers = vers; lines[i - pos].has_newline = 0; vec->vector[i] = &lines[i - pos]; ++i; } + lines[i - pos - 1].len = p - lines[i - pos - 1].text; vec->nlines += nnew; + + return 1; } static void linevector_delete PROTO ((struct linevector *, unsigned int, @@ -1903,6 +3552,8 @@ linevector_copy (to, from) { if (from->nlines > to->lines_alloced) { + if (to->lines_alloced == 0) + to->lines_alloced = 10; while (from->nlines > to->lines_alloced) to->lines_alloced *= 2; to->vector = (struct line **) @@ -1921,7 +3572,8 @@ static void linevector_free (vec) struct linevector *vec; { - free (vec->vector); + if (vec->vector != NULL) + free (vec->vector); } static char *month_printname PROTO ((char *)); @@ -1947,279 +3599,617 @@ month_printname (month) return (char *)months[mnum - 1]; } -static int annotate_fileproc PROTO ((struct file_info *)); +static int +apply_rcs_changes PROTO ((struct linevector *, const char *, size_t, + const char *, RCSVers *, RCSVers *)); + +/* Apply changes to the line vector LINES. DIFFBUF is a buffer of + length DIFFLEN holding the change text from an RCS file (the output + of diff -n). NAME is used in error messages. The VERS field of + any line added is set to ADDVERS. The VERS field of any line + deleted is set to DELVERS, unless DELVERS is NULL, in which case + the VERS field of deleted lines is unchanged. The function returns + non-zero if the change text is applied successfully. It returns + zero if the change text does not appear to apply to LINES (e.g., a + line number is invalid). If the change text is improperly + formatted (e.g., it is not the output of diff -n), the function + calls error with a status of 1, causing the program to exit. */ static int -annotate_fileproc (finfo) - struct file_info *finfo; +apply_rcs_changes (lines, diffbuf, difflen, name, addvers, delvers) + struct linevector *lines; + const char *diffbuf; + size_t difflen; + const char *name; + RCSVers *addvers; + RCSVers *delvers; { + const char *p; + const char *q; + int op; + /* The RCS format throws us for a loop in that the deltafrags (if + we define a deltafrag as an add or a delete) need to be applied + in reverse order. So we stick them into a linked list. */ + struct deltafrag { + enum {ADD, DELETE} type; + unsigned long pos; + unsigned long nlines; + char *new_lines; + size_t len; + struct deltafrag *next; + }; + struct deltafrag *dfhead; + struct deltafrag *df; + + dfhead = NULL; + for (p = diffbuf; p != NULL && p < diffbuf + difflen; ) + { + op = *p++; + if (op != 'a' && op != 'd') + /* Can't just skip over the deltafrag, because the value + of op determines the syntax. */ + error (1, 0, "unrecognized operation '%c' in %s", op, name); + df = (struct deltafrag *) xmalloc (sizeof (struct deltafrag)); + df->next = dfhead; + dfhead = df; + df->pos = strtoul (p, (char **) &q, 10); + + if (p == q) + error (1, 0, "number expected in %s", name); + p = q; + if (*p++ != ' ') + error (1, 0, "space expected in %s", name); + df->nlines = strtoul (p, (char **) &q, 10); + if (p == q) + error (1, 0, "number expected in %s", name); + p = q; + if (*p++ != '\012') + error (1, 0, "linefeed expected in %s", name); + + if (op == 'a') + { + unsigned int i; + + df->type = ADD; + i = df->nlines; + /* The text we want is the number of lines specified, or + until the end of the value, whichever comes first (it + will be the former except in the case where we are + adding a line which does not end in newline). */ + for (q = p; i != 0; ++q) + if (*q == '\n') + --i; + else if (q == diffbuf + difflen) + { + if (i != 1) + error (1, 0, "premature end of change in %s", name); + else + break; + } + + /* Copy the text we are adding into allocated space. */ + df->new_lines = block_alloc (q - p); + memcpy (df->new_lines, p, q - p); + df->len = q - p; + + p = q; + } + else + { + /* Correct for the fact that line numbers in RCS files + start with 1. */ + --df->pos; + + assert (op == 'd'); + df->type = DELETE; + } + } + + for (df = dfhead; df != NULL;) + { + unsigned int ln; + + switch (df->type) + { + case ADD: + if (! linevector_add (lines, df->new_lines, df->len, addvers, + df->pos)) + return 0; + break; + case DELETE: + if (df->pos > lines->nlines + || df->pos + df->nlines > lines->nlines) + return 0; + if (delvers != NULL) + for (ln = df->pos; ln < df->pos + df->nlines; ++ln) + lines->vector[ln]->vers = delvers; + linevector_delete (lines, df->pos, df->nlines); + break; + } + df = df->next; + free (dfhead); + dfhead = df; + } + + return 1; +} + +/* Apply an RCS change text to a buffer. The function name starts + with rcs rather than RCS because this does not take an RCSNode + argument. NAME is used in error messages. TEXTBUF is the text + buffer to change, and TEXTLEN is the size. DIFFBUF and DIFFLEN are + the change buffer and size. The new buffer is returned in *RETBUF + and *RETLEN. The new buffer is allocated by xmalloc. The function + changes the contents of TEXTBUF. This function returns 1 for + success. On failure, it calls error and returns 0. */ + +int +rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen) + const char *name; + char *textbuf; + size_t textlen; + const char *diffbuf; + size_t difflen; + char **retbuf; + size_t *retlen; +{ + struct linevector lines; + int ret; + + *retbuf = NULL; + *retlen = 0; + + linevector_init (&lines); + + if (! linevector_add (&lines, textbuf, textlen, NULL, 0)) + error (1, 0, "cannot initialize line vector"); + + if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL)) + { + error (0, 0, "invalid change text in %s", name); + ret = 0; + } + else + { + char *p; + size_t n; + unsigned int ln; + + n = 0; + for (ln = 0; ln < lines.nlines; ++ln) + /* 1 for \n */ + n += lines.vector[ln]->len + 1; + + p = xmalloc (n); + *retbuf = p; + + for (ln = 0; ln < lines.nlines; ++ln) + { + memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len); + p += lines.vector[ln]->len; + if (lines.vector[ln]->has_newline) + *p++ = '\n'; + } + + *retlen = p - *retbuf; + assert (*retlen <= n); + + ret = 1; + } + + linevector_free (&lines); + + /* Note that this assumes that we have not called from anything + else which uses the block vectors. FIXME: We could fix this by + saving and restoring the state of the block allocation code. */ + block_free (); + + return ret; +} + +/* Walk the deltas in RCS to get to revision VERSION. + + If OP is RCS_ANNOTATE, then write annotations using cvs_output. + + If OP is RCS_FETCH, then put the contents of VERSION into a + newly-malloc'd array and put a pointer to it in *TEXT. Each line + is \n terminated; the caller is responsible for converting text + files if desired. The total length is put in *LEN. + + If FP is non-NULL, it should be a file descriptor open to the file + RCS with file position pointing to the deltas. We close the file + when we are done. + + If LOG is non-NULL, then *LOG is set to the log message of VERSION, + and *LOGLEN is set to the length of the log message. + + On error, give a fatal error. */ + +static void +RCS_deltas (rcs, fp, version, op, text, len, log, loglen) + RCSNode *rcs; FILE *fp; + char *version; + enum rcs_delta_op op; + char **text; + size_t *len; + char **log; + size_t *loglen; +{ + char *branchversion; + char *cpversion; char *key; char *value; + size_t vallen; RCSVers *vers; RCSVers *prev_vers; + RCSVers *trunk_vers; + char *next; int n; - int ishead; + int ishead, isnext, isversion, onbranch; Node *node; struct linevector headlines; struct linevector curlines; + struct linevector trunklines; + int foundhead; - if (finfo->rcs == NULL) - return (1); - - /* Distinguish output for various files if we are processing - several files. */ - cvs_outerr ("Annotations for ", 0); - cvs_outerr (finfo->fullname, 0); - cvs_outerr ("\n***************\n", 0); - - if (!(finfo->rcs->flags & PARTIAL)) - /* We are leaking memory by calling RCS_reparsefile again. */ - error (0, 0, "internal warning: non-partial rcs in annotate_fileproc"); - RCS_reparsercsfile (finfo->rcs, &fp); + if (fp == NULL) + { + if (rcs->flags & NODELTA) + { + free_rcsnode_contents (rcs); + RCS_reparsercsfile (rcs, 0, &fp); + } + else + { + fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); + if (fp == NULL) + error (1, 0, "unable to reopen `%s'", rcs->path); + if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0) + error (1, 0, "cannot fseek RCS file"); + } + } ishead = 1; vers = NULL; + prev_vers = NULL; + trunk_vers = NULL; + next = NULL; + onbranch = 0; + foundhead = 0; + + linevector_init (&curlines); + linevector_init (&headlines); + linevector_init (&trunklines); + + /* We set BRANCHVERSION to the version we are currently looking + for. Initially, this is the version on the trunk from which + VERSION branches off. If VERSION is not a branch, then + BRANCHVERSION is just VERSION. */ + branchversion = xstrdup (version); + cpversion = strchr (branchversion, '.'); + if (cpversion != NULL) + cpversion = strchr (cpversion + 1, '.'); + if (cpversion != NULL) + *cpversion = '\0'; do { getrcsrev (fp, &key); - /* Stash the previous version. */ - prev_vers = vers; + if (next != NULL && strcmp (next, key) != 0) + { + /* This is not the next version we need. It is a branch + version which we want to ignore. */ + isnext = 0; + isversion = 0; + } + else + { + isnext = 1; + + /* look up the revision */ + node = findnode (rcs->versions, key); + if (node == NULL) + error (1, 0, + "mismatch in rcs file %s between deltas and deltatexts", + rcs->path); + + /* Stash the previous version. */ + prev_vers = vers; - /* look up the revision */ - node = findnode (finfo->rcs->versions, key); - if (node == NULL) - error (1, 0, "mismatch in rcs file %s between deltas and deltatexts", - finfo->rcs->path); - vers = (RCSVers *) node->data; + vers = (RCSVers *) node->data; + next = vers->next; + + /* Compare key and trunkversion now, because key points to + storage controlled by getrcskey. */ + if (strcmp (branchversion, key) == 0) + isversion = 1; + else + isversion = 0; + } - while ((n = getrcskey (fp, &key, &value)) >= 0) + while ((n = getrcskey (fp, &key, &value, &vallen)) >= 0) { + if (log != NULL + && isversion + && strcmp (key, "log") == 0 + && strcmp (branchversion, version) == 0) + { + *log = xmalloc (vallen); + memcpy (*log, value, vallen); + *loglen = vallen; + } + if (strcmp (key, "text") == 0) { if (ishead) { char *p; - p = block_alloc (strlen (value) + 1); - strcpy (p, value); + p = block_alloc (vallen); + memcpy (p, value, vallen); + + if (! linevector_add (&curlines, p, vallen, NULL, 0)) + error (1, 0, "invalid rcs file %s", rcs->path); - linevector_init (&headlines); - linevector_init (&curlines); - linevector_add (&headlines, p, NULL, 0); - linevector_copy (&curlines, &headlines); ishead = 0; } - else + else if (isnext) { - char *p; - char *q; - int op; - /* The RCS format throws us for a loop in that the - deltafrags (if we define a deltafrag as an - add or a delete) need to be applied in reverse - order. So we stick them into a linked list. */ - struct deltafrag { - enum {ADD, DELETE} type; - unsigned long pos; - unsigned long nlines; - char *new_lines; - struct deltafrag *next; - }; - struct deltafrag *dfhead; - struct deltafrag *df; - - dfhead = NULL; - for (p = value; p != NULL && *p != '\0'; ) - { - op = *p++; - if (op != 'a' && op != 'd') - /* Can't just skip over the deltafrag, because - the value of op determines the syntax. */ - error (1, 0, "unrecognized operation '%c' in %s", - op, finfo->rcs->path); - df = (struct deltafrag *) - xmalloc (sizeof (struct deltafrag)); - df->next = dfhead; - dfhead = df; - df->pos = strtoul (p, &q, 10); - - if (p == q) - error (1, 0, "number expected in %s", - finfo->rcs->path); - p = q; - if (*p++ != ' ') - error (1, 0, "space expected in %s", - finfo->rcs->path); - df->nlines = strtoul (p, &q, 10); - if (p == q) - error (1, 0, "number expected in %s", - finfo->rcs->path); - p = q; - if (*p++ != '\012') - error (1, 0, "linefeed expected in %s", - finfo->rcs->path); - - if (op == 'a') - { - unsigned int i; - - df->type = ADD; - i = df->nlines; - /* The text we want is the number of lines - specified, or until the end of the value, - whichever comes first (it will be the former - except in the case where we are adding a line - which does not end in newline). */ - for (q = p; i != 0; ++q) - if (*q == '\n') - --i; - else if (*q == '\0') - { - if (i != 1) - error (1, 0, "\ -invalid rcs file %s: premature end of value", - finfo->rcs->path); - else - break; - } - - /* Copy the text we are adding into allocated - space. */ - df->new_lines = block_alloc (q - p + 1); - strncpy (df->new_lines, p, q - p); - df->new_lines[q - p] = '\0'; - - p = q; - } - else - { - /* Correct for the fact that line numbers in RCS - files start with 1. */ - --df->pos; - - assert (op == 'd'); - df->type = DELETE; - } - } - for (df = dfhead; df != NULL;) - { - unsigned int ln; - - switch (df->type) - { - case ADD: - linevector_add (&curlines, df->new_lines, - NULL, df->pos); - break; - case DELETE: - if (df->pos > curlines.nlines - || df->pos + df->nlines > curlines.nlines) - error (1, 0, "\ -invalid rcs file %s (`d' operand out of range)", - finfo->rcs->path); - for (ln = df->pos; ln < df->pos + df->nlines; ++ln) - curlines.vector[ln]->vers = prev_vers; - linevector_delete (&curlines, df->pos, df->nlines); - break; - } - df = df->next; - free (dfhead); - dfhead = df; - } + if (! apply_rcs_changes (&curlines, value, vallen, + rcs->path, + onbranch ? vers : NULL, + onbranch ? NULL : prev_vers)) + error (1, 0, "invalid change text in %s", rcs->path); } break; } } if (n < 0) goto l_error; - } while (vers->next != NULL); - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", finfo->rcs->path); + if (isversion) + { + /* This is either the version we want, or it is the + branchpoint to the version we want. */ + if (strcmp (branchversion, version) == 0) + { + /* This is the version we want. */ + linevector_copy (&headlines, &curlines); + foundhead = 1; + if (onbranch) + { + /* We have found this version by tracking up a + branch. Restore back to the lines we saved + when we left the trunk, and continue tracking + down the trunk. */ + onbranch = 0; + vers = trunk_vers; + next = vers->next; + linevector_copy (&curlines, &trunklines); + } + } + else + { + Node *p; - /* Now print out the data we have just computed. */ - { - unsigned int ln; + /* We need to look up the branch. */ + onbranch = 1; - for (ln = 0; ln < headlines.nlines; ++ln) - { - char buf[80]; - /* Period which separates year from month in date. */ - char *ym; - /* Period which separates month from day in date. */ - char *md; - RCSVers *prvers; + if (numdots (branchversion) < 2) + { + unsigned int ln; + + /* We are leaving the trunk; save the current + lines so that we can restore them when we + continue tracking down the trunk. */ + trunk_vers = vers; + linevector_copy (&trunklines, &curlines); + + /* Reset the version information we have + accumulated so far. It only applies to the + changes from the head to this version. */ + for (ln = 0; ln < curlines.nlines; ++ln) + curlines.vector[ln]->vers = NULL; + } - prvers = headlines.vector[ln]->vers; - if (prvers == NULL) - prvers = vers; + /* The next version we want is the entry on + VERS->branches which matches this branch. For + example, suppose VERSION is 1.21.4.3 and + BRANCHVERSION was 1.21. Then we look for an entry + starting with "1.21.4" and we'll put it (probably + 1.21.4.1) in NEXT. We'll advance BRANCHVERSION by + two dots (in this example, to 1.21.4.3). */ + + if (vers->branches == NULL) + error (1, 0, "missing expected branches in %s", + rcs->path); + *cpversion = '.'; + ++cpversion; + cpversion = strchr (cpversion, '.'); + if (cpversion == NULL) + error (1, 0, "version number confusion in %s", + rcs->path); + for (p = vers->branches->list->next; + p != vers->branches->list; + p = p->next) + if (strncmp (p->key, branchversion, + cpversion - branchversion) == 0) + break; + if (p == vers->branches->list) + error (1, 0, "missing expected branch in %s", + rcs->path); - sprintf (buf, "%-12s (%-8.8s ", - prvers->version, - prvers->author); - cvs_output (buf, 0); + next = p->key; - /* Now output the date. */ - ym = strchr (prvers->date, '.'); - if (ym == NULL) - cvs_output ("??-???-??", 0); - else - { - md = strchr (ym + 1, '.'); - if (md == NULL) - cvs_output ("??", 0); - else - cvs_output (md + 1, 2); - - cvs_output ("-", 1); - cvs_output (month_printname (ym + 1), 0); - cvs_output ("-", 1); - /* Only output the last two digits of the year. Our output - lines are long enough as it is without printing the - century. */ - cvs_output (ym - 2, 2); + cpversion = strchr (cpversion + 1, '.'); + if (cpversion != NULL) + *cpversion = '\0'; } - cvs_output ("): ", 0); - cvs_output (headlines.vector[ln]->text, 0); - cvs_output ("\n", 1); } - } + if (op == RCS_FETCH && foundhead) + break; + } while (next != NULL); + + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", rcs->path); - if (!ishead) + if (! foundhead) + error (1, 0, "could not find desired version %s in %s", + version, rcs->path); + + /* Now print out or return the data we have just computed. */ + switch (op) { - linevector_free (&curlines); - linevector_free (&headlines); + case RCS_ANNOTATE: + { + unsigned int ln; + + for (ln = 0; ln < headlines.nlines; ++ln) + { + char buf[80]; + /* Period which separates year from month in date. */ + char *ym; + /* Period which separates month from day in date. */ + char *md; + RCSVers *prvers; + + prvers = headlines.vector[ln]->vers; + if (prvers == NULL) + prvers = vers; + + sprintf (buf, "%-12s (%-8.8s ", + prvers->version, + prvers->author); + cvs_output (buf, 0); + + /* Now output the date. */ + ym = strchr (prvers->date, '.'); + if (ym == NULL) + cvs_output ("?\?-??\?-??", 0); + else + { + md = strchr (ym + 1, '.'); + if (md == NULL) + cvs_output ("??", 0); + else + cvs_output (md + 1, 2); + + cvs_output ("-", 1); + cvs_output (month_printname (ym + 1), 0); + cvs_output ("-", 1); + /* Only output the last two digits of the year. Our output + lines are long enough as it is without printing the + century. */ + cvs_output (ym - 2, 2); + } + cvs_output ("): ", 0); + cvs_output (headlines.vector[ln]->text, + headlines.vector[ln]->len); + cvs_output ("\n", 1); + } + } + break; + case RCS_FETCH: + { + char *p; + size_t n; + unsigned int ln; + + assert (text != NULL); + assert (len != NULL); + + n = 0; + for (ln = 0; ln < headlines.nlines; ++ln) + /* 1 for \n */ + n += headlines.vector[ln]->len + 1; + p = xmalloc (n); + *text = p; + for (ln = 0; ln < headlines.nlines; ++ln) + { + memcpy (p, headlines.vector[ln]->text, + headlines.vector[ln]->len); + p += headlines.vector[ln]->len; + if (headlines.vector[ln]->has_newline) + *p++ = '\n'; + } + *len = p - *text; + assert (*len <= n); + } + break; } + + linevector_free (&curlines); + linevector_free (&headlines); + linevector_free (&trunklines); + block_free (); - return 0; + return; l_error: if (ferror (fp)) - error (1, errno, "cannot read %s", finfo->rcs->path); + error (1, errno, "cannot read %s", rcs->path); else error (1, 0, "%s does not appear to be a valid rcs file", - finfo->rcs->path); - /* Shut up gcc -Wall. */ + rcs->path); +} + + +/* Annotate command. In rcs.c for historical reasons (from back when + what is now RCS_deltas was part of annotate_fileproc). */ + +/* Options from the command line. */ + +static int force_tag_match = 1; +static char *tag = NULL; +static char *date = NULL; + +static int annotate_fileproc PROTO ((void *callerdat, struct file_info *)); + +static int +annotate_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; +{ + FILE *fp = NULL; + char *version; + + if (finfo->rcs == NULL) + return (1); + + if (finfo->rcs->flags & PARTIAL) + RCS_reparsercsfile (finfo->rcs, 0, &fp); + + version = RCS_getversion (finfo->rcs, tag, date, force_tag_match, + (int *) NULL); + if (version == NULL) + return 0; + + /* Distinguish output for various files if we are processing + several files. */ + cvs_outerr ("Annotations for ", 0); + cvs_outerr (finfo->fullname, 0); + cvs_outerr ("\n***************\n", 0); + + RCS_deltas (finfo->rcs, fp, version, RCS_ANNOTATE, (char **) NULL, + (size_t) NULL, (char **) NULL, (size_t *) NULL); + free (version); return 0; } static const char *const annotate_usage[] = { - "Usage: %s %s [-l] [files...]\n", + "Usage: %s %s [-lRf] [-r rev|-D date] [files...]\n", "\t-l\tLocal directory only, no recursion.\n", + "\t-R\tProcess directories recursively.\n", + "\t-f\tUse head revision if tag/date not found.\n", + "\t-r rev\tAnnotate file as of specified revision/tag.\n", + "\t-D date\tAnnotate file as of specified date.\n", NULL }; /* Command to show the revision, date, and author where each line of a - file was modified. Currently it will only show the trunk, all the - way to the head, but it would be useful to enhance it to (a) allow - one to specify a revision, and display only as far as that (easy; - just have annotate_fileproc set all the ->vers fields to NULL when - you hit that revision), and (b) handle branches (not as easy, but - doable). The user interface for both (a) and (b) could be a -r - option. */ + file was modified. */ int annotate (argc, argv) @@ -2233,13 +4223,25 @@ annotate (argc, argv) usage (annotate_usage); optind = 0; - while ((c = getopt (argc, argv, "+l")) != -1) + while ((c = getopt (argc, argv, "+lr:D:fR")) != -1) { switch (c) { case 'l': local = 1; break; + case 'R': + local = 0; + break; + case 'r': + tag = optarg; + break; + case 'D': + date = Make_Date (optarg); + break; + case 'f': + force_tag_match = 0; + break; case '?': default: usage (annotate_usage); @@ -2257,18 +4259,20 @@ annotate (argc, argv) if (local) send_arg ("-l"); + if (!force_tag_match) + send_arg ("-f"); + option_with_arg ("-r", tag); + if (date) + client_senddate (date); send_file_names (argc, argv, SEND_EXPAND_WILD); - /* FIXME: We shouldn't have to send current files, but I'm not sure - whether it works. So send the files -- - it's slower but it works. */ - send_files (argc, argv, local, 0); + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); send_to_server ("annotate\012", 0); return get_responses_and_close (); } #endif /* CLIENT_SUPPORT */ return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, - 1, 0); + 1); } |