Example #1
0
/*
 * Finds all the ,v files in the argument directory, and adds them to the
 * files list.  Returns 0 for success and non-zero if the argument directory
 * cannot be opened, in which case errno is set to indicate the error.
 * In the error case LIST is left in some reasonable state (unchanged, or
 * containing the files which were found before the error occurred).
 */
static int find_rcs (const char *dir, List *list, const char *regex)
{
    Node *p;
    struct dirent *dp;
    DIR *dirp;

	/* set up to read the dir */
    if ((dirp = opendir (dir)) == NULL)
	return (1);

	/* read the dir, grabbing the ,v files */
    errno = 0;
    while ((dp = readdir (dirp)) != NULL)
    {
		if (CVS_FNMATCH (RCSPAT, dp->d_name, CVS_CASEFOLD) == 0) 
		{
			char *comma, *q;

			comma = strrchr (dp->d_name, ',');	/* strip the ,v */
			*comma = '\0';

			if(!fncmp(dp->d_name,RCSREPOVERSION))
				continue;

			q = map_fixed_rename(dir,dp->d_name);

			if(q && *q && (!regex || regex_filename_match(regex, q)))
			{
				p = getnode ();
				p->type = FILES;
				p->key = xstrdup (q);
				if(!findnode_fn(list,p->key))
				{
					if(addnode (list, p) != 0)
						freenode (p);
				}
			}
		}
		errno = 0;
    }
    if (errno != 0)
    {
	int save_errno = errno;
	(void) closedir (dirp);
	errno = save_errno;
	return 1;
    }
    (void) closedir (dirp);
    return (0);
}
Example #2
0
static void addfile (List **listp, char *dir, char *file)
{
    Node *n;
    List *fl;

    /* add this dir. */
    addlist (listp, dir);

    n = findnode_fn (*listp, dir);
    if (n == NULL)
    {
	error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
	       dir);
    }

    n->type = DIRS;
    fl = (List *) n->data;
	addlist (&fl, file);
    n->data = (char *) fl;
    return;
}
Example #3
0
/* Fill in and return a Vers_TS structure for the file FINFO.
 *
 * INPUTS
 *   finfo		struct file_info data about the file to be examined.
 *   options		Keyword expansion options, I think generally from the
 *			command line.  Can be either NULL or "" to indicate
 *			none are specified here.
 *   tag		Tag specified by user on the command line (via -r).
 *   date		Date specified by user on the command line (via -D).
 *   force_tag_match	If set and TAG is specified, will only set RET->vn_rcs
 *   			based on TAG.  Otherwise, if TAG is specified and does
 *   			not exist in the file, RET->vn_rcs will be set to the
 *   			head revision.
 *   set_time		If set, set the last modification time of the user file
 *			specified by FINFO to the checkin time of RET->vn_rcs.
 *
 * RETURNS
 *   Vers_TS structure for FINFO.
 */
Vers_TS *
Version_TS (struct file_info *finfo, char *options, char *tag, char *date,
            int force_tag_match, int set_time)
{
    Node *p;
    RCSNode *rcsdata;
    Vers_TS *vers_ts;
    struct stickydirtag *sdtp;
    Entnode *entdata;
    char *rcsexpand = NULL;

    /* get a new Vers_TS struct */

    vers_ts = xmalloc (sizeof (Vers_TS));
    memset (vers_ts, 0, sizeof (*vers_ts));

    /*
     * look up the entries file entry and fill in the version and timestamp
     * if entries is NULL, there is no entries file so don't bother trying to
     * look it up (used by checkout -P)
     */
    if (finfo->entries == NULL)
    {
	sdtp = NULL;
	p = NULL;
    }
    else
    {
	p = findnode_fn (finfo->entries, finfo->file);
	sdtp = finfo->entries->list->data; /* list-private */
    }

    if (p == NULL)
    {
	entdata = NULL;
    }
    else
    {
	entdata = p->data;

	if (entdata->type == ENT_SUBDIR)
	{
	    /* According to cvs.texinfo, the various fields in the Entries
	       file for a directory (other than the name) do not have a
	       defined meaning.  We need to pass them along without getting
	       confused based on what is in them.  Therefore we make sure
	       not to set vn_user and the like from Entries, add.c and
	       perhaps other code will expect these fields to be NULL for
	       a directory.  */
	    vers_ts->entdata = entdata;
	}
	else
#ifdef SERVER_SUPPORT
	/* An entries line with "D" in the timestamp indicates that the
	   client sent Is-modified without sending Entry.  So we want to
	   use the entries line for the sole purpose of telling
	   time_stamp_server what is up; we don't want the rest of CVS
	   to think there is an entries line.  */
	if (strcmp (entdata->timestamp, "D") != 0)
#endif
	{
	    vers_ts->vn_user = xstrdup (entdata->version);
	    vers_ts->ts_rcs = xstrdup (entdata->timestamp);
	    vers_ts->ts_conflict = xstrdup (entdata->conflict);
	    if (!(tag || date) && !(sdtp && sdtp->aflag))
	    {
		vers_ts->tag = xstrdup (entdata->tag);
		vers_ts->date = xstrdup (entdata->date);
	    }
	    vers_ts->entdata = entdata;
	}
	/* Even if we don't have an "entries line" as such
	   (vers_ts->entdata), we want to pick up options which could
	   have been from a Kopt protocol request.  */
	if (!options || *options == '\0')
	{
	    if (!(sdtp && sdtp->aflag))
		vers_ts->options = xstrdup (entdata->options);
	}
    }

    /* Always look up the RCS keyword mode when we have an RCS archive.  It
     * will either be needed as a default or to avoid allowing the -k options
     * specified on the command line from overriding binary mode (-kb).
     */
    if (finfo->rcs != NULL)
	rcsexpand = RCS_getexpand (finfo->rcs);

    /*
     * -k options specified on the command line override (and overwrite)
     * options stored in the entries file and default options from the RCS
     * archive, except for binary mode (-kb).
     */
    if (options && *options != '\0')
    {
	if (vers_ts->options != NULL)
	    free (vers_ts->options);
	if (rcsexpand != NULL && strcmp (rcsexpand, "b") == 0)
	    vers_ts->options = xstrdup ("-kb");
	else
	    vers_ts->options = xstrdup (options);
    }
    else if ((!vers_ts->options || *vers_ts->options == '\0')
             && rcsexpand != NULL)
    {
	/* If no keyword expansion was specified on command line,
	   use whatever was in the rcs file (if there is one).  This
	   is how we, if we are the server, tell the client whether
	   a file is binary.  */
	if (vers_ts->options != NULL)
	    free (vers_ts->options);
	vers_ts->options = xmalloc (strlen (rcsexpand) + 3);
	strcpy (vers_ts->options, "-k");
	strcat (vers_ts->options, rcsexpand);
    }
    if (!vers_ts->options)
	vers_ts->options = xstrdup ("");

    /*
     * if tags were specified on the command line, they override what is in
     * the Entries file
     */
    if (tag || date)
    {
	vers_ts->tag = xstrdup (tag);
	vers_ts->date = xstrdup (date);
    }
    else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0))
    {
	if (!vers_ts->tag)
	{
	    vers_ts->tag = xstrdup (sdtp->tag);
	    vers_ts->nonbranch = sdtp->nonbranch;
	}
	if (!vers_ts->date)
	    vers_ts->date = xstrdup (sdtp->date);
    }

    /* Now look up the info on the source controlled file */
    if (finfo->rcs != NULL)
    {
	rcsdata = finfo->rcs;
	rcsdata->refcount++;
    }
    else if (finfo->repository != NULL)
	rcsdata = RCS_parse (finfo->file, finfo->repository);
    else
	rcsdata = NULL;

    if (rcsdata != NULL)
    {
	/* squirrel away the rcsdata pointer for others */
	vers_ts->srcfile = rcsdata;

	if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0)
	{
	    vers_ts->vn_rcs = xstrdup (vers_ts->vn_user);
	    vers_ts->vn_tag = xstrdup (vers_ts->vn_user);
	}
	else
	{
	    int simple;

	    vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
					      vers_ts->date, force_tag_match,
					      &simple);
	    if (vers_ts->vn_rcs == NULL)
		vers_ts->vn_tag = NULL;
	    else if (simple)
		vers_ts->vn_tag = xstrdup (vers_ts->tag);
	    else
		vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs);
	}

	/*
	 * If the source control file exists and has the requested revision,
	 * get the Date the revision was checked in.  If "user" exists, set
	 * its mtime.
	 */
	if (set_time && vers_ts->vn_rcs != NULL)
	{
#ifdef SERVER_SUPPORT
	    if (server_active)
		server_modtime (finfo, vers_ts);
	    else
#endif
	    {
		struct utimbuf t;

		memset (&t, 0, sizeof (t));
		t.modtime = RCS_getrevtime (rcsdata, vers_ts->vn_rcs, 0, 0);
		if (t.modtime != (time_t) -1)
		{
#ifdef UTIME_EXPECTS_WRITABLE
		    int change_it_back = 0;
#endif

		    (void) time (&t.actime);

#ifdef UTIME_EXPECTS_WRITABLE
		    if (!iswritable (finfo->file))
		    {
			xchmod (finfo->file, 1);
			change_it_back = 1;
		    }
#endif  /* UTIME_EXPECTS_WRITABLE  */

		    /* This used to need to ignore existence_errors
		       (for cases like where update.c now clears
		       set_time if noexec, but didn't used to).  I
		       think maybe now it doesn't (server_modtime does
		       not like those kinds of cases).  */
		    (void) utime (finfo->file, &t);

#ifdef UTIME_EXPECTS_WRITABLE
		    if (change_it_back)
			xchmod (finfo->file, 0);
#endif  /*  UTIME_EXPECTS_WRITABLE  */
		}
	    }
	}
    }

    /* get user file time-stamp in ts_user */
    if (finfo->entries != NULL)
    {
#ifdef SERVER_SUPPORT
	if (server_active)
	    time_stamp_server (finfo->file, vers_ts, entdata);
	else
#endif
	    vers_ts->ts_user = time_stamp (finfo->file);
    }

    return (vers_ts);
}
Example #4
0
File: ignore.c Project: aosm/cvs
/*
 * Process the current directory, looking for files not in ILIST and
 * not on the global ignore list for this directory.  If we find one,
 * call PROC passing it the name of the file and the update dir.
 * ENTRIES is the entries list, which is used to identify known
 * directories.  ENTRIES may be NULL, in which case we assume that any
 * directory with a CVS administration directory is known.
 */
void
ignore_files (List *ilist, List *entries, const char *update_dir,
              Ignore_proc proc)
{
    int subdirs;
    DIR *dirp;
    struct dirent *dp;
    struct stat sb;
    char *file;
    const char *xdir;
    List *files;
    Node *p;

    /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
    if (entries == NULL)
	subdirs = 0;
    else
    {
	struct stickydirtag *sdtp = entries->list->data;

	subdirs = sdtp == NULL || sdtp->subdirs;
    }

    /* we get called with update_dir set to "." sometimes... strip it */
    if (strcmp (update_dir, ".") == 0)
	xdir = "";
    else
	xdir = update_dir;

    dirp = CVS_OPENDIR (".");
    if (dirp == NULL)
    {
	error (0, errno, "cannot open current directory");
	return;
    }

    ign_add_file (CVSDOTIGNORE, 1);
    wrap_add_file (CVSDOTWRAPPER, 1);

    /* Make a list for the files.  */
    files = getlist ();

    while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL)
    {
	file = dp->d_name;
	if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
	    continue;
	if (findnode_fn (ilist, file) != NULL)
	    continue;
	if (subdirs)
	{
	    Node *node;

	    node = findnode_fn (entries, file);
	    if (node != NULL
		&& ((Entnode *) node->data)->type == ENT_SUBDIR)
	    {
		char *p;
		int dir;

		/* For consistency with past behaviour, we only ignore
		   this directory if there is a CVS subdirectory.
		   This will normally be the case, but the user may
		   have messed up the working directory somehow.  */
		p = Xasprintf ("%s/%s", file, CVSADM);
		dir = isdir (p);
		free (p);
		if (dir)
		    continue;
	    }
	}

	/* We could be ignoring FIFOs and other files which are neither
	   regular files nor directories here.  */
	if (ign_name (file))
	    continue;

	if (
#ifdef DT_DIR
	    dp->d_type != DT_UNKNOWN ||
#endif
	    lstat (file, &sb) != -1)
	{

	    if (
#ifdef DT_DIR
		dp->d_type == DT_DIR
		|| (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
#else
		S_ISDIR (sb.st_mode)
#endif
		)
	    {
		if (!subdirs)
		{
		    char *temp = Xasprintf ("%s/%s", file, CVSADM);
		    if (isdir (temp))
		    {
			free (temp);
			continue;
		    }
		    free (temp);
		}
	    }
#ifdef S_ISLNK
	    else if (
#ifdef DT_DIR
		     dp->d_type == DT_LNK
		     || (dp->d_type == DT_UNKNOWN && S_ISLNK (sb.st_mode))
#else
		     S_ISLNK (sb.st_mode)
#endif
		     )
	    {
		continue;
	    }
#endif
	}

	p = getnode ();
	p->type = FILES;
	p->key = xstrdup (file);
	(void) addnode (files, p);
    }
    if (errno != 0)
	error (0, errno, "error reading current directory");
    (void) CVS_CLOSEDIR (dirp);

    sortlist (files, fsortcmp);
    for (p = files->list->next; p != files->list; p = p->next)
	(*proc) (p->key, xdir);
    dellist (&files);
}
Example #5
0
int cvsrename(int argc, char **argv)
{
    int c;
    int err = 0;
	char *repos_file1, *repos_file2;
	char *root1, *root2;
	const char *filename1, *filename2, *dir1, *dir2;
	int rootlen;
	List *ent,*ent2;
	Node *node;
	Entnode *entnode;
	const char *cwd;

	if (argc == -1)
		usage (rename_usage);

	quiet = 0;

    optind = 0;
    while ((c = getopt (argc, argv, "+q")) != -1)
    {
	switch (c)
	{
	case 'q':
		quiet = 1;
		break;
    case '?':
    default:
		usage (rename_usage);
		break;
	}
    }
    argc -= optind;
    argv += optind;

	if(argc!=2)
	{
		usage(rename_usage);
	};

	error(0,0,"Warning: rename is still experimental and may not behave as you would expect");

	if(current_parsed_root->isremote)
	{
		if(!supported_request("Rename"))
			error(1,0,"Remote server does not support rename");
		if(!supported_request("Can-Rename"))
			error(1,0,"Renames are currently disabled");
	}

	if(!strcmp(argv[0],argv[1]))
		return 0;

	rootlen = strlen(current_parsed_root->directory);

	if(!isfile(argv[0]))
		error(1,0,"%s does not exist",argv[0]);

	if(isfile(argv[1]) && fncmp(argv[0],argv[1])) /* We allow case renames (on Unix this is redundant) */
		error(1,0,"%s already exists",argv[1]);

	if(isdir(argv[0]))
		error(1,0,"Directory renames are not currently supported");

	validate_file(argv[0],&root1, &repos_file1, &filename1, &dir1, 1);
	validate_file(argv[1],&root2, &repos_file2, &filename2, &dir2, 0);

	if(strcmp(root1,root2) || strcmp(root1,current_parsed_root->original))
		error(1,0,"%s and %s are in different repositories",argv[0],argv[1]);

	xfree(root1);
	xfree(root2);

	repos_file1 = (char*)xrealloc(repos_file1, strlen(filename1)+strlen(repos_file1)+rootlen+10);
	repos_file2 = (char*)xrealloc(repos_file2, strlen(filename2)+strlen(repos_file2)+rootlen+10);
	memmove(repos_file1+rootlen+1,repos_file1,strlen(repos_file1)+1);
	memmove(repos_file2+rootlen+1,repos_file2,strlen(repos_file2)+1);
	strcpy(repos_file1,current_parsed_root->directory);
	strcpy(repos_file2,current_parsed_root->directory);
	repos_file1[rootlen]='/';
	repos_file2[rootlen]='/';
	strcat(repos_file1,"/");
	strcat(repos_file2,"/");
	strcat(repos_file1,filename1);
	strcat(repos_file2,filename2);

	if(fncmp(argv[0],argv[1]))
		set_mapping(dir2,repos_file2+rootlen+1,""); /* Delete old file */
	if(fncmp(dir1,dir2))
		set_mapping(dir1,repos_file1+rootlen+1,""); 
	set_mapping(dir2,repos_file1+rootlen+1,repos_file2+rootlen+1); /* Rename to new file */

	cwd = xgetwd();
	if(CVS_CHDIR(dir1))
		error(1,errno,"Couldn't chdir to %s",dir1);
	ent = Entries_Open(0, NULL);
	node = findnode_fn(ent, filename1);
	entnode=(Entnode*)node->data;

	if(!node)
	{
		error(1,0,"%s is not listed in CVS/Entries",filename1);
		CVS_CHDIR(cwd);
		xfree(cwd);
		return 1;
	}

	if(!fncmp(dir1,dir2))
		Rename_Entry(ent,filename1,filename2);
	else
	{
		if(CVS_CHDIR(cwd))
			error(1,errno,"Couldn't chdir to %s",cwd);
		if(CVS_CHDIR(dir2))
			error(1,errno,"Couldn't chdir to %s",dir2);
		ent2 = Entries_Open(0, NULL);
		if(entnode->type==ENT_FILE)
			Register(ent2,(char*)filename2,entnode->version,entnode->timestamp,entnode->options,entnode->tag,entnode->date,entnode->conflict,entnode->merge_from_tag_1,entnode->merge_from_tag_2,entnode->rcs_timestamp,entnode->edit_revision,entnode->edit_tag,entnode->edit_bugid,entnode->md5);
		else if(entnode->type==ENT_SUBDIR)
			Subdir_Register(ent2,NULL,filename2);
		else
			error(1,0,"Unknown entry type %d in entries file",node->type);

		Entries_Close(ent2);
		if(CVS_CHDIR(cwd))
			error(1,errno,"Couldn't chdir to %s",cwd);
		if(CVS_CHDIR(dir1))
			error(1,errno,"Couldn't chdir to %s",dir1);

		if(entnode->type==ENT_SUBDIR)
			Subdir_Deregister(ent,NULL,filename1);
		else if(entnode->type==ENT_FILE)
			Scratch_Entry(ent,filename1);
		else
			error(1,0,"Unknown entry type %d in entries file",node->type);
	}
	Entries_Close(ent);

	CVS_RENAME(argv[0],argv[1]);

	if(isdir(argv[1]))
	{
		char *tmp=(char*)xmalloc(strlen(argv[1])+strlen(CVSADM_VIRTREPOS)+10);
		FILE *fp;
		sprintf(tmp,"%s/%s",argv[1],CVSADM_VIRTREPOS);
		fp = fopen(tmp,"w");
		if(!fp)
			error(0,errno,"Couldn't write %s",tmp);
		fprintf(fp,"%s\n",repos_file2+rootlen+1);
		fclose(fp);
	}

	xfree(repos_file1);
	xfree(repos_file2);
	xfree(dir1);
	xfree(dir2);
	CVS_CHDIR(cwd);
	xfree(cwd);

    return (err);
}
Example #6
0
/*
 * Finds all the subdirectories of the argument dir and adds them to
 * the specified list.  Sub-directories without a CVS administration
 * directory are optionally ignored.  If ENTRIES is not NULL, all
 * files on the list are ignored.  Returns 0 for success or 1 on
 * error, in which case errno is set to indicate the error.
 */
static int find_dirs (const char *dir, List *list, int checkadm, List *entries, const char *regex)
{
    Node *p;
    char *tmp = NULL;
    size_t tmp_size = 0;
    struct dirent *dp;
    DIR *dirp;
    int skip_emptydir = 0;

    /* First figure out whether we need to skip directories named
       Emptydir.  Except in the CVSNULLREPOS case, Emptydir is just
       a normal directory name.  */
    if (isabsolute (dir)
	&& fnncmp (dir, current_parsed_root->directory, strlen (current_parsed_root->directory)) == 0
	&& ISDIRSEP (dir[strlen (current_parsed_root->directory)])
	&& fncmp (dir + strlen (current_parsed_root->directory) + 1, CVSROOTADM) == 0)
	skip_emptydir = 1;

    /* set up to read the dir */
    if ((dirp = opendir (dir)) == NULL)
	return (1);

    /* read the dir, grabbing sub-dirs */
    errno = 0;
    while ((dp = readdir (dirp)) != NULL)
    {
	if (fncmp (dp->d_name, ".") == 0 ||
	    fncmp (dp->d_name, "..") == 0 ||
	    fncmp (dp->d_name, CVSATTIC) == 0 ||
	    fncmp (dp->d_name, CVSLCK) == 0 ||
	    fncmp (dp->d_name, CVSREP) == 0 ||
		fncmp (dp->d_name, CVSDUMMY) == 0)
	    goto do_it_again;

	/* findnode() is going to be significantly faster than stat()
	   because it involves no system calls.  That is why we bother
	   with the entries argument, and why we check this first.  */
	if (entries != NULL && findnode_fn (entries, dp->d_name) != NULL)
	    goto do_it_again;

	if (skip_emptydir
	    && fncmp (dp->d_name, CVSNULLREPOS) == 0)
	    goto do_it_again;

	    /* don't bother stating ,v files */
	    if (CVS_FNMATCH (RCSPAT, dp->d_name, CVS_CASEFOLD) == 0)
		goto do_it_again;

	    expand_string (&tmp,
			   &tmp_size,
			   strlen (dir) + strlen (dp->d_name) + 10);
	    sprintf (tmp, "%s/%s", dir, dp->d_name);
	    if (!isdir (tmp))
		goto do_it_again;

	/* check for administration directories (if needed) */
	if (checkadm)
	{
	    /* blow off symbolic links to dirs in local dir */
		/* Note that we only get here if we already set tmp
		   above.  */
		if (islink (tmp))
		    goto do_it_again;

	    /* check for new style */
	    expand_string (&tmp,
			   &tmp_size,
			   (strlen (dir) + strlen (dp->d_name)
			    + sizeof (CVSADM) + 10));
	    (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
	    if (!isdir (tmp))
		goto do_it_again;
	}

	/* If there's a regex, test against the name with '/' on the end to signify a directory */
	if(regex)
	{
		strcpy(tmp,dp->d_name);
		strcat(tmp,"/");
		if(!regex_filename_match(regex,tmp))
			goto do_it_again;
	}

	/* put it in the list */
	p = getnode ();
	p->type = DIRS;
	p->key = xstrdup (dp->d_name);
	if (addnode (list, p) != 0)
	    freenode (p);

    do_it_again:
	errno = 0;
    }
    if (errno != 0)
    {
	int save_errno = errno;
	(void) closedir (dirp);
	errno = save_errno;
	return 1;
    }
    (void) closedir (dirp);
    if (tmp != NULL)
	xfree (tmp);
    return (0);
}
Example #7
0
/*
 * Process each of the directories in the list (recursing as we go)
 */
static int do_dir_proc (Node *p, void *closure)
{
    struct frame_and_entries *frent = (struct frame_and_entries *) closure;
    struct recursion_frame *frame = frent->frame;
    struct recursion_frame xframe;
    char *dir = p->key;
    char *newrepos, *virtrepos;
    List *sdirlist;
    char *srepository;
	char *smapped_repository;
    Dtype dir_return = R_PROCESS;
	Dtype hint;
    int stripped_dot = 0;
    int err = 0;
    struct saved_cwd cwd;
    char *saved_update_dir;
    int process_this_directory = 1;
	int directory_opened = 0;
	char *old_update_repos = NULL;
	int directory_not_valid = 0;

    if (fncmp (dir, CVSADM) == 0
		|| fncmp (dir, CVSDUMMY) == 0)
    {
	/* This seems to most often happen when users (beginning users,
	   generally), try "cvs ci *" or something similar.  On that
	   theory, it is possible that we should just silently skip the
	   CVSADM directories, but on the other hand, using a wildcard
	   like this isn't necessarily a practice to encourage (it operates
	   only on files which exist in the working directory, unlike
	   regular CVS recursion).  */

	/* FIXME-reentrancy: printed_cvs_msg should be in a "command
	   struct" or some such, so that it gets cleared for each new
	   command (this is possible using the remote protocol and a
	   custom-written client).  The struct recursion_frame is not
	   far back enough though, some commands (commit at least)
	   will call start_recursion several times.  An alternate solution
	   would be to take this whole check and move it to a new function
	   validate_arguments or some such that all the commands call
	   and which snips the offending directory from the argc,argv
	   vector.  */
	static int printed_cvs_msg = 0;
	if (!printed_cvs_msg)
	{
	    error (0, 0, "warning: directory %s specified in argument",
		   dir);
	    error (0, 0, "but CVS uses %s for its own purposes; skipping %s directory", dir, dir);
	    printed_cvs_msg = 1;
	}
	return 0;
    }

    saved_update_dir = update_dir;
    update_dir = (char*)xmalloc (strlen (saved_update_dir)
			  + strlen (dir)
			  + 5);
    strcpy (update_dir, saved_update_dir);

    /* set up update_dir - skip dots if not at start */
    if (strcmp (dir, ".") != 0)
    {
	if (update_dir[0] != '\0')
	{
	    (void) strcat (update_dir, "/");
	    (void) strcat (update_dir, dir);
	}
	else
	    (void) strcpy (update_dir, dir);

	/*
	 * Here we need a plausible repository name for the sub-directory. We
	 * create one by concatenating the new directory name onto the
	 * previous repository name.  The only case where the name should be
	 * used is in the case where we are creating a new sub-directory for
	 * update -d and in that case the generated name will be correct.
	 */
	if (repository == NULL)
	{
	    newrepos = xstrdup ("");
		virtrepos = xstrdup ("");
	}
	else
	{
		if(frame->which&W_LOCAL)
		{
			char *d=(char*)xmalloc(strlen(dir)+strlen(CVSADM_REP)+32);
			sprintf(d,"%s/%s",dir,CVSADM_REP);
			if(isfile(d))
				virtrepos = Name_Repository(dir,update_dir);
			else
			{
				virtrepos = (char*)xmalloc (strlen (repository) + strlen (dir) + 5);
				sprintf (virtrepos, "%s/%s", repository, dir);
			}
			xfree(d);

		}
		else
		{
			virtrepos = (char*)xmalloc (strlen (repository) + strlen (dir) + 5);
			sprintf (virtrepos, "%s/%s", repository, dir);
		}
		if(!current_parsed_root->isremote)
		{
			newrepos = map_repository(virtrepos);
			if(!newrepos)
				error(1,0,"Internal error - couldn't map %s to anything",virtrepos);
		}
		else
			newrepos = xstrdup(virtrepos);
	}
    }
    else
    {
	if (update_dir[0] == '\0')
	    (void) strcpy (update_dir, dir);

	if (repository == NULL)
	{
	    newrepos = xstrdup ("");
		virtrepos = xstrdup ("");
	}
	else
	{
	    newrepos = xstrdup (mapped_repository);
		virtrepos = xstrdup (repository);
	}
    }

	/* Check to see that the CVSADM directory, if it exists, seems to be
       well-formed.  It can be missing files if the user hit ^C in the
       middle of a previous run.  We want to (a) make this a nonfatal
       error, and (b) make sure we print which directory has the
       problem.

       Do this before the direntproc, so that (1) the direntproc
       doesn't have to guess/deduce whether we will skip the directory
       (e.g. send_dirent_proc and whether to send the directory), and
       (2) so that the warm fuzzy doesn't get printed if we skip the
       directory.  */
    if (frame->which & W_LOCAL)
    {
	char *cvsadmdir;

	cvsadmdir = (char*)xmalloc (strlen (dir)
			     + sizeof (CVSADM_REP)
			     + sizeof (CVSADM_ENT)
			     + 80);

	strcpy (cvsadmdir, dir);
	strcat (cvsadmdir, "/");
	strcat (cvsadmdir, CVSADM);
	if (isdir (cvsadmdir))
	{
	    strcpy (cvsadmdir, dir);
	    strcat (cvsadmdir, "/");
	    strcat (cvsadmdir, CVSADM_REP);
	    if (!isfile (cvsadmdir))
	    {
		/* Some commands like update may have printed "? foo" but
		   if we were planning to recurse, and don't on account of
		   CVS/Repository, we want to say why.  */
		error (0, 0, "ignoring %s (%s missing)", update_dir,
		       CVSADM_REP);
		dir_return = R_SKIP_ALL;
	    }

	    /* Likewise for CVS/Entries.  */
	    if (dir_return != R_SKIP_ALL)
	    {
		strcpy (cvsadmdir, dir);
		strcat (cvsadmdir, "/");
		strcat (cvsadmdir, CVSADM_ENT);
		if (!isfile (cvsadmdir))
		{
		    /* Some commands like update may have printed "? foo" but
		       if we were planning to recurse, and don't on account of
		       CVS/Repository, we want to say why.  */
		    error (0, 0, "ignoring %s (%s missing)", update_dir,
			   CVSADM_ENT);
		    dir_return = R_SKIP_ALL;
		}
	    }
	}
	xfree (cvsadmdir);
    }

    /* Only process this directory if the root matches.  This nearly
       duplicates code in do_recursion. */

	/* If -d was specified, it should override CVS/Root.

	   In the single-repository case, it is long-standing CVS behavior
	   and makes sense - the user might want another access method,
	   another server (which mounts the same repository), &c.

	   In the multiple-repository case, -d overrides all CVS/Root
	   files.  That is the only plausible generalization I can
	   think of.  */
    if (!(frame->which&W_FAKE) && CVSroot_cmdline == NULL && !server_active)
    {
	char *this_root = Name_Root (dir, update_dir);
	if (this_root != NULL)
	{
	    if (findnode_fn (root_directories, this_root) == NULL)
	    {
		/* Add it to our list. */

		Node *n = getnode ();
		n->type = NT_UNKNOWN;
		n->key = xstrdup (this_root);

		if (addnode (root_directories, n))
		    error (1, 0, "cannot add new CVSROOT %s", this_root);

	    }

	    process_this_directory = (fncmp (current_parsed_root->original, this_root) == 0);

	    xfree (this_root);
	}
    }
    /*
     * Do we have access to this directory?
     */
	if(!current_parsed_root->isremote)
	{
		const char *tag=NULL;
		const char *date=NULL;
		int nonbranch=0;
		const char *message;
		const char *v_msg;

		/* before we do anything else, see if we have any
		   per-directory tags */
		ParseTag (&tag, &date, &nonbranch, NULL);
		if (! verify_access (frame->permproc, newrepos, NULL, update_dir, frame->tag?frame->tag:tag,&message, &v_msg))
		{
			if(frame->permproc!=verify_read)
			{
			  if(tag)
			    error (0, 0, "User '%s' cannot %s %s on tag/branch %s", CVS_Username, v_msg, fn_root(virtrepos), tag);
			  else
			    error (0, 0, "User '%s' cannot %s %s", CVS_Username, v_msg, fn_root(virtrepos));
			  if(message)
				error(0,0,"%s",message);	
			}
			dir_return = R_SKIP_ALL;
		}
		xfree(tag);
		xfree(date);
	}

	if(dir_return!=R_SKIP_ALL)
	{
		/* Generic behavior.  I don't see a reason to make the caller specify
		a direntproc just to get this.  */
		if ((frame->which & (W_LOCAL|W_FAKE)))
		{
			if(!isdir (dir))
				hint = R_SKIP_ALL;
			else
				hint = R_PROCESS;
		}
		else if(!isdir(newrepos))
			hint = R_SKIP_ALL;
		else
			hint = R_PROCESS;
	}

    if (dir_return == R_SKIP_ALL || dir_return == R_ERROR)
	;
    else if(process_this_directory)
	{
		int directory_opened_status=-1;
		const char *dirversion=NULL;
		const char *dirtag=NULL;
		const char *dirdate=NULL;
		int dirnonbranch=0;

		if(frame->predirentproc != NULL)
		{
			frame->predirentproc (frame->callerdat, dir, newrepos,
							update_dir, frent->entries, virtrepos, hint);
		}

		/* before we do anything else, see if we have any
			per-directory tags */
		ParseTag_Dir (dir, &dirtag, &dirdate, &dirnonbranch, &dirversion);
		directory_opened_status=open_directory(newrepos,dir,dirtag,dirdate,dirnonbranch,dirversion,current_parsed_root->isremote);
		if (directory_opened_status!=-1)
			directory_opened = 1;
		xfree(dirversion);
		xfree(dirtag);
		xfree(dirdate);
		if(!current_parsed_root->isremote)
			fileattr_startdir(newrepos);
	}

	/* call-back dir entry proc (if any) */
    if (dir_return == R_SKIP_ALL || dir_return == R_ERROR)
		;
    else if (frame->direntproc != NULL)
    {
	/* If we're doing the actual processing, call direntproc.
           Otherwise, assume that we need to process this directory
           and recurse. FIXME. */

	if (process_this_directory)
	    dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
					    update_dir, frent->entries, virtrepos, hint);
	else
	    dir_return = R_PROCESS;
    }
    else
    {
	/* Generic behavior.  I don't see a reason to make the caller specify
	   a direntproc just to get this.  */
		dir_return = hint;
    }

    /* only process the dir if the return code was 0 */
    if (dir_return != R_SKIP_ALL && dir_return !=R_ERROR)
    {
		/* save our current directory and static vars */
        if (save_cwd (&cwd))
			error_exit ();

		sdirlist = dirlist;
		srepository = repository;
		smapped_repository = mapped_repository;
		dirlist = NULL;

		/* cd to the sub-directory */
		if (!(frame->which&(W_LOCAL|W_FAKE)))
		{
			if ( CVS_CHDIR (newrepos) < 0)
			{
				error (1, errno, "could not chdir to %s", fn_root(newrepos));
			}
		}
		else
		{
			if ( !(frame->which&W_FAKE) && CVS_CHDIR (dir) < 0)
			{
				if(!noexec)
					error (1, errno, "could not chdir to %s", fn_root(update_dir));
				else
					directory_not_valid=1;
			}

		}
		old_update_repos = update_repos;
		update_repos = xstrdup(newrepos);

		/* honor the global SKIP_DIRS (a.k.a. local) */
		if (frame->flags == R_SKIP_DIRS)
			dir_return = R_SKIP_DIRS;

		/* remember if the `.' will be stripped for subsequent dirs */
		if (strcmp (update_dir, ".") == 0)
		{
			update_dir[0] = '\0';
			stripped_dot = 1;
		}

		/* make the recursive call */
		xframe = *frame;
		xframe.flags = dir_return;
		if(directory_not_valid)
		{
			xframe.which &= ~W_LOCAL;
			xframe.which |= W_FAKE;
		}
		err += do_recursion (&xframe, 0);

		/* put the `.' back if necessary */
		if (stripped_dot)
			(void) strcpy (update_dir, ".");

		/* call-back dir leave proc (if any) */
		if (process_this_directory && frame->dirleaveproc != NULL)
			err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
						frent->entries);

		if(directory_opened)
			close_directory();

		/* get back to where we started and restore state vars */
		if (restore_cwd (&cwd, NULL))
			error_exit ();

		xfree(update_repos);
		update_repos = old_update_repos;

		free_cwd (&cwd);
		dirlist = sdirlist;
		repository = srepository;
		mapped_repository = smapped_repository;
    }
	else
	{
		if(directory_opened)
			close_directory();
		fileattr_write();
		fileattr_free();
	}

    xfree (newrepos);
	xfree (virtrepos);
    xfree (update_dir);
    update_dir = saved_update_dir;

	if(dir_return == R_ERROR)
		err++;

    return (err);
}
Example #8
0
/*
 * Process each of the files in the list with the callback proc
 */
static int do_file_proc (Node *p, void *closure)
{
    struct frame_and_file *frfile = (struct frame_and_file *)closure;
    struct file_info *finfo = frfile->finfo;
    int ret;
	Entnode *e;
	const char *mapped_name;
	const char *mapped_file_repository = NULL;

	if(!current_parsed_root->isremote)
		mapped_name = map_filename(finfo->virtual_repository, p->key, &mapped_file_repository);
	else
		mapped_name = xstrdup(p->key);

	finfo->file = p->key;
	finfo->mapped_file = mapped_name;

	char *ff = (char*)xmalloc (strlen (finfo->file)
			       + strlen (finfo->update_dir)
			       + 2);
	finfo->fullname = ff;
    ff[0] = '\0';
    if (finfo->update_dir[0] != '\0')
    {
		strcat (ff, finfo->update_dir);
		strcat (ff, "/");
    }
    strcat (ff, finfo->file);

    if (frfile->frame->dosrcs && mapped_file_repository)
    {
	finfo->rcs = RCS_parse (finfo->mapped_file, mapped_file_repository);

	/* OK, without W_LOCAL the error handling becomes relatively
	   simple.  The file names came from readdir() on the
	   repository and so we know any ENOENT is an error
	   (e.g. symlink pointing to nothing).  Now, the logic could
	   be simpler - since we got the name from readdir, we could
	   just be calling RCS_parsercsfile.  */
	if (finfo->rcs == NULL
	    && !(frfile->frame->which & W_LOCAL))
	{
	    error (0, 0, "could not read RCS file for %s", fn_root(finfo->fullname));
	    xfree (finfo->fullname);
	    cvs_flushout ();
	    return 0;
	}
    }
    else 
        finfo->rcs = (RCSNode *) NULL;

	p = findnode_fn(finfo->entries,finfo->file);
	if(p)
	{
		const char *message;
		const char *v_type;

		e = (Entnode *)p->data;

		if (! verify_access(frfile->frame->permproc, finfo->repository, finfo->file, finfo->update_dir, e->tag, &message, &v_type))
		{
			if(e->tag && !quiet && !(frfile->frame->which&W_QUIET))
				error (0, 0, "User %s is unable to %s %s/%s on branch/tag %s",CVS_Username,v_type,finfo->update_dir,finfo->file,e->tag);
			else if(!quiet && !(frfile->frame->which&W_QUIET))
				error (0, 0, "User %s is unable to %s %s/%s",CVS_Username,v_type,finfo->update_dir,finfo->file);
			if(message && !quiet && !(frfile->frame->which&W_QUIET))
				error (0, 0, "%s", message);
			ret = 1;
		}
		else
			ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
	}
	else
		ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);

    freercsnode(&finfo->rcs);
    xfree (finfo->fullname);
	xfree (mapped_name);
	xfree (mapped_file_repository);

    /* Allow the user to monitor progress with tail -f.  Doing this once
       per file should be no big deal, but we don't want the performance
       hit of flushing on every line like previous versions of CVS.  */
    cvs_flushout ();

    return (ret);
}
Example #9
0
/*
 * Implement the recursive policies on the local directory.  This may be
 * called directly, or may be called by start_recursion
 */
static int do_recursion (struct recursion_frame *frame, int top_level)
{
    int err = 0;
    int dodoneproc = 1;
    char *srepository;
    List *entries = NULL;
    int should_readlock;
    int process_this_directory = 1;

    /* do nothing if told */
    if (frame->flags == R_SKIP_ALL)
	return (0);

    should_readlock = noexec ? 0 : frame->readlock;

    /* Check the value in CVSADM_ROOT and see if it's in the list.  If
       not, add it to our lists of CVS/Root directories and do not
       process the files in this directory.  Otherwise, continue as
       usual.  THIS_ROOT might be NULL if we're doing an initial
       checkout -- check before using it.  The default should be that
       we process a directory's contents and only skip those contents
       if a CVS/Root file exists. 

       If we're running the server, we want to process all
       directories, since we're guaranteed to have only one CVSROOT --
       our own.  */

	/* If -d was specified, it should override CVS/Root.

	   In the single-repository case, it is long-standing CVS behavior
	   and makes sense - the user might want another access method,
	   another server (which mounts the same repository), &c.

	   In the multiple-repository case, -d overrides all CVS/Root
	   files.  That is the only plausible generalization I can
	   think of.  */
	if(!(frame->which&W_FAKE) && CVSroot_cmdline == NULL && ! server_active)
    {
	char *this_root = Name_Root ((char *) NULL, update_dir);
	if (this_root != NULL)
	{
	    if (findnode_fn (root_directories, this_root) == NULL)
	    {
		/* Add it to our list. */

		Node *n = getnode ();
		n->type = NT_UNKNOWN;
		n->key = xstrdup (this_root);

		if (addnode (root_directories, n))
		    error (1, 0, "cannot add new CVSROOT %s", this_root);
	
	    }
	
	    process_this_directory =
		    (fncmp (current_parsed_root->original, this_root) == 0);

	    xfree (this_root);
	}
    }

    /*
     * Fill in repository with the current repository
     */
    if (frame->which & W_LOCAL)
    {
		if (isdir (CVSADM))
			repository = Name_Repository ((char *) NULL, update_dir);
		else
			repository = NULL;
    }
    else
	{
		if(update_repos) /* May have been preloaded by checkout */
			repository = xstrdup(update_repos);
		else
		{
			repository = (char*)xmalloc(strlen(current_parsed_root->directory)+strlen(update_dir)+10);
			sprintf(repository,"%s/%s",current_parsed_root->directory,update_dir);
		}
    }

	if(repository && ISDIRSEP(repository[strlen(repository)-1]))
		repository[strlen(repository)-1]='\0';
    srepository = repository;		/* remember what to free */

	if(repository && !current_parsed_root->isremote)
	{
		mapped_repository = map_repository(repository);
	}
	else
	{
		mapped_repository=xstrdup(repository);
	}

	xfree(last_repository);
	last_repository = xstrdup(mapped_repository);

    /*
     * Do we have access to this directory?
     */
	/* Note that for a recursion this is done already in do_dir_proc... */
	if(top_level)
	{
		if(repository && !current_parsed_root->isremote)
		{
			const char *tag;
			const char *message;
			const char *v_msg;

			ParseTag(&tag, NULL, NULL, NULL);

			if (! verify_access(frame->permproc, mapped_repository, NULL, update_dir,frame->tag?frame->tag:tag,&message, &v_msg))
			{
				if(tag)
					error (0, 0, "User '%s' cannot %s %s on tag/branch %s", CVS_Username, v_msg, fn_root(repository), tag);
				else
					error (0, 0, "User '%s' cannot %s %s", CVS_Username, v_msg, fn_root(repository));
				if(message)
					error (0, 0, "%s", message);
				return (1);
			}
			xfree(tag);
			fileattr_startdir (mapped_repository);
		}
	}

    /*
     * The filesdoneproc needs to be called for each directory where files
     * processed, or each directory that is processed by a call where no
     * directories were passed in.  In fact, the only time we don't want to
     * call back the filesdoneproc is when we are processing directories that
     * were passed in on the command line (or in the special case of `.' when
     * we were called with no args
     */
    if (dirlist != NULL && filelist == NULL)
		dodoneproc = 0;

    /*
     * If filelist or dirlist is already set, we don't look again. Otherwise,
     * find the files and directories
     */
    if (filelist == NULL && dirlist == NULL)
    {
	/* both lists were NULL, so start from scratch */
	if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
	{
	    int lwhich = frame->which;

	    /* In the !(which & W_LOCAL) case, we filled in repository
	       earlier in the function.  In the (which & W_LOCAL) case,
	       the Find_Names function is going to look through the
	       Entries file.  If we do not have a repository, that
	       does not make sense, so we insist upon having a
	       repository at this point.  Name_Repository will give a
	       reasonable error message.  */
	    if (repository == NULL)
			repository = Name_Repository ((char *) NULL, update_dir);
		else
		if(mapped_repository == NULL)
			mapped_repository = map_repository(repository);

	    /* find the files and fill in entries if appropriate */
	    if (process_this_directory)
	    {
			filelist = Find_Names (mapped_repository, lwhich, frame->aflag,
						&entries, repository);
			if (filelist == NULL)
			{
				error (0, 0, "skipping directory %s", update_dir);
				/* Note that Find_Directories and the filesdoneproc
				in particular would do bad things ("? foo.c" in
				the case of some filesdoneproc's).  */
				goto skip_directory;
			}
	    }
	}

	if (frame->flags == R_SKIP_DIRS && !(frame->which&W_LOCAL) && nonrecursive_module(repository))
		frame->flags = R_SKIP_DIRS;

	/* find sub-directories if we will recurse */
	if (frame->flags != R_SKIP_DIRS)
	    dirlist = Find_Directories (
		process_this_directory ? mapped_repository : NULL,
		frame->which, entries, repository);
    }
    else
    {
		/* something was passed on the command line */
		if (filelist != NULL && frame->fileproc != NULL)
		{
			/* we will process files, so pre-parse entries */
			if (frame->which & W_LOCAL)
				entries = Entries_Open (frame->aflag, NULL);
		}
    }

    /* process the files (if any) */
    if (process_this_directory && filelist != NULL && frame->fileproc)
    {
	struct file_info finfo_struct;
	struct frame_and_file frfile;

	/* read lock it if necessary */
	if (should_readlock && mapped_repository && Reader_Lock (mapped_repository) != 0)
	    error (1, 0, "read lock failed - giving up");

	/* For the server, we handle notifications in a completely different
	   place (server_notify).  For local, we can't do them here--we don't
	   have writelocks in place, and there is no way to get writelocks
	   here.  */
	if (current_parsed_root->isremote)
	    client_notify_check (repository, update_dir);

	finfo_struct.repository = mapped_repository;
	finfo_struct.update_dir = update_dir;
	finfo_struct.entries = entries;
	finfo_struct.virtual_repository = repository;
	/* do_file_proc will fill in finfo_struct.file.  */

	frfile.finfo = &finfo_struct;
	frfile.frame = frame;

	/* process the files */
	err += walklist (filelist, do_file_proc, &frfile);

	/* unlock it */
	if (should_readlock)
	    Lock_Cleanup_Directory();

	/* clean up */
	if (filelist)
	dellist (&filelist);
    }

    /* call-back files done proc (if any) */
    if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
	{
		err = frame->filesdoneproc (frame->callerdat, err, (char*)mapped_repository,
				    (char*)(update_dir[0] ? update_dir : "."),
				    entries);
	}

 skip_directory:
	if(repository && !current_parsed_root->isremote)
	{
		fileattr_write ();
		fileattr_free ();
	}

    /* process the directories (if necessary) */
    if (dirlist != NULL)
    {
		// BUGME!!!
		/* for some reason this code path is not entered some times when it SHOULD be, eg:
		   after a rename!  This means the .direcrory_history file is not created !!!! */
	struct frame_and_entries frent;

	frent.frame = frame;
	frent.entries = entries;
	err += walklist (dirlist, do_dir_proc, (void *) &frent);
    }
	if (dirlist)
    dellist (&dirlist);

    if (entries) 
    {
	Entries_Close (entries);
	entries = NULL;
    }

    /* free the saved copy of the pointer if necessary */
	xfree (srepository);
	xfree (mapped_repository);
	repository = NULL;

    return (err);
}
Example #10
0
static int unroll_files_proc (Node *p, void *closure)
{
	int directory_opened = 0;
    Node *n;
    struct recursion_frame *frame = (struct recursion_frame *) closure;
    int err = 0;
    List *save_dirlist;
    char *save_update_dir = NULL;
    struct saved_cwd cwd;
	char *dir = p->key;

    /* if this dir was also an explicitly named argument, then skip
       it.  We'll catch it later when we do dirs. */
    n = findnode_fn (dirlist, dir);
    if (n != NULL)
	return (0);

    /* otherwise, call dorecusion for this list of files. */
    filelist = (List *) p->data;
    p->data = NULL;
    save_dirlist = dirlist;
    dirlist = NULL;

    if (strcmp(dir, ".") != 0)
    {
        if (save_cwd (&cwd))
	    error_exit ();
	if ( CVS_CHDIR (dir) < 0)
	    error (1, errno, "could not chdir to %s", dir);

	save_update_dir = update_dir;
	update_dir = (char*)xmalloc (strlen (save_update_dir)
				  + strlen (dir)
				  + 5);
	strcpy (update_dir, save_update_dir);

	if (*update_dir != '\0')
	    (void) strcat (update_dir, "/");

	(void) strcat (update_dir, dir);
    }


	if(frame->which&W_LOCAL)
	{
		int directory_opened_status=-1;
		const char *version;
		const char *tag;
		const char *date;
		int nonbranch;
		char *repository = Name_Repository(NULL,update_dir);

		ParseTag(&tag, &date, &nonbranch, &version);
		directory_opened_status=open_directory(repository,".",tag,date,nonbranch,version,current_parsed_root->isremote);
		if (directory_opened_status!=-1)
			directory_opened = 1;
		xfree(repository);
		xfree(version);
		xfree(tag);
		xfree(date);
	}
	else
	{
		int directory_opened_status=-1;
		/* we can't simply call Name_Repository() here, if we do we get:
		-> Name_Repository((null),cvsnt/src)
		cvs server: in directory cvsnt/src:
		cvs [server aborted]: CVS directory without administration files present.  Cannot continue until this directory is deleted or renamed.
		*/
		directory_opened_status=open_directory(NULL,".",NULL,NULL,0,NULL,1); /* We want to open the directory anyway, so treat it as 'remote'.  This only affects rlog, etc. */
		if (directory_opened_status!=-1)
			directory_opened = 1;
	}

    err += do_recursion (frame, 1);

	if(directory_opened)
		close_directory();

    if (save_update_dir != NULL)
    {
	xfree (update_dir);
	update_dir = save_update_dir;

	if (restore_cwd (&cwd, NULL))
	    error_exit ();
	free_cwd (&cwd);
    }

    dirlist = save_dirlist;
    if (filelist)
	dellist (&filelist);
    return(err);
}