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