Fossil SCM
Add the --include and --exclude options to the tarball and zip commands and the in= and ex= query parameters to the /tarball and /zip webpages.
Commit
ef449a1173c556b2a25d084de6408bd53b5da67c
Parent
1ca598325e65101…
2 files changed
+94
-32
+83
-23
+94
-32
| --- src/tar.c | ||
| +++ src/tar.c | ||
| @@ -73,11 +73,11 @@ | ||
| 73 | 73 | ); |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | |
| 77 | 77 | /* |
| 78 | -** verify that lla characters in 'zName' are in the | |
| 78 | +** Verify that all characters in 'zName' are in the | |
| 79 | 79 | ** ISO646 (=ASCII) character set. |
| 80 | 80 | */ |
| 81 | 81 | static int is_iso646_name( |
| 82 | 82 | const char *zName, /* file path */ |
| 83 | 83 | int nName /* path length */ |
| @@ -90,11 +90,11 @@ | ||
| 90 | 90 | return 1; |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | 93 | |
| 94 | 94 | /* |
| 95 | -** copy string pSrc into pDst, truncating or padding with 0 if necessary | |
| 95 | +** copy string pSrc into pDst, truncating or padding with 0 if necessary | |
| 96 | 96 | */ |
| 97 | 97 | static void padded_copy( |
| 98 | 98 | char *pDest, |
| 99 | 99 | int nDest, |
| 100 | 100 | const char *pSrc, |
| @@ -446,15 +446,16 @@ | ||
| 446 | 446 | blob_write_to_file(&zip, g.argv[2]); |
| 447 | 447 | } |
| 448 | 448 | |
| 449 | 449 | /* |
| 450 | 450 | ** Given the RID for a check-in, construct a tarball containing |
| 451 | -** all files in that check-in | |
| 451 | +** all files in that check-in that match pGlob (or all files if | |
| 452 | +** pGlob is NULL). | |
| 452 | 453 | ** |
| 453 | 454 | ** If RID is for an object that is not a real manifest, then the |
| 454 | 455 | ** resulting tarball contains a single file which is the RID |
| 455 | -** object. | |
| 456 | +** object. pInclude and pExclude are ignored in this case. | |
| 456 | 457 | ** |
| 457 | 458 | ** If the RID object does not exist in the repository, then |
| 458 | 459 | ** pTar is zeroed. |
| 459 | 460 | ** |
| 460 | 461 | ** zDir is a "synthetic" subdirectory which all files get |
| @@ -462,11 +463,17 @@ | ||
| 462 | 463 | ** which case it is ignored. The intention is to create a tarball which |
| 463 | 464 | ** politely expands into a subdir instead of filling your current dir |
| 464 | 465 | ** with source files. For example, pass a UUID or "ProjectName". |
| 465 | 466 | ** |
| 466 | 467 | */ |
| 467 | -void tarball_of_checkin(int rid, Blob *pTar, const char *zDir){ | |
| 468 | +void tarball_of_checkin( | |
| 469 | + int rid, /* The RID of the checkin from which to form a tarball */ | |
| 470 | + Blob *pTar, /* Write the tarball into this blob */ | |
| 471 | + const char *zDir, /* Directory prefix for all file added to tarball */ | |
| 472 | + Glob *pInclude, /* Only add files matching this pattern */ | |
| 473 | + Glob *pExclude /* Exclude files matching this pattern */ | |
| 474 | +){ | |
| 468 | 475 | Blob mfile, hash, file; |
| 469 | 476 | Manifest *pManifest; |
| 470 | 477 | ManifestFile *pFile; |
| 471 | 478 | Blob filename; |
| 472 | 479 | int nPrefix; |
| @@ -488,11 +495,14 @@ | ||
| 488 | 495 | |
| 489 | 496 | pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
| 490 | 497 | if( pManifest ){ |
| 491 | 498 | mTime = (pManifest->rDate - 2440587.5)*86400.0; |
| 492 | 499 | tar_begin(mTime); |
| 493 | - if( db_get_boolean("manifest", 0) ){ | |
| 500 | + if( (pInclude==0 || glob_match(pInclude, "manifest")) | |
| 501 | + && !glob_match(pExclude, "manifest") | |
| 502 | + && db_get_boolean("manifest", 0) | |
| 503 | + ){ | |
| 494 | 504 | blob_append(&filename, "manifest", -1); |
| 495 | 505 | zName = blob_str(&filename); |
| 496 | 506 | sha1sum_blob(&mfile, &hash); |
| 497 | 507 | sterilize_manifest(&mfile); |
| 498 | 508 | tar_add_file(zName, &mfile, 0, mTime); |
| @@ -504,11 +514,14 @@ | ||
| 504 | 514 | tar_add_file(zName, &hash, 0, mTime); |
| 505 | 515 | blob_reset(&hash); |
| 506 | 516 | } |
| 507 | 517 | manifest_file_rewind(pManifest); |
| 508 | 518 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 509 | - int fid = uuid_to_rid(pFile->zUuid, 0); | |
| 519 | + int fid; | |
| 520 | + if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue; | |
| 521 | + if( glob_match(pExclude, pFile->zName) ) continue; | |
| 522 | + fid = uuid_to_rid(pFile->zUuid, 0); | |
| 510 | 523 | if( fid ){ |
| 511 | 524 | content_get(fid, &file); |
| 512 | 525 | blob_resize(&filename, nPrefix); |
| 513 | 526 | blob_append(&filename, pFile->zName, -1); |
| 514 | 527 | zName = blob_str(&filename); |
| @@ -539,19 +552,34 @@ | ||
| 539 | 552 | ** option is used, its argument becomes the name of the top-level directory |
| 540 | 553 | ** in the resulting tarball. If --name is omitted, the top-level directory |
| 541 | 554 | ** named is derived from the project name, the check-in date and time, and |
| 542 | 555 | ** the artifact ID of the check-in. |
| 543 | 556 | ** |
| 557 | +** The GLOBLIST argument to --exclude and --include can be a comma-separated | |
| 558 | +** list of glob patterns, where each glob pattern may optionally be enclosed | |
| 559 | +** in "..." or '...' so that it may contain commas. If a file matches both | |
| 560 | +** --include and --exclude then it is excluded. | |
| 561 | +** | |
| 544 | 562 | ** Options: |
| 545 | -** --name DIRECTORYNAME The name of the top-level directory in the archive | |
| 546 | -** -R REPOSITORY Specify a Fossil repository | |
| 563 | +** -X|--exclude GLOBLIST Comma-separated list of GLOBs of files to exclude | |
| 564 | +** --include GLOBLIST Comma-separated list of GLOBs of files to include | |
| 565 | +** --name DIRECTORYNAME The name of the top-level directory in the archive | |
| 566 | +** -R REPOSITORY Specify a Fossil repository | |
| 547 | 567 | */ |
| 548 | 568 | void tarball_cmd(void){ |
| 549 | 569 | int rid; |
| 550 | 570 | Blob tarball; |
| 551 | 571 | const char *zName; |
| 572 | + Glob *pInclude = 0; | |
| 573 | + Glob *pExclude = 0; | |
| 574 | + const char *zInclude; | |
| 575 | + const char *zExclude; | |
| 552 | 576 | zName = find_option("name", 0, 1); |
| 577 | + zExclude = find_option("exclude", "X", 1); | |
| 578 | + if( zExclude ) pExclude = glob_create(zExclude); | |
| 579 | + zInclude = find_option("include", 0, 1); | |
| 580 | + if( zInclude ) pInclude = glob_create(zInclude); | |
| 553 | 581 | db_find_and_open_repository(0, 0); |
| 554 | 582 | |
| 555 | 583 | /* We should be done with options.. */ |
| 556 | 584 | verify_all_options(); |
| 557 | 585 | |
| @@ -573,46 +601,65 @@ | ||
| 573 | 601 | " WHERE event.objid=%d" |
| 574 | 602 | " AND blob.rid=%d", |
| 575 | 603 | db_get("project-name", "unnamed"), rid, rid |
| 576 | 604 | ); |
| 577 | 605 | } |
| 578 | - tarball_of_checkin(rid, &tarball, zName); | |
| 606 | + tarball_of_checkin(rid, &tarball, zName, pInclude, pExclude); | |
| 607 | + glob_free(pInclude); | |
| 608 | + glob_free(pExclude); | |
| 579 | 609 | blob_write_to_file(&tarball, g.argv[3]); |
| 580 | 610 | blob_reset(&tarball); |
| 581 | 611 | } |
| 582 | 612 | |
| 583 | 613 | /* |
| 584 | 614 | ** WEBPAGE: tarball |
| 585 | -** URL: /tarball/RID.tar.gz | |
| 586 | -** | |
| 587 | -** Generate a compressed tarball for a check-in. | |
| 588 | -** Return that tarball as the HTTP reply content. | |
| 589 | -** | |
| 590 | -** Optional URL Parameters: | |
| 591 | -** | |
| 592 | -** - name=NAME[.tar.gz] is base name of the output file. Defaults to | |
| 593 | -** something project/version-specific. The prefix of the name, up to | |
| 594 | -** the last '.', are used as the top-most directory name in the tar | |
| 595 | -** output. | |
| 596 | -** | |
| 597 | -** - uuid=the version to tar (may be a tag/branch name). | |
| 598 | -** Defaults to "trunk". | |
| 599 | -** | |
| 615 | +** URL: /tarball | |
| 616 | +** | |
| 617 | +** Generate a compressed tarball for a check-in and return that | |
| 618 | +** tarball as the HTTP reply content. | |
| 619 | +** | |
| 620 | +** Query parameters: | |
| 621 | +** | |
| 622 | +** name=NAME[.tar.gz] The base name of the output file. The default | |
| 623 | +** value is a configuration parameter in the project | |
| 624 | +** settings. A prefix of the name, omitting the extension, | |
| 625 | +** is used as the top-most directory name. | |
| 626 | +** | |
| 627 | +** uuid=TAG The check-in that is turned into a tarball. | |
| 628 | +** Defaults to "trunk". | |
| 629 | +** | |
| 630 | +** in=PATTERN Only include files that match the comma-separate | |
| 631 | +** list of GLOB patterns in PATTERN, as with ex= | |
| 632 | +** | |
| 633 | +** ex=PATTERN Omit any file that match PATTERN. PATTERN is a | |
| 634 | +** comma-separated list of GLOB patterns, where each | |
| 635 | +** pattern can optionally be quoted using ".." or '..'. | |
| 636 | +** Any file matching both ex= and in= is excluded. | |
| 600 | 637 | */ |
| 601 | 638 | void tarball_page(void){ |
| 602 | 639 | int rid; |
| 603 | 640 | char *zName, *zRid, *zKey; |
| 604 | 641 | int nName, nRid; |
| 605 | - Blob tarball; | |
| 642 | + const char *zInclude; /* The in= query parameter */ | |
| 643 | + const char *zExclude; /* The ex= query parameter */ | |
| 644 | + Blob cacheKey; /* The key to cache */ | |
| 645 | + Glob *pInclude = 0; /* The compiled in= glob pattern */ | |
| 646 | + Glob *pExclude = 0; /* The compiled ex= glob pattern */ | |
| 647 | + Blob tarball; /* Tarball accumulated here */ | |
| 606 | 648 | |
| 607 | 649 | login_check_credentials(); |
| 608 | 650 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 609 | 651 | load_control(); |
| 610 | 652 | zName = mprintf("%s", PD("name","")); |
| 611 | 653 | nName = strlen(zName); |
| 612 | 654 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 613 | 655 | nRid = strlen(zRid); |
| 656 | + zInclude = P("in"); | |
| 657 | + if( zInclude ) pInclude = glob_create(zInclude); | |
| 658 | + zExclude = P("ex"); | |
| 659 | + if( zExclude ) pExclude = glob_create(zExclude); | |
| 660 | + | |
| 614 | 661 | if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){ |
| 615 | 662 | /* Special case: Remove the ".tar.gz" suffix. */ |
| 616 | 663 | nName -= 7; |
| 617 | 664 | zName[nName] = 0; |
| 618 | 665 | }else{ |
| @@ -629,16 +676,29 @@ | ||
| 629 | 676 | if( rid==0 ){ |
| 630 | 677 | @ Not found |
| 631 | 678 | return; |
| 632 | 679 | } |
| 633 | 680 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 634 | - zKey = db_text(0, "SELECT '/tarball/'||uuid||'/%q'" | |
| 635 | - " FROM blob WHERE rid=%d",zName,rid); | |
| 681 | + | |
| 682 | + /* Compute a unique key for the cache entry based on query parameters */ | |
| 683 | + blob_init(&cacheKey, 0, 0); | |
| 684 | + blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid)); | |
| 685 | + if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude); | |
| 686 | + if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude); | |
| 687 | + blob_appendf(&cacheKey, "/%q", zName); | |
| 688 | + zKey = blob_str(&cacheKey); | |
| 689 | + | |
| 636 | 690 | if( P("debug")!=0 ){ |
| 637 | 691 | style_header("Tarball Generator Debug Screen"); |
| 638 | 692 | @ zName = "%h(zName)"<br> |
| 639 | 693 | @ rid = %d(rid)<br> |
| 694 | + if( zInclude ){ | |
| 695 | + @ zInclude = "%h(zInclude)"<br> | |
| 696 | + } | |
| 697 | + if( zExclude ){ | |
| 698 | + @ zExclude = "%h(zExclude)"<br> | |
| 699 | + } | |
| 640 | 700 | @ zKey = "%h(zKey)" |
| 641 | 701 | style_footer(); |
| 642 | 702 | return; |
| 643 | 703 | } |
| 644 | 704 | if( referred_from_login() ){ |
| @@ -652,14 +712,16 @@ | ||
| 652 | 712 | style_footer(); |
| 653 | 713 | return; |
| 654 | 714 | } |
| 655 | 715 | blob_zero(&tarball); |
| 656 | 716 | if( cache_read(&tarball, zKey)==0 ){ |
| 657 | - tarball_of_checkin(rid, &tarball, zName); | |
| 717 | + tarball_of_checkin(rid, &tarball, zName, pInclude, pExclude); | |
| 658 | 718 | cache_write(&tarball, zKey); |
| 659 | 719 | } |
| 660 | - free( zName ); | |
| 661 | - free( zRid ); | |
| 662 | - free( zKey ); | |
| 720 | + glob_free(pInclude); | |
| 721 | + glob_free(pExclude); | |
| 722 | + fossil_free(zName); | |
| 723 | + fossil_free(zRid); | |
| 724 | + blob_reset(&cacheKey); | |
| 663 | 725 | cgi_set_content(&tarball); |
| 664 | 726 | cgi_set_content_type("application/x-compressed"); |
| 665 | 727 | } |
| 666 | 728 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -73,11 +73,11 @@ | |
| 73 | ); |
| 74 | } |
| 75 | |
| 76 | |
| 77 | /* |
| 78 | ** verify that lla characters in 'zName' are in the |
| 79 | ** ISO646 (=ASCII) character set. |
| 80 | */ |
| 81 | static int is_iso646_name( |
| 82 | const char *zName, /* file path */ |
| 83 | int nName /* path length */ |
| @@ -90,11 +90,11 @@ | |
| 90 | return 1; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | /* |
| 95 | ** copy string pSrc into pDst, truncating or padding with 0 if necessary |
| 96 | */ |
| 97 | static void padded_copy( |
| 98 | char *pDest, |
| 99 | int nDest, |
| 100 | const char *pSrc, |
| @@ -446,15 +446,16 @@ | |
| 446 | blob_write_to_file(&zip, g.argv[2]); |
| 447 | } |
| 448 | |
| 449 | /* |
| 450 | ** Given the RID for a check-in, construct a tarball containing |
| 451 | ** all files in that check-in |
| 452 | ** |
| 453 | ** If RID is for an object that is not a real manifest, then the |
| 454 | ** resulting tarball contains a single file which is the RID |
| 455 | ** object. |
| 456 | ** |
| 457 | ** If the RID object does not exist in the repository, then |
| 458 | ** pTar is zeroed. |
| 459 | ** |
| 460 | ** zDir is a "synthetic" subdirectory which all files get |
| @@ -462,11 +463,17 @@ | |
| 462 | ** which case it is ignored. The intention is to create a tarball which |
| 463 | ** politely expands into a subdir instead of filling your current dir |
| 464 | ** with source files. For example, pass a UUID or "ProjectName". |
| 465 | ** |
| 466 | */ |
| 467 | void tarball_of_checkin(int rid, Blob *pTar, const char *zDir){ |
| 468 | Blob mfile, hash, file; |
| 469 | Manifest *pManifest; |
| 470 | ManifestFile *pFile; |
| 471 | Blob filename; |
| 472 | int nPrefix; |
| @@ -488,11 +495,14 @@ | |
| 488 | |
| 489 | pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
| 490 | if( pManifest ){ |
| 491 | mTime = (pManifest->rDate - 2440587.5)*86400.0; |
| 492 | tar_begin(mTime); |
| 493 | if( db_get_boolean("manifest", 0) ){ |
| 494 | blob_append(&filename, "manifest", -1); |
| 495 | zName = blob_str(&filename); |
| 496 | sha1sum_blob(&mfile, &hash); |
| 497 | sterilize_manifest(&mfile); |
| 498 | tar_add_file(zName, &mfile, 0, mTime); |
| @@ -504,11 +514,14 @@ | |
| 504 | tar_add_file(zName, &hash, 0, mTime); |
| 505 | blob_reset(&hash); |
| 506 | } |
| 507 | manifest_file_rewind(pManifest); |
| 508 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 509 | int fid = uuid_to_rid(pFile->zUuid, 0); |
| 510 | if( fid ){ |
| 511 | content_get(fid, &file); |
| 512 | blob_resize(&filename, nPrefix); |
| 513 | blob_append(&filename, pFile->zName, -1); |
| 514 | zName = blob_str(&filename); |
| @@ -539,19 +552,34 @@ | |
| 539 | ** option is used, its argument becomes the name of the top-level directory |
| 540 | ** in the resulting tarball. If --name is omitted, the top-level directory |
| 541 | ** named is derived from the project name, the check-in date and time, and |
| 542 | ** the artifact ID of the check-in. |
| 543 | ** |
| 544 | ** Options: |
| 545 | ** --name DIRECTORYNAME The name of the top-level directory in the archive |
| 546 | ** -R REPOSITORY Specify a Fossil repository |
| 547 | */ |
| 548 | void tarball_cmd(void){ |
| 549 | int rid; |
| 550 | Blob tarball; |
| 551 | const char *zName; |
| 552 | zName = find_option("name", 0, 1); |
| 553 | db_find_and_open_repository(0, 0); |
| 554 | |
| 555 | /* We should be done with options.. */ |
| 556 | verify_all_options(); |
| 557 | |
| @@ -573,46 +601,65 @@ | |
| 573 | " WHERE event.objid=%d" |
| 574 | " AND blob.rid=%d", |
| 575 | db_get("project-name", "unnamed"), rid, rid |
| 576 | ); |
| 577 | } |
| 578 | tarball_of_checkin(rid, &tarball, zName); |
| 579 | blob_write_to_file(&tarball, g.argv[3]); |
| 580 | blob_reset(&tarball); |
| 581 | } |
| 582 | |
| 583 | /* |
| 584 | ** WEBPAGE: tarball |
| 585 | ** URL: /tarball/RID.tar.gz |
| 586 | ** |
| 587 | ** Generate a compressed tarball for a check-in. |
| 588 | ** Return that tarball as the HTTP reply content. |
| 589 | ** |
| 590 | ** Optional URL Parameters: |
| 591 | ** |
| 592 | ** - name=NAME[.tar.gz] is base name of the output file. Defaults to |
| 593 | ** something project/version-specific. The prefix of the name, up to |
| 594 | ** the last '.', are used as the top-most directory name in the tar |
| 595 | ** output. |
| 596 | ** |
| 597 | ** - uuid=the version to tar (may be a tag/branch name). |
| 598 | ** Defaults to "trunk". |
| 599 | ** |
| 600 | */ |
| 601 | void tarball_page(void){ |
| 602 | int rid; |
| 603 | char *zName, *zRid, *zKey; |
| 604 | int nName, nRid; |
| 605 | Blob tarball; |
| 606 | |
| 607 | login_check_credentials(); |
| 608 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 609 | load_control(); |
| 610 | zName = mprintf("%s", PD("name","")); |
| 611 | nName = strlen(zName); |
| 612 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 613 | nRid = strlen(zRid); |
| 614 | if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){ |
| 615 | /* Special case: Remove the ".tar.gz" suffix. */ |
| 616 | nName -= 7; |
| 617 | zName[nName] = 0; |
| 618 | }else{ |
| @@ -629,16 +676,29 @@ | |
| 629 | if( rid==0 ){ |
| 630 | @ Not found |
| 631 | return; |
| 632 | } |
| 633 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 634 | zKey = db_text(0, "SELECT '/tarball/'||uuid||'/%q'" |
| 635 | " FROM blob WHERE rid=%d",zName,rid); |
| 636 | if( P("debug")!=0 ){ |
| 637 | style_header("Tarball Generator Debug Screen"); |
| 638 | @ zName = "%h(zName)"<br> |
| 639 | @ rid = %d(rid)<br> |
| 640 | @ zKey = "%h(zKey)" |
| 641 | style_footer(); |
| 642 | return; |
| 643 | } |
| 644 | if( referred_from_login() ){ |
| @@ -652,14 +712,16 @@ | |
| 652 | style_footer(); |
| 653 | return; |
| 654 | } |
| 655 | blob_zero(&tarball); |
| 656 | if( cache_read(&tarball, zKey)==0 ){ |
| 657 | tarball_of_checkin(rid, &tarball, zName); |
| 658 | cache_write(&tarball, zKey); |
| 659 | } |
| 660 | free( zName ); |
| 661 | free( zRid ); |
| 662 | free( zKey ); |
| 663 | cgi_set_content(&tarball); |
| 664 | cgi_set_content_type("application/x-compressed"); |
| 665 | } |
| 666 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -73,11 +73,11 @@ | |
| 73 | ); |
| 74 | } |
| 75 | |
| 76 | |
| 77 | /* |
| 78 | ** Verify that all characters in 'zName' are in the |
| 79 | ** ISO646 (=ASCII) character set. |
| 80 | */ |
| 81 | static int is_iso646_name( |
| 82 | const char *zName, /* file path */ |
| 83 | int nName /* path length */ |
| @@ -90,11 +90,11 @@ | |
| 90 | return 1; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | /* |
| 95 | ** copy string pSrc into pDst, truncating or padding with 0 if necessary |
| 96 | */ |
| 97 | static void padded_copy( |
| 98 | char *pDest, |
| 99 | int nDest, |
| 100 | const char *pSrc, |
| @@ -446,15 +446,16 @@ | |
| 446 | blob_write_to_file(&zip, g.argv[2]); |
| 447 | } |
| 448 | |
| 449 | /* |
| 450 | ** Given the RID for a check-in, construct a tarball containing |
| 451 | ** all files in that check-in that match pGlob (or all files if |
| 452 | ** pGlob is NULL). |
| 453 | ** |
| 454 | ** If RID is for an object that is not a real manifest, then the |
| 455 | ** resulting tarball contains a single file which is the RID |
| 456 | ** object. pInclude and pExclude are ignored in this case. |
| 457 | ** |
| 458 | ** If the RID object does not exist in the repository, then |
| 459 | ** pTar is zeroed. |
| 460 | ** |
| 461 | ** zDir is a "synthetic" subdirectory which all files get |
| @@ -462,11 +463,17 @@ | |
| 463 | ** which case it is ignored. The intention is to create a tarball which |
| 464 | ** politely expands into a subdir instead of filling your current dir |
| 465 | ** with source files. For example, pass a UUID or "ProjectName". |
| 466 | ** |
| 467 | */ |
| 468 | void tarball_of_checkin( |
| 469 | int rid, /* The RID of the checkin from which to form a tarball */ |
| 470 | Blob *pTar, /* Write the tarball into this blob */ |
| 471 | const char *zDir, /* Directory prefix for all file added to tarball */ |
| 472 | Glob *pInclude, /* Only add files matching this pattern */ |
| 473 | Glob *pExclude /* Exclude files matching this pattern */ |
| 474 | ){ |
| 475 | Blob mfile, hash, file; |
| 476 | Manifest *pManifest; |
| 477 | ManifestFile *pFile; |
| 478 | Blob filename; |
| 479 | int nPrefix; |
| @@ -488,11 +495,14 @@ | |
| 495 | |
| 496 | pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
| 497 | if( pManifest ){ |
| 498 | mTime = (pManifest->rDate - 2440587.5)*86400.0; |
| 499 | tar_begin(mTime); |
| 500 | if( (pInclude==0 || glob_match(pInclude, "manifest")) |
| 501 | && !glob_match(pExclude, "manifest") |
| 502 | && db_get_boolean("manifest", 0) |
| 503 | ){ |
| 504 | blob_append(&filename, "manifest", -1); |
| 505 | zName = blob_str(&filename); |
| 506 | sha1sum_blob(&mfile, &hash); |
| 507 | sterilize_manifest(&mfile); |
| 508 | tar_add_file(zName, &mfile, 0, mTime); |
| @@ -504,11 +514,14 @@ | |
| 514 | tar_add_file(zName, &hash, 0, mTime); |
| 515 | blob_reset(&hash); |
| 516 | } |
| 517 | manifest_file_rewind(pManifest); |
| 518 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 519 | int fid; |
| 520 | if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue; |
| 521 | if( glob_match(pExclude, pFile->zName) ) continue; |
| 522 | fid = uuid_to_rid(pFile->zUuid, 0); |
| 523 | if( fid ){ |
| 524 | content_get(fid, &file); |
| 525 | blob_resize(&filename, nPrefix); |
| 526 | blob_append(&filename, pFile->zName, -1); |
| 527 | zName = blob_str(&filename); |
| @@ -539,19 +552,34 @@ | |
| 552 | ** option is used, its argument becomes the name of the top-level directory |
| 553 | ** in the resulting tarball. If --name is omitted, the top-level directory |
| 554 | ** named is derived from the project name, the check-in date and time, and |
| 555 | ** the artifact ID of the check-in. |
| 556 | ** |
| 557 | ** The GLOBLIST argument to --exclude and --include can be a comma-separated |
| 558 | ** list of glob patterns, where each glob pattern may optionally be enclosed |
| 559 | ** in "..." or '...' so that it may contain commas. If a file matches both |
| 560 | ** --include and --exclude then it is excluded. |
| 561 | ** |
| 562 | ** Options: |
| 563 | ** -X|--exclude GLOBLIST Comma-separated list of GLOBs of files to exclude |
| 564 | ** --include GLOBLIST Comma-separated list of GLOBs of files to include |
| 565 | ** --name DIRECTORYNAME The name of the top-level directory in the archive |
| 566 | ** -R REPOSITORY Specify a Fossil repository |
| 567 | */ |
| 568 | void tarball_cmd(void){ |
| 569 | int rid; |
| 570 | Blob tarball; |
| 571 | const char *zName; |
| 572 | Glob *pInclude = 0; |
| 573 | Glob *pExclude = 0; |
| 574 | const char *zInclude; |
| 575 | const char *zExclude; |
| 576 | zName = find_option("name", 0, 1); |
| 577 | zExclude = find_option("exclude", "X", 1); |
| 578 | if( zExclude ) pExclude = glob_create(zExclude); |
| 579 | zInclude = find_option("include", 0, 1); |
| 580 | if( zInclude ) pInclude = glob_create(zInclude); |
| 581 | db_find_and_open_repository(0, 0); |
| 582 | |
| 583 | /* We should be done with options.. */ |
| 584 | verify_all_options(); |
| 585 | |
| @@ -573,46 +601,65 @@ | |
| 601 | " WHERE event.objid=%d" |
| 602 | " AND blob.rid=%d", |
| 603 | db_get("project-name", "unnamed"), rid, rid |
| 604 | ); |
| 605 | } |
| 606 | tarball_of_checkin(rid, &tarball, zName, pInclude, pExclude); |
| 607 | glob_free(pInclude); |
| 608 | glob_free(pExclude); |
| 609 | blob_write_to_file(&tarball, g.argv[3]); |
| 610 | blob_reset(&tarball); |
| 611 | } |
| 612 | |
| 613 | /* |
| 614 | ** WEBPAGE: tarball |
| 615 | ** URL: /tarball |
| 616 | ** |
| 617 | ** Generate a compressed tarball for a check-in and return that |
| 618 | ** tarball as the HTTP reply content. |
| 619 | ** |
| 620 | ** Query parameters: |
| 621 | ** |
| 622 | ** name=NAME[.tar.gz] The base name of the output file. The default |
| 623 | ** value is a configuration parameter in the project |
| 624 | ** settings. A prefix of the name, omitting the extension, |
| 625 | ** is used as the top-most directory name. |
| 626 | ** |
| 627 | ** uuid=TAG The check-in that is turned into a tarball. |
| 628 | ** Defaults to "trunk". |
| 629 | ** |
| 630 | ** in=PATTERN Only include files that match the comma-separate |
| 631 | ** list of GLOB patterns in PATTERN, as with ex= |
| 632 | ** |
| 633 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 634 | ** comma-separated list of GLOB patterns, where each |
| 635 | ** pattern can optionally be quoted using ".." or '..'. |
| 636 | ** Any file matching both ex= and in= is excluded. |
| 637 | */ |
| 638 | void tarball_page(void){ |
| 639 | int rid; |
| 640 | char *zName, *zRid, *zKey; |
| 641 | int nName, nRid; |
| 642 | const char *zInclude; /* The in= query parameter */ |
| 643 | const char *zExclude; /* The ex= query parameter */ |
| 644 | Blob cacheKey; /* The key to cache */ |
| 645 | Glob *pInclude = 0; /* The compiled in= glob pattern */ |
| 646 | Glob *pExclude = 0; /* The compiled ex= glob pattern */ |
| 647 | Blob tarball; /* Tarball accumulated here */ |
| 648 | |
| 649 | login_check_credentials(); |
| 650 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 651 | load_control(); |
| 652 | zName = mprintf("%s", PD("name","")); |
| 653 | nName = strlen(zName); |
| 654 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 655 | nRid = strlen(zRid); |
| 656 | zInclude = P("in"); |
| 657 | if( zInclude ) pInclude = glob_create(zInclude); |
| 658 | zExclude = P("ex"); |
| 659 | if( zExclude ) pExclude = glob_create(zExclude); |
| 660 | |
| 661 | if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){ |
| 662 | /* Special case: Remove the ".tar.gz" suffix. */ |
| 663 | nName -= 7; |
| 664 | zName[nName] = 0; |
| 665 | }else{ |
| @@ -629,16 +676,29 @@ | |
| 676 | if( rid==0 ){ |
| 677 | @ Not found |
| 678 | return; |
| 679 | } |
| 680 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 681 | |
| 682 | /* Compute a unique key for the cache entry based on query parameters */ |
| 683 | blob_init(&cacheKey, 0, 0); |
| 684 | blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid)); |
| 685 | if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude); |
| 686 | if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude); |
| 687 | blob_appendf(&cacheKey, "/%q", zName); |
| 688 | zKey = blob_str(&cacheKey); |
| 689 | |
| 690 | if( P("debug")!=0 ){ |
| 691 | style_header("Tarball Generator Debug Screen"); |
| 692 | @ zName = "%h(zName)"<br> |
| 693 | @ rid = %d(rid)<br> |
| 694 | if( zInclude ){ |
| 695 | @ zInclude = "%h(zInclude)"<br> |
| 696 | } |
| 697 | if( zExclude ){ |
| 698 | @ zExclude = "%h(zExclude)"<br> |
| 699 | } |
| 700 | @ zKey = "%h(zKey)" |
| 701 | style_footer(); |
| 702 | return; |
| 703 | } |
| 704 | if( referred_from_login() ){ |
| @@ -652,14 +712,16 @@ | |
| 712 | style_footer(); |
| 713 | return; |
| 714 | } |
| 715 | blob_zero(&tarball); |
| 716 | if( cache_read(&tarball, zKey)==0 ){ |
| 717 | tarball_of_checkin(rid, &tarball, zName, pInclude, pExclude); |
| 718 | cache_write(&tarball, zKey); |
| 719 | } |
| 720 | glob_free(pInclude); |
| 721 | glob_free(pExclude); |
| 722 | fossil_free(zName); |
| 723 | fossil_free(zRid); |
| 724 | blob_reset(&cacheKey); |
| 725 | cgi_set_content(&tarball); |
| 726 | cgi_set_content_type("application/x-compressed"); |
| 727 | } |
| 728 |
+83
-23
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -309,11 +309,11 @@ | ||
| 309 | 309 | ** Given the RID for a manifest, construct a ZIP archive containing |
| 310 | 310 | ** all files in the corresponding baseline. |
| 311 | 311 | ** |
| 312 | 312 | ** If RID is for an object that is not a real manifest, then the |
| 313 | 313 | ** resulting ZIP archive contains a single file which is the RID |
| 314 | -** object. | |
| 314 | +** object. The pInclude and pExclude parameters are ignored in this case. | |
| 315 | 315 | ** |
| 316 | 316 | ** If the RID object does not exist in the repository, then |
| 317 | 317 | ** pZip is zeroed. |
| 318 | 318 | ** |
| 319 | 319 | ** zDir is a "synthetic" subdirectory which all zipped files get |
| @@ -321,11 +321,17 @@ | ||
| 321 | 321 | ** in which case it is ignored. The intention is to create a zip which |
| 322 | 322 | ** politely expands into a subdir instead of filling your current dir |
| 323 | 323 | ** with source files. For example, pass a UUID or "ProjectName". |
| 324 | 324 | ** |
| 325 | 325 | */ |
| 326 | -void zip_of_baseline(int rid, Blob *pZip, const char *zDir){ | |
| 326 | +void zip_of_checkin( | |
| 327 | + int rid, /* The RID of the checkin to construct the ZIP archive from */ | |
| 328 | + Blob *pZip, /* Write the ZIP archive content into this blob */ | |
| 329 | + const char *zDir, /* Top-level directory of the ZIP archive */ | |
| 330 | + Glob *pInclude, /* Only include files that match this pattern */ | |
| 331 | + Glob *pExclude /* Exclude files that match this pattern */ | |
| 332 | +){ | |
| 327 | 333 | Blob mfile, hash, file; |
| 328 | 334 | Manifest *pManifest; |
| 329 | 335 | ManifestFile *pFile; |
| 330 | 336 | Blob filename; |
| 331 | 337 | int nPrefix; |
| @@ -346,11 +352,14 @@ | ||
| 346 | 352 | |
| 347 | 353 | pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
| 348 | 354 | if( pManifest ){ |
| 349 | 355 | char *zName; |
| 350 | 356 | zip_set_timedate(pManifest->rDate); |
| 351 | - if( db_get_boolean("manifest", 0) ){ | |
| 357 | + if( (pInclude==0 || glob_match(pInclude, "manifest")) | |
| 358 | + && !glob_match(pExclude, "manifest") | |
| 359 | + && db_get_boolean("manifest", 0) | |
| 360 | + ){ | |
| 352 | 361 | blob_append(&filename, "manifest", -1); |
| 353 | 362 | zName = blob_str(&filename); |
| 354 | 363 | zip_add_folders(zName); |
| 355 | 364 | sha1sum_blob(&mfile, &hash); |
| 356 | 365 | sterilize_manifest(&mfile); |
| @@ -363,11 +372,14 @@ | ||
| 363 | 372 | zip_add_file(zName, &hash, 0); |
| 364 | 373 | blob_reset(&hash); |
| 365 | 374 | } |
| 366 | 375 | manifest_file_rewind(pManifest); |
| 367 | 376 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 368 | - int fid = uuid_to_rid(pFile->zUuid, 0); | |
| 377 | + int fid; | |
| 378 | + if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue; | |
| 379 | + if( glob_match(pExclude, pFile->zName) ) continue; | |
| 380 | + fid = uuid_to_rid(pFile->zUuid, 0); | |
| 369 | 381 | if( fid ){ |
| 370 | 382 | content_get(fid, &file); |
| 371 | 383 | blob_resize(&filename, nPrefix); |
| 372 | 384 | blob_append(&filename, pFile->zName, -1); |
| 373 | 385 | zName = blob_str(&filename); |
| @@ -385,23 +397,42 @@ | ||
| 385 | 397 | } |
| 386 | 398 | |
| 387 | 399 | /* |
| 388 | 400 | ** COMMAND: zip* |
| 389 | 401 | ** |
| 390 | -** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO] | |
| 402 | +** Usage: %fossil zip VERSION OUTPUTFILE [OPTIONS] | |
| 391 | 403 | ** |
| 392 | -** Generate a ZIP archive for a specified version. If the --name option is | |
| 404 | +** Generate a ZIP archive for a check-in. If the --name option is | |
| 393 | 405 | ** used, its argument becomes the name of the top-level directory in the |
| 394 | 406 | ** resulting ZIP archive. If --name is omitted, the top-level directory |
| 395 | 407 | ** named is derived from the project name, the check-in date and time, and |
| 396 | 408 | ** the artifact ID of the check-in. |
| 409 | +** | |
| 410 | +** The GLOBLIST argument to --exclude and --include can be a comma-separated | |
| 411 | +** list of glob patterns, where each glob pattern may optionally be enclosed | |
| 412 | +** in "..." or '...' so that it may contain commas. If a file matches both | |
| 413 | +** --include and --exclude then it is excluded. | |
| 414 | +** | |
| 415 | +** Options: | |
| 416 | +** -X|--exclude GLOBLIST Comma-separated list of GLOBs of files to exclude | |
| 417 | +** --include GLOBLIST Comma-separated list of GLOBs of files to include | |
| 418 | +** --name DIRECTORYNAME The name of the top-level directory in the archive | |
| 419 | +** -R REPOSITORY Specify a Fossil repository | |
| 397 | 420 | */ |
| 398 | -void baseline_zip_cmd(void){ | |
| 421 | +void zip_cmd(void){ | |
| 399 | 422 | int rid; |
| 400 | 423 | Blob zip; |
| 401 | 424 | const char *zName; |
| 425 | + Glob *pInclude = 0; | |
| 426 | + Glob *pExclude = 0; | |
| 427 | + const char *zInclude; | |
| 428 | + const char *zExclude; | |
| 402 | 429 | zName = find_option("name", 0, 1); |
| 430 | + zExclude = find_option("exclude", "X", 1); | |
| 431 | + if( zExclude ) pExclude = glob_create(zExclude); | |
| 432 | + zInclude = find_option("include", 0, 1); | |
| 433 | + if( zInclude ) pInclude = glob_create(zInclude); | |
| 403 | 434 | db_find_and_open_repository(0, 0); |
| 404 | 435 | |
| 405 | 436 | /* We should be done with options.. */ |
| 406 | 437 | verify_all_options(); |
| 407 | 438 | |
| @@ -418,46 +449,65 @@ | ||
| 418 | 449 | " WHERE event.objid=%d" |
| 419 | 450 | " AND blob.rid=%d", |
| 420 | 451 | db_get("project-name", "unnamed"), rid, rid |
| 421 | 452 | ); |
| 422 | 453 | } |
| 423 | - zip_of_baseline(rid, &zip, zName); | |
| 454 | + zip_of_checkin(rid, &zip, zName, pInclude, pExclude); | |
| 424 | 455 | blob_write_to_file(&zip, g.argv[3]); |
| 456 | + glob_free(pInclude); | |
| 457 | + glob_free(pExclude); | |
| 425 | 458 | } |
| 426 | 459 | |
| 427 | 460 | /* |
| 428 | 461 | ** WEBPAGE: zip |
| 429 | -** URL: /zip/RID.zip | |
| 462 | +** URL: /zip | |
| 430 | 463 | ** |
| 431 | -** Generate a ZIP archive for the baseline. | |
| 464 | +** Generate a ZIP archive for a checkin specified by the uuid= query parameter. | |
| 432 | 465 | ** Return that ZIP archive as the HTTP reply content. |
| 433 | 466 | ** |
| 434 | -** Optional URL Parameters: | |
| 435 | -** | |
| 436 | -** - name=NAME[.zip] is the name of the output file. Defaults to | |
| 437 | -** something project/version-specific. The base part of the | |
| 438 | -** name, up to the last dot, is used as the top-most directory | |
| 439 | -** name in the output file. | |
| 440 | -** | |
| 441 | -** - uuid=the version to zip (may be a tag/branch name). | |
| 442 | -** Defaults to "trunk". | |
| 443 | -** | |
| 467 | +** Query parameters: | |
| 468 | +** | |
| 469 | +** name=NAME[.tar.gz] The base name of the output file. The default | |
| 470 | +** value is a configuration parameter in the project | |
| 471 | +** settings. A prefix of the name, omitting the extension, | |
| 472 | +** is used as the top-most directory name. | |
| 473 | + | |
| 474 | +** | |
| 475 | +** uuid=TAG The check-in that is turned into a tarball. | |
| 476 | +** Defaults to "trunk". | |
| 477 | +** | |
| 478 | +** in=PATTERN Only include files that match the comma-separate | |
| 479 | +** list of GLOB patterns in PATTERN, as with ex= | |
| 480 | +** | |
| 481 | +** ex=PATTERN Omit any file that match PATTERN. PATTERN is a | |
| 482 | +** comma-separated list of GLOB patterns, where each | |
| 483 | +** pattern can optionally be quoted using ".." or '..'. | |
| 484 | +** Any file matching both ex= and in= is excluded. | |
| 444 | 485 | */ |
| 445 | 486 | void baseline_zip_page(void){ |
| 446 | 487 | int rid; |
| 447 | 488 | char *zName, *zRid; |
| 448 | 489 | int nName, nRid; |
| 449 | 490 | Blob zip; |
| 450 | 491 | char *zKey; |
| 492 | + const char *zInclude; /* The in= query parameter */ | |
| 493 | + const char *zExclude; /* The ex= query parameter */ | |
| 494 | + Blob cacheKey; /* The key to cache */ | |
| 495 | + Glob *pInclude = 0; /* The compiled in= glob pattern */ | |
| 496 | + Glob *pExclude = 0; /* The compiled ex= glob pattern */ | |
| 451 | 497 | |
| 452 | 498 | login_check_credentials(); |
| 453 | 499 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 454 | 500 | load_control(); |
| 455 | 501 | zName = mprintf("%s", PD("name","")); |
| 456 | 502 | nName = strlen(zName); |
| 457 | 503 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 458 | 504 | nRid = strlen(zRid); |
| 505 | + zInclude = P("in"); | |
| 506 | + if( zInclude ) pInclude = glob_create(zInclude); | |
| 507 | + zExclude = P("ex"); | |
| 508 | + if( zExclude ) pExclude = glob_create(zExclude); | |
| 459 | 509 | if( nName>4 && fossil_strcmp(&zName[nName-4], ".zip")==0 ){ |
| 460 | 510 | /* Special case: Remove the ".zip" suffix. */ |
| 461 | 511 | nName -= 4; |
| 462 | 512 | zName[nName] = 0; |
| 463 | 513 | }else{ |
| @@ -485,17 +535,27 @@ | ||
| 485 | 535 | @ </form> |
| 486 | 536 | style_footer(); |
| 487 | 537 | return; |
| 488 | 538 | } |
| 489 | 539 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 490 | - zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid); | |
| 540 | + | |
| 541 | + /* Compute a unique key for the cache entry based on query parameters */ | |
| 542 | + blob_init(&cacheKey, 0, 0); | |
| 543 | + blob_appendf(&cacheKey, "/zip/%z", rid_to_uuid(rid)); | |
| 544 | + if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude); | |
| 545 | + if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude); | |
| 546 | + blob_appendf(&cacheKey, "/%q", zName); | |
| 547 | + zKey = blob_str(&cacheKey); | |
| 548 | + | |
| 491 | 549 | blob_zero(&zip); |
| 492 | 550 | if( cache_read(&zip, zKey)==0 ){ |
| 493 | - zip_of_baseline(rid, &zip, zName); | |
| 551 | + zip_of_checkin(rid, &zip, zName, pInclude, pExclude); | |
| 494 | 552 | cache_write(&zip, zKey); |
| 495 | 553 | } |
| 496 | 554 | fossil_free( zName ); |
| 497 | 555 | fossil_free( zRid ); |
| 498 | - fossil_free( zKey ); | |
| 556 | + blob_reset(&cacheKey); | |
| 557 | + glob_free(pInclude); | |
| 558 | + glob_free(pExclude); | |
| 499 | 559 | cgi_set_content(&zip); |
| 500 | 560 | cgi_set_content_type("application/zip"); |
| 501 | 561 | } |
| 502 | 562 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -309,11 +309,11 @@ | |
| 309 | ** Given the RID for a manifest, construct a ZIP archive containing |
| 310 | ** all files in the corresponding baseline. |
| 311 | ** |
| 312 | ** If RID is for an object that is not a real manifest, then the |
| 313 | ** resulting ZIP archive contains a single file which is the RID |
| 314 | ** object. |
| 315 | ** |
| 316 | ** If the RID object does not exist in the repository, then |
| 317 | ** pZip is zeroed. |
| 318 | ** |
| 319 | ** zDir is a "synthetic" subdirectory which all zipped files get |
| @@ -321,11 +321,17 @@ | |
| 321 | ** in which case it is ignored. The intention is to create a zip which |
| 322 | ** politely expands into a subdir instead of filling your current dir |
| 323 | ** with source files. For example, pass a UUID or "ProjectName". |
| 324 | ** |
| 325 | */ |
| 326 | void zip_of_baseline(int rid, Blob *pZip, const char *zDir){ |
| 327 | Blob mfile, hash, file; |
| 328 | Manifest *pManifest; |
| 329 | ManifestFile *pFile; |
| 330 | Blob filename; |
| 331 | int nPrefix; |
| @@ -346,11 +352,14 @@ | |
| 346 | |
| 347 | pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
| 348 | if( pManifest ){ |
| 349 | char *zName; |
| 350 | zip_set_timedate(pManifest->rDate); |
| 351 | if( db_get_boolean("manifest", 0) ){ |
| 352 | blob_append(&filename, "manifest", -1); |
| 353 | zName = blob_str(&filename); |
| 354 | zip_add_folders(zName); |
| 355 | sha1sum_blob(&mfile, &hash); |
| 356 | sterilize_manifest(&mfile); |
| @@ -363,11 +372,14 @@ | |
| 363 | zip_add_file(zName, &hash, 0); |
| 364 | blob_reset(&hash); |
| 365 | } |
| 366 | manifest_file_rewind(pManifest); |
| 367 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 368 | int fid = uuid_to_rid(pFile->zUuid, 0); |
| 369 | if( fid ){ |
| 370 | content_get(fid, &file); |
| 371 | blob_resize(&filename, nPrefix); |
| 372 | blob_append(&filename, pFile->zName, -1); |
| 373 | zName = blob_str(&filename); |
| @@ -385,23 +397,42 @@ | |
| 385 | } |
| 386 | |
| 387 | /* |
| 388 | ** COMMAND: zip* |
| 389 | ** |
| 390 | ** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO] |
| 391 | ** |
| 392 | ** Generate a ZIP archive for a specified version. If the --name option is |
| 393 | ** used, its argument becomes the name of the top-level directory in the |
| 394 | ** resulting ZIP archive. If --name is omitted, the top-level directory |
| 395 | ** named is derived from the project name, the check-in date and time, and |
| 396 | ** the artifact ID of the check-in. |
| 397 | */ |
| 398 | void baseline_zip_cmd(void){ |
| 399 | int rid; |
| 400 | Blob zip; |
| 401 | const char *zName; |
| 402 | zName = find_option("name", 0, 1); |
| 403 | db_find_and_open_repository(0, 0); |
| 404 | |
| 405 | /* We should be done with options.. */ |
| 406 | verify_all_options(); |
| 407 | |
| @@ -418,46 +449,65 @@ | |
| 418 | " WHERE event.objid=%d" |
| 419 | " AND blob.rid=%d", |
| 420 | db_get("project-name", "unnamed"), rid, rid |
| 421 | ); |
| 422 | } |
| 423 | zip_of_baseline(rid, &zip, zName); |
| 424 | blob_write_to_file(&zip, g.argv[3]); |
| 425 | } |
| 426 | |
| 427 | /* |
| 428 | ** WEBPAGE: zip |
| 429 | ** URL: /zip/RID.zip |
| 430 | ** |
| 431 | ** Generate a ZIP archive for the baseline. |
| 432 | ** Return that ZIP archive as the HTTP reply content. |
| 433 | ** |
| 434 | ** Optional URL Parameters: |
| 435 | ** |
| 436 | ** - name=NAME[.zip] is the name of the output file. Defaults to |
| 437 | ** something project/version-specific. The base part of the |
| 438 | ** name, up to the last dot, is used as the top-most directory |
| 439 | ** name in the output file. |
| 440 | ** |
| 441 | ** - uuid=the version to zip (may be a tag/branch name). |
| 442 | ** Defaults to "trunk". |
| 443 | ** |
| 444 | */ |
| 445 | void baseline_zip_page(void){ |
| 446 | int rid; |
| 447 | char *zName, *zRid; |
| 448 | int nName, nRid; |
| 449 | Blob zip; |
| 450 | char *zKey; |
| 451 | |
| 452 | login_check_credentials(); |
| 453 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 454 | load_control(); |
| 455 | zName = mprintf("%s", PD("name","")); |
| 456 | nName = strlen(zName); |
| 457 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 458 | nRid = strlen(zRid); |
| 459 | if( nName>4 && fossil_strcmp(&zName[nName-4], ".zip")==0 ){ |
| 460 | /* Special case: Remove the ".zip" suffix. */ |
| 461 | nName -= 4; |
| 462 | zName[nName] = 0; |
| 463 | }else{ |
| @@ -485,17 +535,27 @@ | |
| 485 | @ </form> |
| 486 | style_footer(); |
| 487 | return; |
| 488 | } |
| 489 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 490 | zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid); |
| 491 | blob_zero(&zip); |
| 492 | if( cache_read(&zip, zKey)==0 ){ |
| 493 | zip_of_baseline(rid, &zip, zName); |
| 494 | cache_write(&zip, zKey); |
| 495 | } |
| 496 | fossil_free( zName ); |
| 497 | fossil_free( zRid ); |
| 498 | fossil_free( zKey ); |
| 499 | cgi_set_content(&zip); |
| 500 | cgi_set_content_type("application/zip"); |
| 501 | } |
| 502 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -309,11 +309,11 @@ | |
| 309 | ** Given the RID for a manifest, construct a ZIP archive containing |
| 310 | ** all files in the corresponding baseline. |
| 311 | ** |
| 312 | ** If RID is for an object that is not a real manifest, then the |
| 313 | ** resulting ZIP archive contains a single file which is the RID |
| 314 | ** object. The pInclude and pExclude parameters are ignored in this case. |
| 315 | ** |
| 316 | ** If the RID object does not exist in the repository, then |
| 317 | ** pZip is zeroed. |
| 318 | ** |
| 319 | ** zDir is a "synthetic" subdirectory which all zipped files get |
| @@ -321,11 +321,17 @@ | |
| 321 | ** in which case it is ignored. The intention is to create a zip which |
| 322 | ** politely expands into a subdir instead of filling your current dir |
| 323 | ** with source files. For example, pass a UUID or "ProjectName". |
| 324 | ** |
| 325 | */ |
| 326 | void zip_of_checkin( |
| 327 | int rid, /* The RID of the checkin to construct the ZIP archive from */ |
| 328 | Blob *pZip, /* Write the ZIP archive content into this blob */ |
| 329 | const char *zDir, /* Top-level directory of the ZIP archive */ |
| 330 | Glob *pInclude, /* Only include files that match this pattern */ |
| 331 | Glob *pExclude /* Exclude files that match this pattern */ |
| 332 | ){ |
| 333 | Blob mfile, hash, file; |
| 334 | Manifest *pManifest; |
| 335 | ManifestFile *pFile; |
| 336 | Blob filename; |
| 337 | int nPrefix; |
| @@ -346,11 +352,14 @@ | |
| 352 | |
| 353 | pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
| 354 | if( pManifest ){ |
| 355 | char *zName; |
| 356 | zip_set_timedate(pManifest->rDate); |
| 357 | if( (pInclude==0 || glob_match(pInclude, "manifest")) |
| 358 | && !glob_match(pExclude, "manifest") |
| 359 | && db_get_boolean("manifest", 0) |
| 360 | ){ |
| 361 | blob_append(&filename, "manifest", -1); |
| 362 | zName = blob_str(&filename); |
| 363 | zip_add_folders(zName); |
| 364 | sha1sum_blob(&mfile, &hash); |
| 365 | sterilize_manifest(&mfile); |
| @@ -363,11 +372,14 @@ | |
| 372 | zip_add_file(zName, &hash, 0); |
| 373 | blob_reset(&hash); |
| 374 | } |
| 375 | manifest_file_rewind(pManifest); |
| 376 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 377 | int fid; |
| 378 | if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue; |
| 379 | if( glob_match(pExclude, pFile->zName) ) continue; |
| 380 | fid = uuid_to_rid(pFile->zUuid, 0); |
| 381 | if( fid ){ |
| 382 | content_get(fid, &file); |
| 383 | blob_resize(&filename, nPrefix); |
| 384 | blob_append(&filename, pFile->zName, -1); |
| 385 | zName = blob_str(&filename); |
| @@ -385,23 +397,42 @@ | |
| 397 | } |
| 398 | |
| 399 | /* |
| 400 | ** COMMAND: zip* |
| 401 | ** |
| 402 | ** Usage: %fossil zip VERSION OUTPUTFILE [OPTIONS] |
| 403 | ** |
| 404 | ** Generate a ZIP archive for a check-in. If the --name option is |
| 405 | ** used, its argument becomes the name of the top-level directory in the |
| 406 | ** resulting ZIP archive. If --name is omitted, the top-level directory |
| 407 | ** named is derived from the project name, the check-in date and time, and |
| 408 | ** the artifact ID of the check-in. |
| 409 | ** |
| 410 | ** The GLOBLIST argument to --exclude and --include can be a comma-separated |
| 411 | ** list of glob patterns, where each glob pattern may optionally be enclosed |
| 412 | ** in "..." or '...' so that it may contain commas. If a file matches both |
| 413 | ** --include and --exclude then it is excluded. |
| 414 | ** |
| 415 | ** Options: |
| 416 | ** -X|--exclude GLOBLIST Comma-separated list of GLOBs of files to exclude |
| 417 | ** --include GLOBLIST Comma-separated list of GLOBs of files to include |
| 418 | ** --name DIRECTORYNAME The name of the top-level directory in the archive |
| 419 | ** -R REPOSITORY Specify a Fossil repository |
| 420 | */ |
| 421 | void zip_cmd(void){ |
| 422 | int rid; |
| 423 | Blob zip; |
| 424 | const char *zName; |
| 425 | Glob *pInclude = 0; |
| 426 | Glob *pExclude = 0; |
| 427 | const char *zInclude; |
| 428 | const char *zExclude; |
| 429 | zName = find_option("name", 0, 1); |
| 430 | zExclude = find_option("exclude", "X", 1); |
| 431 | if( zExclude ) pExclude = glob_create(zExclude); |
| 432 | zInclude = find_option("include", 0, 1); |
| 433 | if( zInclude ) pInclude = glob_create(zInclude); |
| 434 | db_find_and_open_repository(0, 0); |
| 435 | |
| 436 | /* We should be done with options.. */ |
| 437 | verify_all_options(); |
| 438 | |
| @@ -418,46 +449,65 @@ | |
| 449 | " WHERE event.objid=%d" |
| 450 | " AND blob.rid=%d", |
| 451 | db_get("project-name", "unnamed"), rid, rid |
| 452 | ); |
| 453 | } |
| 454 | zip_of_checkin(rid, &zip, zName, pInclude, pExclude); |
| 455 | blob_write_to_file(&zip, g.argv[3]); |
| 456 | glob_free(pInclude); |
| 457 | glob_free(pExclude); |
| 458 | } |
| 459 | |
| 460 | /* |
| 461 | ** WEBPAGE: zip |
| 462 | ** URL: /zip |
| 463 | ** |
| 464 | ** Generate a ZIP archive for a checkin specified by the uuid= query parameter. |
| 465 | ** Return that ZIP archive as the HTTP reply content. |
| 466 | ** |
| 467 | ** Query parameters: |
| 468 | ** |
| 469 | ** name=NAME[.tar.gz] The base name of the output file. The default |
| 470 | ** value is a configuration parameter in the project |
| 471 | ** settings. A prefix of the name, omitting the extension, |
| 472 | ** is used as the top-most directory name. |
| 473 | |
| 474 | ** |
| 475 | ** uuid=TAG The check-in that is turned into a tarball. |
| 476 | ** Defaults to "trunk". |
| 477 | ** |
| 478 | ** in=PATTERN Only include files that match the comma-separate |
| 479 | ** list of GLOB patterns in PATTERN, as with ex= |
| 480 | ** |
| 481 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 482 | ** comma-separated list of GLOB patterns, where each |
| 483 | ** pattern can optionally be quoted using ".." or '..'. |
| 484 | ** Any file matching both ex= and in= is excluded. |
| 485 | */ |
| 486 | void baseline_zip_page(void){ |
| 487 | int rid; |
| 488 | char *zName, *zRid; |
| 489 | int nName, nRid; |
| 490 | Blob zip; |
| 491 | char *zKey; |
| 492 | const char *zInclude; /* The in= query parameter */ |
| 493 | const char *zExclude; /* The ex= query parameter */ |
| 494 | Blob cacheKey; /* The key to cache */ |
| 495 | Glob *pInclude = 0; /* The compiled in= glob pattern */ |
| 496 | Glob *pExclude = 0; /* The compiled ex= glob pattern */ |
| 497 | |
| 498 | login_check_credentials(); |
| 499 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 500 | load_control(); |
| 501 | zName = mprintf("%s", PD("name","")); |
| 502 | nName = strlen(zName); |
| 503 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 504 | nRid = strlen(zRid); |
| 505 | zInclude = P("in"); |
| 506 | if( zInclude ) pInclude = glob_create(zInclude); |
| 507 | zExclude = P("ex"); |
| 508 | if( zExclude ) pExclude = glob_create(zExclude); |
| 509 | if( nName>4 && fossil_strcmp(&zName[nName-4], ".zip")==0 ){ |
| 510 | /* Special case: Remove the ".zip" suffix. */ |
| 511 | nName -= 4; |
| 512 | zName[nName] = 0; |
| 513 | }else{ |
| @@ -485,17 +535,27 @@ | |
| 535 | @ </form> |
| 536 | style_footer(); |
| 537 | return; |
| 538 | } |
| 539 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 540 | |
| 541 | /* Compute a unique key for the cache entry based on query parameters */ |
| 542 | blob_init(&cacheKey, 0, 0); |
| 543 | blob_appendf(&cacheKey, "/zip/%z", rid_to_uuid(rid)); |
| 544 | if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude); |
| 545 | if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude); |
| 546 | blob_appendf(&cacheKey, "/%q", zName); |
| 547 | zKey = blob_str(&cacheKey); |
| 548 | |
| 549 | blob_zero(&zip); |
| 550 | if( cache_read(&zip, zKey)==0 ){ |
| 551 | zip_of_checkin(rid, &zip, zName, pInclude, pExclude); |
| 552 | cache_write(&zip, zKey); |
| 553 | } |
| 554 | fossil_free( zName ); |
| 555 | fossil_free( zRid ); |
| 556 | blob_reset(&cacheKey); |
| 557 | glob_free(pInclude); |
| 558 | glob_free(pExclude); |
| 559 | cgi_set_content(&zip); |
| 560 | cgi_set_content_type("application/zip"); |
| 561 | } |
| 562 |