I started on a patch to add a list option to cvs.  It came about because
I wanted to run things like bonsai and cvsweb on remote repositories.  I
guess you could say "I scratched an itch", as Eric S. Raymond has put
it.

It is based on patch.c, which I belatedly discovered might not have been
the best idea, as it uses the do_module function which refers to trigger
scripts in the comments.  It currently lists all the files, as expected,
but I am not sure how to get it to list all the directories.  If someone
could help with this is would be appreciated.

The are XX options available:
-r to get a list of files based on tag/branch
-D to get a list of files based on date
-l to turn off recursion
-R to force recursion
-s to not show state, author, version, and date for each file
-a to list all files, even if they have state == dead
-b to not show directory name for each file
-? to get help

The default output has state, version, author, date, directory, and
filename listed for each file in the module.  The author field has a
width of 35, which is fairly large, but is the longest length username
on mozilla.org's CVS repository.

I have not implemented any functionality to list the top level
directories, but rather require the user to specify the module or
directory to list.  I chose to do this because some repository
administrators use the fact that you can't easily get a list of
modules/top level directories as a feature.

I would appreciate any assistance with this option, and would hope that
this will eventually make it into a release of cvs.

Thanks.
Peter Bowen


Index: cvs.h
===================================================================
RCS file: /home2/cvsroot/ccvs/src/cvs.h,v
retrieving revision 1.206
diff -u -r1.206 cvs.h
--- cvs.h       2000/10/12 15:33:21     1.206
+++ cvs.h       2000/10/16 21:40:41
@@ -314,7 +314,7 @@
 /* The type of request that is being done in do_module() */
 enum mtype
 {
-    CHECKOUT, TAG, PATCH, EXPORT
+    CHECKOUT, TAG, PATCH, EXPORT, CVSLIST
 };
 
 /*
@@ -856,6 +856,7 @@
 extern int cvsstatus PROTO((int argc, char **argv));
 extern int cvstag PROTO((int argc, char **argv));
 extern int version PROTO((int argc, char **argv));
+extern int cvslist PROTO((int argc, char **argv));
 
 extern unsigned long int lookup_command_attribute PROTO((char *));
 
Index: main.c
===================================================================
RCS file: /home2/cvsroot/ccvs/src/main.c,v
retrieving revision 1.154
diff -u -r1.154 main.c
--- main.c      2000/10/11 16:17:53     1.154
+++ main.c      2000/10/16 21:40:41
@@ -114,6 +114,7 @@
 #if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT)
     { "kserver",  NULL,       NULL,        server }, /* placeholder */
 #endif
+    { "list",    "ls",       NULL,        cvslist },
     { "log",      "lo",       "rlog",      cvslog },
 #ifdef AUTH_CLIENT_SUPPORT
     { "login",    "logon",    "lgn",       login },
@@ -329,6 +330,7 @@
        something doesn't use the WD. */
     if ((strcmp (cmd_name, "checkout") != 0) &&
         (strcmp (cmd_name, "init") != 0) &&
+        (strcmp (cmd_name, "list") != 0) &&
         (strcmp (cmd_name, "login") != 0) &&
        (strcmp (cmd_name, "logout") != 0) &&
         (strcmp (cmd_name, "rdiff") != 0) &&
@@ -350,6 +352,7 @@
         (strcmp (cmd_name, "editors") != 0) &&
         (strcmp (cmd_name, "export") != 0) &&
         (strcmp (cmd_name, "history") != 0) &&
+        (strcmp (cmd_name, "list") != 0) &&
         (strcmp (cmd_name, "log") != 0) &&
         (strcmp (cmd_name, "noop") != 0) &&
         (strcmp (cmd_name, "watchers") != 0) &&

/*
 * Copyright (c) 1992, Brian Berliner and Jeff Polk
 * Copyright (c) 1989-1992, Brian Berliner
 * 
 * You may distribute under the terms of the GNU General Public License as
 * specified in the README file that comes with the CVS source distribution.
 * 
 * List
 *
 * Print a list of file under version control
 */

#include "cvs.h"
#include "getline.h"

static RETSIGTYPE list_cleanup PROTO((void));
static Dtype list_dirproc PROTO ((void *callerdat, char *dir,
				   char *repos, char *update_dir,
				   List *entries));
static int list_fileproc PROTO ((void *callerdat, struct file_info *finfo));
static int list_proc PROTO((int argc, char **argv, char *xwhere,
		       char *mwhere, char *mfile, int shorten,
		       int local_specified, char *mname, char *msg));

static int force_tag_match = 1;
static int list_short = 0;
static int list_all = 0;
static int list_bare = 0;
static int local = 0;
static char *options = NULL;
static char *rev = NULL;
static int rev_validated = 0;
static char *date = NULL;

static const char *const list_usage[] =
{
    "Usage: %s %s [-flR] [-s|-t] \n",
    "    [-r rev | -D date] modules...\n",
    "\t-f\tForce a head revision match if tag/date not found.\n",
    "\t-l\tLocal directory only, not recursive\n",
    "\t-R\tProcess directories recursively.\n",
    "\t-s\tShort list - only list filename.\n",
    "\t-a\tAll files - list all files, including dead ones.\n",
    "\t-b\tBare - don't show directory name with each file.\n",
    "\t-D date\tDate.\n",
    "\t-r rev\tRevision - symbolic or numeric.\n",
    "(Specify the --help global option for a list of other help options)\n",
    NULL
};

int
cvslist (argc, argv)
    int argc;
    char **argv;
{
    register int i;
    int c;
    int err = 0;
    DBM *db;

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

    optind = 0;
    while ((c = getopt (argc, argv, "+V:k:cuibftsaQqlRD:r:")) != -1)
    {
	switch (c)
	{
	    case 'Q':
	    case 'q':
#ifdef SERVER_SUPPORT
		/* The CVS 1.5 client sends these options (in addition to
		   Global_option requests), so we must ignore them.  */
		if (!server_active)
#endif
		    error (1, 0,
			   "-q or -Q must be specified before \"%s\"",
			   command_name);
		break;
	    case 'f':
		force_tag_match = 0;
		break;
	    case 'l':
		local = 1;
		break;
	    case 'R':
		local = 0;
		break;
	    case 's':
		list_short = 1;
		break;
	    case 'a':
		list_all = 1;
		break;
	    case 'b':
		list_bare = 1;
		break;
	    case 'D':
		date = Make_Date (optarg);
		break;
	    case 'r':
		rev = optarg;
		break;
	    case '?':
	    default:
		usage (list_usage);
		break;
	}
    }
    argc -= optind;
    argv += optind;

    /* Sanity checks */
    if (argc < 1)
	usage (list_usage);

    /* if options is NULL, make it a NULL string */
    if (options == NULL)
	options = xstrdup ("");

#ifdef CLIENT_SUPPORT
    if (client_active)
    {
	/* We're the client side.  Fire up the remote server.  */
	start_server ();
	
	ign_setup ();

	if (local)
	    send_arg("-l");
	if (!force_tag_match)
	    send_arg("-f");
	if (list_short)
	    send_arg("-s");
	if (list_all)
	    send_arg("-a");
	if (list_bare)
	    send_arg("-b");

	if (rev)
	    option_with_arg ("-r", rev);
	if (date)
	    client_senddate (date);
	if (options[0] != '\0')
	    send_arg (options);

	{
	    int i;
	    for (i = 0; i < argc; ++i)
		send_arg (argv[i]);
	}

	send_to_server ("list\012", 0);
        return get_responses_and_close ();
    }
#endif

    /* turn this off, as we nolonger have tempfiles */
#if 0
    /* clean up if we get a signal */
#ifdef SIGABRT
    (void) SIG_register (SIGABRT, list_cleanup);
#endif
#ifdef SIGHUP
    (void) SIG_register (SIGHUP, list_cleanup);
#endif
#ifdef SIGINT
    (void) SIG_register (SIGINT, list_cleanup);
#endif
#ifdef SIGQUIT
    (void) SIG_register (SIGQUIT, list_cleanup);
#endif
#ifdef SIGPIPE
    (void) SIG_register (SIGPIPE, list_cleanup);
#endif
#ifdef SIGTERM
    (void) SIG_register (SIGTERM, list_cleanup);
#endif
#endif

    db = open_module ();
    for (i = 0; i < argc; i++)
	err += do_module (db, argv[i], CVSLIST, "Listing", list_proc,
			  (char *) NULL, 0, 0, 0, (char *) NULL);
    close_module (db);
    free (options);
    list_cleanup ();
    return (err);
}

/*
 * callback proc for doing the real work of listing
 */
/* ARGSUSED */
static int
list_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
	    mname, msg)
    int argc;
    char **argv;
    char *xwhere;
    char *mwhere;
    char *mfile;
    int shorten;
    int local_specified;
    char *mname;
    char *msg;
{
    char *myargv[2];
    int err = 0;
    int which;
    char *repository;
    char *where;

    repository = xmalloc (strlen (CVSroot_directory) + strlen (argv[0])
			  + (mfile == NULL ? 0 : strlen (mfile)) + 30);
    (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]);
    where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile))
		     + 10);
    (void) strcpy (where, argv[0]);

    /* if mfile isn't null, we need to set up to do only part of the module */
    if (mfile != NULL)
    {
	char *cp;
	char *path;

	/* if the portion of the module is a path, put the dir part on repos */
	if ((cp = strrchr (mfile, '/')) != NULL)
	{
	    *cp = '\0';
	    (void) strcat (repository, "/");
	    (void) strcat (repository, mfile);
	    (void) strcat (where, "/");
	    (void) strcat (where, mfile);
	    mfile = cp + 1;
	}

	/* take care of the rest */
	path = xmalloc (strlen (repository) + strlen (mfile) + 5);
	(void) sprintf (path, "%s/%s", repository, mfile);
	if (isdir (path))
	{
	    /* directory means repository gets the dir tacked on */
	    (void) strcpy (repository, path);
	    (void) strcat (where, "/");
	    (void) strcat (where, mfile);
	}
	else
	{
	    myargv[0] = argv[0];
	    myargv[1] = mfile;
	    argc = 2;
	    argv = myargv;
	}
	free (path);
    }

    /* cd to the starting repository */
    if ( CVS_CHDIR (repository) < 0)
    {
	error (0, errno, "cannot chdir to %s", repository);
	free (repository);
	return (1);
    }
    free (repository);

    if (1)
	which = W_REPOS | W_ATTIC;
    else
	which = W_REPOS;

    if (rev != NULL && !rev_validated)
    {
	tag_check_valid (rev, argc - 1, argv + 1, local, 0, NULL);
	rev_validated = 1;
    }

    /* start the recursion processor */
    err = start_recursion (list_fileproc, (FILESDONEPROC) NULL, list_dirproc,
			   (DIRLEAVEPROC) NULL, NULL,
			   argc - 1, argv + 1, local,
			   which, 0, 1, where, 1);
    free (where);

    return (err);
}

/*
 * Called to examine a particular RCS file, as appropriate with the options
 * that were set above.
 */
/* ARGSUSED */
static int
list_fileproc (callerdat, finfo)
    void *callerdat;
    struct file_info *finfo;
{
    struct utimbuf t;
    char *vers_tag, *vers_head;
    char *rcs = NULL;
    RCSNode *rcsfile;
    FILE *fp1, *fp2, *fp3;
    int ret = 0;
    int isattic = 0;
    int retcode = 0;
    char *file1;
    char *file2;
    char *strippath;
    char *line1, *line2;
    size_t line1_chars_allocated;
    size_t line2_chars_allocated;
    char *cp1, *cp2;
    FILE *fp;
    int line_length;

    line1 = NULL;
    line1_chars_allocated = 0;
    line2 = NULL;
    line2_chars_allocated = 0;


    /* find the parsed rcs file */
    if ((rcsfile = finfo->rcs) == NULL)
    {
	ret = 1;
	goto out2;
    }
    if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
	isattic = 1;

    rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
    (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);


    vers_tag = RCS_getversion (rcsfile, rev, date, force_tag_match,
			       (int *) NULL);


    if (list_all == 0 && vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
    {
        free (vers_tag);
	vers_tag = NULL;
    }

    if (vers_tag == NULL)
    {
	/* Nothing known about specified revs.  */
	ret = 0;
	goto out2;
    }

	if (!list_short) {
		Node *p;
		RCSVers *vers;
		char buf[100];
		int year, mon, mday, hour, min, sec;

		p = findnode (rcsfile->versions, vers_tag);
		vers = (RCSVers *) p->data;

		(void) sscanf (vers->date, SDATEFORM, &year, &mon, &mday, &hour, &min, &sec);
                if (year < 1900)
                        year += 1900;
		sprintf(buf, "%-10s %-15s %-35s %04d/%02d/%02d %02d:%02d:%02d ", 
			vers->state, vers->version, vers->author, year, mon, 
			mday, hour, min, sec);
		cvs_output(buf, 0);
	}

        if (list_bare) {
                cvs_output(finfo->file, 0);
        } else {
                cvs_output(finfo->fullname, 0);
        }
	cvs_output("\n", 1);

	ret=0;
	out2:
    if (vers_tag != NULL)
	free (vers_tag);
    if (rcs != NULL)
	free (rcs);
    return (ret);
}

/*
 * Print a warm fuzzy message
 */
/* ARGSUSED */
static Dtype
list_dirproc (callerdat, dir, repos, update_dir, entries)
    void *callerdat;
    char *dir;
    char *repos;
    char *update_dir;
    List *entries;
{
    if (!quiet)
	error (0, 0, "Listing %s", update_dir);
    return (R_PROCESS);
}

static RETSIGTYPE
list_cleanup ()
{
	/* Placeholder function*/
}

Reply via email to