summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>1997-05-15 22:52:26 +0000
committerpeter <peter@FreeBSD.org>1997-05-15 22:52:26 +0000
commit6ff5202f154ca19bec5c77bfedbd9febafda6b23 (patch)
tree0c3be018c4b60cf53793c31cbb6e361daca83738 /contrib/cvs/src
parente99541e5084ccab931752735585261dc4727e69f (diff)
downloadFreeBSD-src-6ff5202f154ca19bec5c77bfedbd9febafda6b23.zip
FreeBSD-src-6ff5202f154ca19bec5c77bfedbd9febafda6b23.tar.gz
Merge import conflicts
Diffstat (limited to 'contrib/cvs/src')
-rw-r--r--contrib/cvs/src/diff.c622
-rw-r--r--contrib/cvs/src/main.c1000
-rw-r--r--contrib/cvs/src/mkmodules.c233
-rw-r--r--contrib/cvs/src/rcs.c2798
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);
}
OpenPOWER on IntegriCloud