| | @@ -363,27 +363,28 @@ |
| 363 | 363 | } |
| 364 | 364 | db_finalize(&q); |
| 365 | 365 | } |
| 366 | 366 | |
| 367 | 367 | /* |
| 368 | | -** Create a TEMP table named SFILE and add all unmanaged files named on the command-line |
| 369 | | -** to that table. If directories are named, then add all unmanaged files contained |
| 370 | | -** underneath those directories. If there are no files or directories named on the |
| 371 | | -** command-line, then add all unmanaged files anywhere in the checkout. |
| 368 | +** Create a TEMP table named SFILE and add all unmanaged files named on |
| 369 | +** the command-line to that table. If directories are named, then add |
| 370 | +** all unmanaged files contained underneath those directories. If there |
| 371 | +** are no files or directories named on the command-line, then add all |
| 372 | +** unmanaged files anywhere in the checkout. |
| 372 | 373 | */ |
| 373 | 374 | static void locate_unmanaged_files( |
| 374 | | - int argc, /* Number of command-line arguments to examine */ |
| 375 | | - char **argv, /* values of command-line arguments */ |
| 376 | | - unsigned scanFlags, /* Zero or more SCAN_xxx flags */ |
| 377 | | - Glob *pIgnore1, /* Do not add files that match this GLOB */ |
| 378 | | - Glob *pIgnore2 /* Omit files matching this GLOB too */ |
| 375 | + int argc, /* Number of command-line arguments to examine */ |
| 376 | + char **argv, /* values of command-line arguments */ |
| 377 | + unsigned scanFlags, /* Zero or more SCAN_xxx flags */ |
| 378 | + Glob *pIgnore1, /* Do not add files that match this GLOB */ |
| 379 | + Glob *pIgnore2 /* Omit files matching this GLOB too */ |
| 379 | 380 | ){ |
| 380 | | - Blob name; /* Name of a candidate file or directory */ |
| 381 | | - char *zName; /* Name of a candidate file or directory */ |
| 382 | | - int isDir; /* 1 for a directory, 0 if doesn't exist, 2 for anything else */ |
| 383 | | - int i; /* Loop counter */ |
| 384 | | - int nRoot; /* length of g.zLocalRoot */ |
| 381 | + Blob name; /* Name of a candidate file or directory */ |
| 382 | + char *zName; /* Name of a candidate file or directory */ |
| 383 | + int isDir; /* 1 for a directory, 0 if doesn't exist, 2 for anything else */ |
| 384 | + int i; /* Loop counter */ |
| 385 | + int nRoot; /* length of g.zLocalRoot */ |
| 385 | 386 | |
| 386 | 387 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)", |
| 387 | 388 | filename_collation()); |
| 388 | 389 | nRoot = (int)strlen(g.zLocalRoot); |
| 389 | 390 | if( argc==0 ){ |
| | @@ -506,12 +507,28 @@ |
| 506 | 507 | ** Files and subdirectories whose names begin with "." are |
| 507 | 508 | ** normally kept. They are handled if the "--dotfiles" option |
| 508 | 509 | ** is used. |
| 509 | 510 | ** |
| 510 | 511 | ** Options: |
| 512 | +** --allckouts Check for empty directories within any checkouts |
| 513 | +** that may be nested within the current one. This |
| 514 | +** option should be used with great care because the |
| 515 | +** empty-dirs setting (and other applicable settings) |
| 516 | +** belonging to the other repositories, if any, will |
| 517 | +** not be checked. |
| 511 | 518 | ** --case-sensitive <BOOL> override case-sensitive setting |
| 519 | +** --dirsonly Only remove empty directories. No files will |
| 520 | +** be removed. Using this option will automatically |
| 521 | +** enable the --emptydirs option as well. |
| 512 | 522 | ** --dotfiles Include files beginning with a dot ("."). |
| 523 | +** --emptydirs Remove any empty directories that are not |
| 524 | +** explicitly exempted via the empty-dirs setting |
| 525 | +** or another applicable setting or command line |
| 526 | +** argument. Matching files, if any, are removed |
| 527 | +** prior to checking for any empty directories; |
| 528 | +** therefore, directories that contain only files |
| 529 | +** that were removed will be removed as well. |
| 513 | 530 | ** -f|--force Remove files without prompting. |
| 514 | 531 | ** --clean <CSG> Never prompt for files matching this |
| 515 | 532 | ** comma separated list of glob patterns. |
| 516 | 533 | ** --ignore <CSG> Ignore files matching patterns from the |
| 517 | 534 | ** comma separated list of glob patterns. |
| | @@ -522,25 +539,27 @@ |
| 522 | 539 | ** -v|--verbose Show all files as they are removed. |
| 523 | 540 | ** |
| 524 | 541 | ** See also: addremove, extra, status |
| 525 | 542 | */ |
| 526 | 543 | void clean_cmd(void){ |
| 527 | | - int allFlag, dryRunFlag, verboseFlag; |
| 544 | + int allFileFlag, allDirFlag, dryRunFlag, verboseFlag; |
| 545 | + int emptyDirsFlag, dirsOnlyFlag; |
| 528 | 546 | unsigned scanFlags = 0; |
| 529 | 547 | const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag; |
| 530 | | - Blob repo; |
| 531 | | - Stmt q; |
| 532 | 548 | Glob *pIgnore, *pKeep, *pClean; |
| 533 | 549 | int nRoot; |
| 534 | 550 | |
| 535 | 551 | dryRunFlag = find_option("dry-run","n",0)!=0; |
| 536 | 552 | if( !dryRunFlag ){ |
| 537 | 553 | dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ |
| 538 | 554 | } |
| 539 | | - allFlag = find_option("force","f",0)!=0; |
| 555 | + allFileFlag = allDirFlag = find_option("force","f",0)!=0; |
| 556 | + dirsOnlyFlag = find_option("dirsonly",0,0)!=0; |
| 557 | + emptyDirsFlag = dirsOnlyFlag || find_option("emptydirs","d",0)!=0; |
| 540 | 558 | if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; |
| 541 | 559 | if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; |
| 560 | + if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED; |
| 542 | 561 | zIgnoreFlag = find_option("ignore",0,1); |
| 543 | 562 | verboseFlag = find_option("verbose","v",0)!=0; |
| 544 | 563 | zKeepFlag = find_option("keep",0,1); |
| 545 | 564 | zCleanFlag = find_option("clean",0,1); |
| 546 | 565 | capture_case_sensitive_option(); |
| | @@ -556,51 +575,99 @@ |
| 556 | 575 | } |
| 557 | 576 | verify_all_options(); |
| 558 | 577 | pIgnore = glob_create(zIgnoreFlag); |
| 559 | 578 | pKeep = glob_create(zKeepFlag); |
| 560 | 579 | pClean = glob_create(zCleanFlag); |
| 561 | | - locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, pKeep); |
| 562 | | - glob_free(pKeep); |
| 563 | | - glob_free(pIgnore); |
| 564 | | - db_prepare(&q, |
| 565 | | - "SELECT %Q || x FROM sfile" |
| 566 | | - " WHERE x NOT IN (%s)" |
| 567 | | - " ORDER BY 1", |
| 568 | | - g.zLocalRoot, fossil_all_reserved_names(0) |
| 569 | | - ); |
| 570 | | - if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 571 | | - db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 572 | | - } |
| 573 | | - db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)"); |
| 574 | 580 | nRoot = (int)strlen(g.zLocalRoot); |
| 575 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 576 | | - const char *zName = db_column_text(&q, 0); |
| 577 | | - if( !allFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ |
| 578 | | - Blob ans; |
| 579 | | - char cReply; |
| 580 | | - char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ", |
| 581 | | - zName+nRoot); |
| 582 | | - blob_zero(&ans); |
| 583 | | - prompt_user(prompt, &ans); |
| 584 | | - cReply = blob_str(&ans)[0]; |
| 585 | | - if( cReply=='a' || cReply=='A' ){ |
| 586 | | - allFlag = 1; |
| 587 | | - }else if( cReply!='y' && cReply!='Y' ){ |
| 588 | | - blob_reset(&ans); |
| 589 | | - continue; |
| 590 | | - } |
| 591 | | - blob_reset(&ans); |
| 592 | | - } |
| 593 | | - if( verboseFlag || dryRunFlag ){ |
| 594 | | - fossil_print("Removed unmanaged file: %s\n", zName+nRoot); |
| 595 | | - } |
| 596 | | - if( !dryRunFlag ){ |
| 597 | | - file_delete(zName); |
| 598 | | - } |
| 581 | + if( !dirsOnlyFlag ){ |
| 582 | + Stmt q; |
| 583 | + Blob repo; |
| 584 | + locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, pKeep); |
| 585 | + db_prepare(&q, |
| 586 | + "SELECT %Q || x FROM sfile" |
| 587 | + " WHERE x NOT IN (%s)" |
| 588 | + " ORDER BY 1", |
| 589 | + g.zLocalRoot, fossil_all_reserved_names(0) |
| 590 | + ); |
| 591 | + if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 592 | + db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 593 | + } |
| 594 | + db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)"); |
| 595 | + while( db_step(&q)==SQLITE_ROW ){ |
| 596 | + const char *zName = db_column_text(&q, 0); |
| 597 | + if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ |
| 598 | + Blob ans; |
| 599 | + char cReply; |
| 600 | + char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ", |
| 601 | + zName+nRoot); |
| 602 | + blob_zero(&ans); |
| 603 | + prompt_user(prompt, &ans); |
| 604 | + cReply = blob_str(&ans)[0]; |
| 605 | + if( cReply=='a' || cReply=='A' ){ |
| 606 | + allFileFlag = 1; |
| 607 | + }else if( cReply!='y' && cReply!='Y' ){ |
| 608 | + blob_reset(&ans); |
| 609 | + continue; |
| 610 | + } |
| 611 | + blob_reset(&ans); |
| 612 | + } |
| 613 | + if ( dryRunFlag || file_delete(zName)==0 ){ |
| 614 | + if( verboseFlag || dryRunFlag ){ |
| 615 | + fossil_print("Removed unmanaged file: %s\n", zName+nRoot); |
| 616 | + } |
| 617 | + }else if( verboseFlag ){ |
| 618 | + fossil_print("Could not remove file: %s\n", zName+nRoot); |
| 619 | + } |
| 620 | + } |
| 621 | + db_finalize(&q); |
| 622 | + } |
| 623 | + if( emptyDirsFlag ){ |
| 624 | + Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0)); |
| 625 | + Stmt q; |
| 626 | + Blob root; |
| 627 | + blob_init(&root, g.zLocalRoot, nRoot - 1); |
| 628 | + vfile_dir_scan(&root, blob_size(&root), scanFlags, pIgnore, pKeep, |
| 629 | + pEmptyDirs); |
| 630 | + blob_reset(&root); |
| 631 | + db_prepare(&q, |
| 632 | + "SELECT %Q || x FROM dscan_temp" |
| 633 | + " WHERE x NOT IN (%s) AND y = 0" |
| 634 | + " ORDER BY 1 DESC", |
| 635 | + g.zLocalRoot, fossil_all_reserved_names(0) |
| 636 | + ); |
| 637 | + while( db_step(&q)==SQLITE_ROW ){ |
| 638 | + const char *zName = db_column_text(&q, 0); |
| 639 | + if( !allDirFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ |
| 640 | + Blob ans; |
| 641 | + char cReply; |
| 642 | + char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ", |
| 643 | + zName+nRoot); |
| 644 | + blob_zero(&ans); |
| 645 | + prompt_user(prompt, &ans); |
| 646 | + cReply = blob_str(&ans)[0]; |
| 647 | + if( cReply=='a' || cReply=='A' ){ |
| 648 | + allDirFlag = 1; |
| 649 | + }else if( cReply!='y' && cReply!='Y' ){ |
| 650 | + blob_reset(&ans); |
| 651 | + continue; |
| 652 | + } |
| 653 | + blob_reset(&ans); |
| 654 | + } |
| 655 | + if ( dryRunFlag || file_rmdir(zName)==0 ){ |
| 656 | + if( verboseFlag || dryRunFlag ){ |
| 657 | + fossil_print("Removed unmanaged directory: %s\n", zName+nRoot); |
| 658 | + } |
| 659 | + }else if( verboseFlag ){ |
| 660 | + fossil_print("Could not remove directory: %s\n", zName+nRoot); |
| 661 | + } |
| 662 | + } |
| 663 | + db_finalize(&q); |
| 664 | + glob_free(pEmptyDirs); |
| 599 | 665 | } |
| 600 | 666 | glob_free(pClean); |
| 601 | | - db_finalize(&q); |
| 667 | + glob_free(pKeep); |
| 668 | + glob_free(pIgnore); |
| 602 | 669 | } |
| 603 | 670 | |
| 604 | 671 | /* |
| 605 | 672 | ** Prompt the user for a check-in or stash comment (given in pPrompt), |
| 606 | 673 | ** gather the response, then return the response in pComment. |
| 607 | 674 | |