diff options
Diffstat (limited to 'src/ls.c')
-rw-r--r-- | src/ls.c | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/src/ls.c b/src/ls.c new file mode 100644 index 0000000..0184bdd --- /dev/null +++ b/src/ls.c @@ -0,0 +1,688 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * Copyright (c) 2001, Tony Hoyle + * Copyright (c) 2004, Derek R. Price & Ximbiot <http://ximbiot.com> + * + * 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. + * + * Query CVS/Entries from server + */ + +#include "cvs.h" +#include <stdbool.h> + +static int ls_proc (int argc, char **argv, char *xwhere, char *mwhere, + char *mfile, int shorten, int local, char *mname, + char *msg); + +static const char *const ls_usage[] = +{ + "Usage: %s %s [-e | -l] [-RP] [-r rev] [-D date] [path...]\n", + "\t-d\tShow dead revisions (with tag when specified).\n", + "\t-e\tDisplay in CVS/Entries format.\n", + "\t-l\tDisplay all details.\n", + "\t-P\tPrune empty directories.\n", + "\t-R\tList recursively.\n", + "\t-r rev\tShow files with revision or tag.\n", + "\t-D date\tShow files from date.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +static bool entries_format; +static bool long_format; +static char *show_tag; +static char *show_date; +static bool set_tag; +static char *created_dir; +static bool tag_validated; +static bool recurse; +static bool ls_prune_dirs; +static char *regexp_match; +static bool is_rls; +static bool show_dead_revs; + + + +int +ls (int argc, char **argv) +{ + int c; + int err = 0; + + is_rls = strcmp (cvs_cmd_name, "rls") == 0; + + if (argc == -1) + usage (ls_usage); + + entries_format = false; + long_format = false; + show_tag = NULL; + show_date = NULL; + tag_validated = false; + recurse = false; + ls_prune_dirs = false; + show_dead_revs = false; + + optind = 0; + + while ((c = getopt (argc, argv, +#ifdef SERVER_SUPPORT + server_active ? "qdelr:D:PR" : +#endif /* SERVER_SUPPORT */ + "delr:D:RP" + )) != -1) + { + switch (c) + { +#ifdef SERVER_SUPPORT + case 'q': + if (server_active) + { + error (0, 0, +"`%s ls -q' is deprecated. Please use the global `-q' option instead.", + program_name); + quiet = true; + } + else + usage (ls_usage); + break; +#endif /* SERVER_SUPPORT */ + case 'd': + show_dead_revs = true; + break; + case 'e': + entries_format = true; + break; + case 'l': + long_format = true; + break; + case 'r': + parse_tagdate (&show_tag, &show_date, optarg); + break; + case 'D': + if (show_date) free (show_date); + show_date = Make_Date (optarg); + break; + case 'P': + ls_prune_dirs = true; + break; + case 'R': + recurse = true; + break; + case '?': + default: + usage (ls_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (entries_format && long_format) + { + error (0, 0, "`-e' & `-l' are mutually exclusive."); + usage (ls_usage); + } + + wrap_setup (); + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + /* We're the local client. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (is_rls ? !(supported_request ("rlist") || supported_request ("ls")) + : !supported_request ("list")) + error (1, 0, "server does not support %s", cvs_cmd_name); + + if (quiet && !supported_request ("global-list-quiet")) + send_arg ("-q"); + if (entries_format) + send_arg ("-e"); + if (long_format) + send_arg ("-l"); + if (ls_prune_dirs) + send_arg ("-P"); + if (recurse) + send_arg ("-R"); + if (show_dead_revs) + send_arg ("-d"); + if (show_tag) + option_with_arg ("-r", show_tag); + if (show_date) + client_senddate (show_date); + + send_arg ("--"); + + if (is_rls) + { + int i; + for (i = 0; i < argc; i++) + send_arg (argv[i]); + if (supported_request ("rlist")) + send_to_server ("rlist\012", 0); + else + /* For backwards compatibility with CVSNT... */ + send_to_server ("ls\012", 0); + } + else + { + /* Setting this means, I think, that any empty directories created + * by the server will be deleted by the client. Since any dirs + * created at all by ls should remain empty, this should cause any + * dirs created by the server for the ls command to be deleted. + */ + client_prune_dirs = 1; + + /* I explicitly decide not to send contents here. We *could* let + * the user pull status information with this command, but why + * don't they just use update or status? + */ + send_files (argc, argv, !recurse, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server ("list\012", 0); + } + + err = get_responses_and_close (); + return err; + } +#endif + + if (is_rls) + { + DBM *db; + int i; + db = open_module (); + if (argc) + { + for (i = 0; i < argc; i++) + { + char *mod = xstrdup (argv[i]); + char *p; + + for (p=strchr (mod,'\\'); p; p=strchr (p,'\\')) + *p='/'; + + p = strrchr (mod,'/'); + if (p && (strchr (p,'?') || strchr (p,'*'))) + { + *p='\0'; + regexp_match = p+1; + } + else + regexp_match = NULL; + + /* Frontends like to do 'ls -q /', so we support it explicitly. + */ + if (!strcmp (mod,"/")) + { + *mod='\0'; + } + + err += do_module (db, mod, MISC, "Listing", + ls_proc, NULL, 0, 0, 0, 0, NULL); + + free (mod); + } + } + else + { + /* should be ".", but do_recursion() + fails this: assert ( strstr ( repository, "/./" ) == NULL ); */ + char *topmod = xstrdup (""); + err += do_module (db, topmod, MISC, "Listing", + ls_proc, NULL, 0, 0, 0, 0, NULL); + free (topmod); + } + close_module (db); + } + else + ls_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, 0, NULL, NULL); + + return err; +} + + + +struct long_format_data +{ + char *header; + char *time; + char *footer; +}; + +static int +ls_print (Node *p, void *closure) +{ + if (long_format) + { + struct long_format_data *data = p->data; + cvs_output_tagged ("text", data->header); + if (data->time) + cvs_output_tagged ("date", data->time); + if (data->footer) + cvs_output_tagged ("text", data->footer); + cvs_output_tagged ("newline", NULL); + } + else + cvs_output (p->data, 0); + return 0; +} + + + +static int +ls_print_dir (Node *p, void *closure) +{ + static bool printed = false; + + if (recurse && !(ls_prune_dirs && list_isempty (p->data))) + { + /* Keep track of whether we've printed. If we have, then put a blank + * line before directory headers, to separate the header from the + * listing of the previous directory. + */ + if (printed) + cvs_output ("\n", 1); + else + printed = true; + + if (!strcmp (p->key, "")) + cvs_output (".", 1); + else + cvs_output (p->key, 0); + cvs_output (":\n", 2); + } + walklist (p->data, ls_print, NULL); + return 0; +} + + + +/* + * Delproc for a node containing a struct long_format_data as data. + */ +static void +long_format_data_delproc (Node *n) +{ + struct long_format_data *data = (struct long_format_data *)n->data; + if (data->header) free (data->header); + if (data->time) free (data->time); + if (data->footer) free (data->footer); + free (data); +} + + + +/* A delproc for a List Node containing a List *. */ +static void +ls_delproc (Node *p) +{ + dellist ((List **)&p->data); +} + + + +/* + * Add a file to our list of data to print for a directory. + */ +/* ARGSUSED */ +static int +ls_fileproc (void *callerdat, struct file_info *finfo) +{ + Vers_TS *vers; + char *regex_err; + Node *p, *n; + bool isdead; + const char *filename; + + if (regexp_match) + { +#ifdef FILENAMES_CASE_INSENSITIVE + re_set_syntax (REG_ICASE|RE_SYNTAX_EGREP); +#else + re_set_syntax (RE_SYNTAX_EGREP); +#endif + if ((regex_err = re_comp (regexp_match)) != NULL) + { + error (1, 0, "bad regular expression passed to 'ls': %s", + regex_err); + } + if (re_exec (finfo->file) == 0) + return 0; /* no match */ + } + + vers = Version_TS (finfo, NULL, show_tag, show_date, 1, 0); + /* Skip dead revisions unless specifically requested to do otherwise. + * We also bother to check for long_format so we can print the state. + */ + if (vers->vn_rcs && (!show_dead_revs || long_format)) + isdead = RCS_isdead (finfo->rcs, vers->vn_rcs); + else + isdead = false; + if (!vers->vn_rcs || (!show_dead_revs && isdead)) + { + freevers_ts (&vers); + return 0; + } + + p = findnode (callerdat, finfo->update_dir); + if (!p) + { + /* This only occurs when a complete path to a file is specified on the + * command line. Put the file in the root list. + */ + filename = finfo->fullname; + + /* Add update_dir node. */ + p = findnode (callerdat, "."); + if (!p) + { + p = getnode (); + p->key = xstrdup ("."); + p->data = getlist (); + p->delproc = ls_delproc; + addnode (callerdat, p); + } + } + else + filename = finfo->file; + + n = getnode(); + if (entries_format) + { + char *outdate = entries_time (RCS_getrevtime (finfo->rcs, vers->vn_rcs, + 0, 0)); + n->data = Xasprintf ("/%s/%s/%s/%s/%s%s\n", + filename, vers->vn_rcs, + outdate, vers->options, + show_tag ? "T" : "", show_tag ? show_tag : ""); + free (outdate); + } + else if (long_format) + { + struct long_format_data *out = + xmalloc (sizeof (struct long_format_data)); + out->header = Xasprintf ("%-5.5s", + vers->options[0] != '\0' ? vers->options + : "----"); + /* FIXME: Do we want to mimc the real `ls' command's date format? */ + out->time = gmformat_time_t (RCS_getrevtime (finfo->rcs, vers->vn_rcs, + 0, 0)); + out->footer = Xasprintf (" %-9.9s%s %s%s", vers->vn_rcs, + strlen (vers->vn_rcs) > 9 ? "+" : " ", + show_dead_revs ? (isdead ? "dead " : " ") + : "", + filename); + n->data = out; + n->delproc = long_format_data_delproc; + } + else + n->data = Xasprintf ("%s\n", filename); + + addnode (p->data, n); + + freevers_ts (&vers); + return 0; +} + + + +/* + * Add this directory to the list of data to be printed for a directory and + * decide whether to tell the recursion processor whether to continue + * recursing or not. + */ +static Dtype +ls_direntproc (void *callerdat, const char *dir, const char *repos, + const char *update_dir, List *entries) +{ + Dtype retval; + Node *p; + + /* Due to the way we called start_recursion() from ls_proc() with a single + * argument at a time, we can assume that if we don't yet have a parent + * directory in DIRS then this directory should be processed. + */ + + if (strcmp (dir, ".")) + { + /* Search for our parent directory. */ + char *parent; + parent = xmalloc (strlen (update_dir) - strlen (dir) + 1); + strncpy (parent, update_dir, strlen (update_dir) - strlen (dir)); + parent[strlen (update_dir) - strlen (dir)] = '\0'; + strip_trailing_slashes (parent); + p = findnode (callerdat, parent); + } + else + p = NULL; + + if (p) + { + /* Push this dir onto our parent directory's listing. */ + Node *n = getnode(); + + if (entries_format) + n->data = Xasprintf ("D/%s////\n", dir); + else if (long_format) + { + struct long_format_data *out = + xmalloc (sizeof (struct long_format_data)); + out->header = xstrdup ("d--- "); + out->time = gmformat_time_t (unix_time_stamp (repos)); + out->footer = Xasprintf ("%12s%s%s", "", + show_dead_revs ? " " : "", dir); + n->data = out; + n->delproc = long_format_data_delproc; + } + else + n->data = Xasprintf ("%s\n", dir); + + addnode (p->data, n); + } + + if (!p || recurse) + { + /* Create a new list for this directory. */ + p = getnode (); + p->key = xstrdup (strcmp (update_dir, ".") ? update_dir : ""); + p->data = getlist (); + p->delproc = ls_delproc; + addnode (callerdat, p); + + /* Create a local directory and mark it as needing deletion. This is + * the behavior the recursion processor relies upon, a la update & + * checkout. + */ + if (!isdir (dir)) + { + int nonbranch; + if (show_tag == NULL && show_date == NULL) + { + ParseTag (&show_tag, &show_date, &nonbranch); + set_tag = true; + } + + if (!created_dir) + created_dir = xstrdup (update_dir); + + make_directory (dir); + Create_Admin (dir, update_dir, repos, show_tag, show_date, + nonbranch, 0, 0); + Subdir_Register (entries, NULL, dir); + } + + /* Tell do_recursion to keep going. */ + retval = R_PROCESS; + } + else + retval = R_SKIP_ALL; + + return retval; +} + + + +/* Clean up tags, dates, and dirs if we created this directory. + */ +static int +ls_dirleaveproc (void *callerdat, const char *dir, int err, + const char *update_dir, List *entries) +{ + if (created_dir && !strcmp (created_dir, update_dir)) + { + if (set_tag) + { + if (show_tag) free (show_tag); + if (show_date) free (show_date); + show_tag = show_date = NULL; + set_tag = false; + } + + (void)CVS_CHDIR (".."); + if (unlink_file_dir (dir)) + error (0, errno, "Failed to remove directory `%s'", + created_dir); + Subdir_Deregister (entries, NULL, dir); + + free (created_dir); + created_dir = NULL; + } + return err; +} + + + +static int +ls_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile, + int shorten, int local, char *mname, char *msg) +{ + char *repository; + int err = 0; + int which; + char *where; + int i; + + if (is_rls) + { + char *myargv[2]; + + if (!quiet) + error (0, 0, "Listing module: `%s'", + strcmp (mname, "") ? mname : "."); + + repository = xmalloc (strlen (current_parsed_root->directory) + + strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile) + 1) + + 2); + (void)sprintf (repository, "%s/%s", current_parsed_root->directory, + argv[0]); + where = xmalloc (strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile) + 1) + + 1); + (void)strcpy (where, argv[0]); + + /* If mfile isn't null, we need to set up to do only part of the + * module. + */ + if (mfile != NULL) + { + char *cp; + char *path; + + /* If the portion of the module is a path, put the dir part on + * repos. + */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = '\0'; + (void)strcat (repository, "/"); + (void)strcat (repository, mfile); + (void)strcat (where, "/"); + (void)strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + path = Xasprintf ("%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[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); + free (where); + return 1; + } + + which = W_REPOS; + } + else /* !is_rls */ + { + repository = NULL; + where = NULL; + which = W_LOCAL | W_REPOS; + } + + if (show_tag || show_date || show_dead_revs) + which |= W_ATTIC; + + if (show_tag != NULL && !tag_validated) + { + tag_check_valid (show_tag, argc - 1, argv + 1, local, 0, repository, + false); + tag_validated = true; + } + + /* Loop on argc so that we are guaranteed that any directory passed to + * ls_direntproc should be processed if its parent is not yet in DIRS. + */ + if (argc == 1) + { + List *dirs = getlist (); + err = start_recursion (ls_fileproc, NULL, ls_direntproc, + ls_dirleaveproc, dirs, 0, NULL, local, which, 0, + CVS_LOCK_READ, where, 1, repository); + walklist (dirs, ls_print_dir, NULL); + dellist (&dirs); + } + else + { + for (i = 1; i < argc; i++) + { + List *dirs = getlist (); + err = start_recursion (ls_fileproc, NULL, ls_direntproc, + NULL, dirs, 1, argv + i, local, which, 0, + CVS_LOCK_READ, where, 1, repository); + walklist (dirs, ls_print_dir, NULL); + dellist (&dirs); + } + } + + if (!(which & W_LOCAL)) free (repository); + if (where) free (where); + + return err; +} |