xref: /dragonfly/contrib/cvs-1.12/src/ls.c (revision 36a3d1d6)
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  * Copyright (c) 1989-1992, Brian Berliner
4  * Copyright (c) 2001, Tony Hoyle
5  * Copyright (c) 2004, Derek R. Price & Ximbiot <http://ximbiot.com>
6  *
7  * You may distribute under the terms of the GNU General Public License as
8  * specified in the README file that comes with the CVS source distribution.
9  *
10  * Query CVS/Entries from server
11  */
12 
13 #include "cvs.h"
14 #include <stdbool.h>
15 
16 static int ls_proc (int argc, char **argv, char *xwhere, char *mwhere,
17                     char *mfile, int shorten, int local, char *mname,
18                     char *msg);
19 
20 static const char *const ls_usage[] =
21 {
22     "Usage: %s %s [-e | -l] [-RP] [-r rev] [-D date] [path...]\n",
23     "\t-d\tShow dead revisions (with tag when specified).\n",
24     "\t-e\tDisplay in CVS/Entries format.\n",
25     "\t-l\tDisplay all details.\n",
26     "\t-P\tPrune empty directories.\n",
27     "\t-R\tList recursively.\n",
28     "\t-r rev\tShow files with revision or tag.\n",
29     "\t-D date\tShow files from date.\n",
30     "(Specify the --help global option for a list of other help options)\n",
31     NULL
32 };
33 
34 static bool entries_format;
35 static bool long_format;
36 static char *show_tag;
37 static char *show_date;
38 static bool set_tag;
39 static char *created_dir;
40 static bool tag_validated;
41 static bool recurse;
42 static bool ls_prune_dirs;
43 static char *regexp_match;
44 static bool is_rls;
45 static bool show_dead_revs;
46 
47 
48 
49 int
50 ls (int argc, char **argv)
51 {
52     int c;
53     int err = 0;
54 
55     is_rls = strcmp (cvs_cmd_name, "rls") == 0;
56 
57     if (argc == -1)
58 	usage (ls_usage);
59 
60     entries_format = false;
61     long_format = false;
62     show_tag = NULL;
63     show_date = NULL;
64     tag_validated = false;
65     recurse = false;
66     ls_prune_dirs = false;
67     show_dead_revs = false;
68 
69     optind = 0;
70 
71     while ((c = getopt (argc, argv,
72 #ifdef SERVER_SUPPORT
73            server_active ? "qdelr:D:PR" :
74 #endif /* SERVER_SUPPORT */
75            "delr:D:RP"
76            )) != -1)
77     {
78 	switch (c)
79 	{
80 #ifdef SERVER_SUPPORT
81 	    case 'q':
82 		if (server_active)
83 		{
84 		    error (0, 0,
85 "`%s ls -q' is deprecated.  Please use the global `-q' option instead.",
86                            program_name);
87 		    quiet = true;
88 		}
89 		else
90 		    usage (ls_usage);
91 		break;
92 #endif /* SERVER_SUPPORT */
93 	    case 'd':
94 		show_dead_revs = true;
95 		break;
96 	    case 'e':
97 		entries_format = true;
98 		break;
99 	    case 'l':
100 		long_format = true;
101 		break;
102 	    case 'r':
103 		parse_tagdate (&show_tag, &show_date, optarg);
104 		break;
105 	    case 'D':
106 		if (show_date) free (show_date);
107 		show_date = Make_Date (optarg);
108 		break;
109 	    case 'P':
110 		ls_prune_dirs = true;
111 		break;
112 	    case 'R':
113 		recurse = true;
114 		break;
115 	    case '?':
116 	    default:
117 		usage (ls_usage);
118 		break;
119 	}
120     }
121     argc -= optind;
122     argv += optind;
123 
124     if (entries_format && long_format)
125     {
126         error (0, 0, "`-e' & `-l' are mutually exclusive.");
127         usage (ls_usage);
128     }
129 
130     wrap_setup ();
131 
132 #ifdef CLIENT_SUPPORT
133     if (current_parsed_root->isremote)
134     {
135 	/* We're the local client.  Fire up the remote server.	*/
136 	start_server ();
137 
138 	ign_setup ();
139 
140 	if (is_rls ? !(supported_request ("rlist") || supported_request ("ls"))
141                    : !supported_request ("list"))
142 	    error (1, 0, "server does not support %s", cvs_cmd_name);
143 
144 	if (quiet && !supported_request ("global-list-quiet"))
145 	    send_arg ("-q");
146 	if (entries_format)
147 	    send_arg ("-e");
148 	if (long_format)
149 	    send_arg ("-l");
150 	if (ls_prune_dirs)
151 	    send_arg ("-P");
152 	if (recurse)
153 	    send_arg ("-R");
154 	if (show_dead_revs)
155 	    send_arg ("-d");
156 	if (show_tag)
157 	    option_with_arg ("-r", show_tag);
158 	if (show_date)
159 	    client_senddate (show_date);
160 
161 	send_arg ("--");
162 
163 	if (is_rls)
164 	{
165 	    int i;
166 	    for (i = 0; i < argc; i++)
167 		send_arg (argv[i]);
168             if (supported_request ("rlist"))
169 		send_to_server ("rlist\012", 0);
170 	    else
171 		/* For backwards compatibility with CVSNT...  */
172 		send_to_server ("ls\012", 0);
173 	}
174 	else
175 	{
176 	    /* Setting this means, I think, that any empty directories created
177 	     * by the server will be deleted by the client.  Since any dirs
178 	     * created at all by ls should remain empty, this should cause any
179 	     * dirs created by the server for the ls command to be deleted.
180 	     */
181 	    client_prune_dirs = 1;
182 
183 	    /* I explicitly decide not to send contents here.  We *could* let
184 	     * the user pull status information with this command, but why
185 	     * don't they just use update or status?
186 	     */
187 	    send_files (argc, argv, !recurse, 0, SEND_NO_CONTENTS);
188 	    send_file_names (argc, argv, SEND_EXPAND_WILD);
189 	    send_to_server ("list\012", 0);
190 	}
191 
192 	err = get_responses_and_close ();
193 	return err;
194     }
195 #endif
196 
197     if (is_rls)
198     {
199 	DBM *db;
200 	int i;
201 	db = open_module ();
202 	if (argc)
203 	{
204 	    for (i = 0; i < argc; i++)
205 	    {
206 		char *mod = xstrdup (argv[i]);
207 		char *p;
208 
209 		for (p=strchr (mod,'\\'); p; p=strchr (p,'\\'))
210 		    *p='/';
211 
212 		p = strrchr (mod,'/');
213 		if (p && (strchr (p,'?') || strchr (p,'*')))
214 		{
215 		    *p='\0';
216 		    regexp_match = p+1;
217 		}
218 		else
219 		    regexp_match = NULL;
220 
221 		/* Frontends like to do 'ls -q /', so we support it explicitly.
222                  */
223 		if (!strcmp (mod,"/"))
224 		{
225 		    *mod='\0';
226 		}
227 
228 		err += do_module (db, mod, MISC, "Listing",
229 				  ls_proc, NULL, 0, 0, 0, 0, NULL);
230 
231 		free (mod);
232 	    }
233 	}
234 	else
235 	{
236 	    /* should be ".", but do_recursion()
237 	       fails this: assert ( strstr ( repository, "/./" ) == NULL ); */
238 	    char *topmod = xstrdup ("");
239 	    err += do_module (db, topmod, MISC, "Listing",
240 			      ls_proc, NULL, 0, 0, 0, 0, NULL);
241 	    free (topmod);
242 	}
243 	close_module (db);
244     }
245     else
246 	ls_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, 0, NULL, NULL);
247 
248     return err;
249 }
250 
251 
252 
253 struct long_format_data
254 {
255     char *header;
256     char *time;
257     char *footer;
258 };
259 
260 static int
261 ls_print (Node *p, void *closure)
262 {
263     if (long_format)
264     {
265 	struct long_format_data *data = p->data;
266 	cvs_output_tagged ("text", data->header);
267 	if (data->time)
268 	    cvs_output_tagged ("date", data->time);
269 	if (data->footer)
270 	    cvs_output_tagged ("text", data->footer);
271 	cvs_output_tagged ("newline", NULL);
272     }
273     else
274 	cvs_output (p->data, 0);
275     return 0;
276 }
277 
278 
279 
280 static int
281 ls_print_dir (Node *p, void *closure)
282 {
283     static bool printed = false;
284 
285     if (recurse && !(ls_prune_dirs && list_isempty (p->data)))
286     {
287         /* Keep track of whether we've printed.  If we have, then put a blank
288          * line before directory headers, to separate the header from the
289          * listing of the previous directory.
290          */
291         if (printed)
292             cvs_output ("\n", 1);
293         else
294             printed = true;
295 
296         if (!strcmp (p->key, ""))
297             cvs_output (".", 1);
298         else
299 	    cvs_output (p->key, 0);
300         cvs_output (":\n", 2);
301     }
302     walklist (p->data, ls_print, NULL);
303     return 0;
304 }
305 
306 
307 
308 /*
309  * Delproc for a node containing a struct long_format_data as data.
310  */
311 static void
312 long_format_data_delproc (Node *n)
313 {
314 	struct long_format_data *data = (struct long_format_data *)n->data;
315 	if (data->header) free (data->header);
316 	if (data->time) free (data->time);
317 	if (data->footer) free (data->footer);
318 	free (data);
319 }
320 
321 
322 
323 /* A delproc for a List Node containing a List *.  */
324 static void
325 ls_delproc (Node *p)
326 {
327     dellist ((List **)&p->data);
328 }
329 
330 
331 
332 /*
333  * Add a file to our list of data to print for a directory.
334  */
335 /* ARGSUSED */
336 static int
337 ls_fileproc (void *callerdat, struct file_info *finfo)
338 {
339     Vers_TS *vers;
340     char *regex_err;
341     Node *p, *n;
342     bool isdead;
343     const char *filename;
344 
345     if (regexp_match)
346     {
347 #ifdef FILENAMES_CASE_INSENSITIVE
348 	  re_set_syntax (REG_ICASE|RE_SYNTAX_EGREP);
349 #else
350 	  re_set_syntax (RE_SYNTAX_EGREP);
351 #endif
352 	  if ((regex_err = re_comp (regexp_match)) != NULL)
353 	  {
354 	      error (1, 0, "bad regular expression passed to 'ls': %s",
355                      regex_err);
356 	  }
357 	  if (re_exec (finfo->file) == 0)
358 	      return 0;				/* no match */
359     }
360 
361     vers = Version_TS (finfo, NULL, show_tag, show_date, 1, 0);
362     /* Skip dead revisions unless specifically requested to do otherwise.
363      * We also bother to check for long_format so we can print the state.
364      */
365     if (vers->vn_rcs && (!show_dead_revs || long_format))
366 	isdead = RCS_isdead (finfo->rcs, vers->vn_rcs);
367     else
368 	isdead = false;
369     if (!vers->vn_rcs || (!show_dead_revs && isdead))
370     {
371         freevers_ts (&vers);
372 	return 0;
373     }
374 
375     p = findnode (callerdat, finfo->update_dir);
376     if (!p)
377     {
378 	/* This only occurs when a complete path to a file is specified on the
379 	 * command line.  Put the file in the root list.
380 	 */
381 	filename = finfo->fullname;
382 
383 	/* Add update_dir node.  */
384 	p = findnode (callerdat, ".");
385 	if (!p)
386 	{
387 	    p = getnode ();
388 	    p->key = xstrdup (".");
389 	    p->data = getlist ();
390 	    p->delproc = ls_delproc;
391 	    addnode (callerdat, p);
392 	}
393     }
394     else
395 	filename = finfo->file;
396 
397     n = getnode();
398     if (entries_format)
399     {
400 	char *outdate = entries_time (RCS_getrevtime (finfo->rcs, vers->vn_rcs,
401                                                       0, 0));
402 	n->data = Xasprintf ("/%s/%s/%s/%s/%s%s\n",
403                              filename, vers->vn_rcs,
404                              outdate, vers->options,
405                              show_tag ? "T" : "", show_tag ? show_tag : "");
406 	free (outdate);
407     }
408     else if (long_format)
409     {
410 	struct long_format_data *out =
411 		xmalloc (sizeof (struct long_format_data));
412 	out->header = Xasprintf ("%-5.5s",
413                                  vers->options[0] != '\0' ? vers->options
414                                                           : "----");
415 	/* FIXME: Do we want to mimc the real `ls' command's date format?  */
416 	out->time = gmformat_time_t (RCS_getrevtime (finfo->rcs, vers->vn_rcs,
417                                                      0, 0));
418 	out->footer = Xasprintf (" %-9.9s%s %s%s", vers->vn_rcs,
419                                  strlen (vers->vn_rcs) > 9 ? "+" : " ",
420                                  show_dead_revs ? (isdead ? "dead " : "     ")
421                                                 : "",
422                                  filename);
423 	n->data = out;
424 	n->delproc = long_format_data_delproc;
425     }
426     else
427 	n->data = Xasprintf ("%s\n", filename);
428 
429     addnode (p->data, n);
430 
431     freevers_ts (&vers);
432     return 0;
433 }
434 
435 
436 
437 /*
438  * Add this directory to the list of data to be printed for a directory and
439  * decide whether to tell the recursion processor whether to continue
440  * recursing or not.
441  */
442 static Dtype
443 ls_direntproc (void *callerdat, const char *dir, const char *repos,
444                const char *update_dir, List *entries)
445 {
446     Dtype retval;
447     Node *p;
448 
449     /* Due to the way we called start_recursion() from ls_proc() with a single
450      * argument at a time, we can assume that if we don't yet have a parent
451      * directory in DIRS then this directory should be processed.
452      */
453 
454     if (strcmp (dir, "."))
455     {
456         /* Search for our parent directory.  */
457 	char *parent;
458         parent = xmalloc (strlen (update_dir) - strlen (dir) + 1);
459         strncpy (parent, update_dir, strlen (update_dir) - strlen (dir));
460         parent[strlen (update_dir) - strlen (dir)] = '\0';
461         strip_trailing_slashes (parent);
462         p = findnode (callerdat, parent);
463     }
464     else
465         p = NULL;
466 
467     if (p)
468     {
469 	/* Push this dir onto our parent directory's listing.  */
470 	Node *n = getnode();
471 
472 	if (entries_format)
473 	    n->data = Xasprintf ("D/%s////\n", dir);
474 	else if (long_format)
475 	{
476 	    struct long_format_data *out =
477 		    xmalloc (sizeof (struct long_format_data));
478 	    out->header = xstrdup ("d--- ");
479 	    out->time = gmformat_time_t (unix_time_stamp (repos));
480 	    out->footer = Xasprintf ("%12s%s%s", "",
481                                      show_dead_revs ? "     " : "", dir);
482 	    n->data = out;
483 	    n->delproc = long_format_data_delproc;
484 	}
485 	else
486 	    n->data = Xasprintf ("%s\n", dir);
487 
488 	addnode (p->data, n);
489     }
490 
491     if (!p || recurse)
492     {
493 	/* Create a new list for this directory.  */
494 	p = getnode ();
495 	p->key = xstrdup (strcmp (update_dir, ".") ? update_dir : "");
496 	p->data = getlist ();
497         p->delproc = ls_delproc;
498 	addnode (callerdat, p);
499 
500 	/* Create a local directory and mark it as needing deletion.  This is
501          * the behavior the recursion processor relies upon, a la update &
502          * checkout.
503          */
504 	if (!isdir (dir))
505         {
506 	    int nonbranch;
507 	    if (show_tag == NULL && show_date == NULL)
508 	    {
509 		ParseTag (&show_tag, &show_date, &nonbranch);
510 		set_tag = true;
511 	    }
512 
513 	    if (!created_dir)
514 		created_dir = xstrdup (update_dir);
515 
516 	    make_directory (dir);
517 	    Create_Admin (dir, update_dir, repos, show_tag, show_date,
518 			  nonbranch, 0, 0);
519 	    Subdir_Register (entries, NULL, dir);
520 	}
521 
522 	/* Tell do_recursion to keep going.  */
523 	retval = R_PROCESS;
524     }
525     else
526         retval = R_SKIP_ALL;
527 
528     return retval;
529 }
530 
531 
532 
533 /* Clean up tags, dates, and dirs if we created this directory.
534  */
535 static int
536 ls_dirleaveproc (void *callerdat, const char *dir, int err,
537                  const char *update_dir, List *entries)
538 {
539 	if (created_dir && !strcmp (created_dir, update_dir))
540 	{
541 		if (set_tag)
542 		{
543 		    if (show_tag) free (show_tag);
544 		    if (show_date) free (show_date);
545 		    show_tag = show_date = NULL;
546 		    set_tag = false;
547 		}
548 
549 		(void)CVS_CHDIR ("..");
550 		if (unlink_file_dir (dir))
551 		    error (0, errno, "Failed to remove directory `%s'",
552 			   created_dir);
553 		Subdir_Deregister (entries, NULL, dir);
554 
555 		free (created_dir);
556 		created_dir = NULL;
557 	}
558 	return err;
559 }
560 
561 
562 
563 static int
564 ls_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
565          int shorten, int local, char *mname, char *msg)
566 {
567     char *repository;
568     int err = 0;
569     int which;
570     char *where;
571     int i;
572 
573     if (is_rls)
574     {
575 	char *myargv[2];
576 
577 	if (!quiet)
578 	    error (0, 0, "Listing module: `%s'",
579 	           strcmp (mname, "") ? mname : ".");
580 
581 	repository = xmalloc (strlen (current_parsed_root->directory)
582 			      + strlen (argv[0])
583 			      + (mfile == NULL ? 0 : strlen (mfile) + 1)
584 			      + 2);
585 	(void)sprintf (repository, "%s/%s", current_parsed_root->directory,
586 		       argv[0]);
587 	where = xmalloc (strlen (argv[0])
588 			 + (mfile == NULL ? 0 : strlen (mfile) + 1)
589 			 + 1);
590 	(void)strcpy (where, argv[0]);
591 
592 	/* If mfile isn't null, we need to set up to do only part of the
593 	 * module.
594 	 */
595 	if (mfile != NULL)
596 	{
597 	    char *cp;
598 	    char *path;
599 
600 	    /* If the portion of the module is a path, put the dir part on
601 	     * repos.
602 	     */
603 	    if ((cp = strrchr (mfile, '/')) != NULL)
604 	    {
605 		*cp = '\0';
606 		(void)strcat (repository, "/");
607 		(void)strcat (repository, mfile);
608 		(void)strcat (where, "/");
609 		(void)strcat (where, mfile);
610 		mfile = cp + 1;
611 	    }
612 
613 	    /* take care of the rest */
614 	    path = Xasprintf ("%s/%s", repository, mfile);
615 	    if (isdir (path))
616 	    {
617 		/* directory means repository gets the dir tacked on */
618 		(void)strcpy (repository, path);
619 		(void)strcat (where, "/");
620 		(void)strcat (where, mfile);
621 	    }
622 	    else
623 	    {
624 		myargv[1] = mfile;
625 		argc = 2;
626 		argv = myargv;
627 	    }
628 	    free (path);
629 	}
630 
631 	/* cd to the starting repository */
632 	if (CVS_CHDIR (repository) < 0)
633 	{
634 	    error (0, errno, "cannot chdir to %s", repository);
635 	    free (repository);
636 	    free (where);
637 	    return 1;
638 	}
639 
640 	which = W_REPOS;
641     }
642     else /* !is_rls */
643     {
644         repository = NULL;
645         where = NULL;
646         which = W_LOCAL | W_REPOS;
647     }
648 
649     if (show_tag || show_date || show_dead_revs)
650 	which |= W_ATTIC;
651 
652     if (show_tag != NULL && !tag_validated)
653     {
654 	tag_check_valid (show_tag, argc - 1, argv + 1, local, 0, repository,
655 			 false);
656 	tag_validated = true;
657     }
658 
659     /* Loop on argc so that we are guaranteed that any directory passed to
660      * ls_direntproc should be processed if its parent is not yet in DIRS.
661      */
662     if (argc == 1)
663     {
664 	List *dirs = getlist ();
665 	err = start_recursion (ls_fileproc, NULL, ls_direntproc,
666 			       ls_dirleaveproc, dirs, 0, NULL, local, which, 0,
667 			       CVS_LOCK_READ, where, 1, repository);
668 	walklist (dirs, ls_print_dir, NULL);
669 	dellist (&dirs);
670     }
671     else
672     {
673 	for (i = 1; i < argc; i++)
674 	{
675 	    List *dirs = getlist ();
676 	    err = start_recursion (ls_fileproc, NULL, ls_direntproc,
677 				   NULL, dirs, 1, argv + i, local, which, 0,
678 				   CVS_LOCK_READ, where, 1, repository);
679 	    walklist (dirs, ls_print_dir, NULL);
680 	    dellist (&dirs);
681 	}
682     }
683 
684     if (!(which & W_LOCAL)) free (repository);
685     if (where) free (where);
686 
687     return err;
688 }
689