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