Fossil

Check-in [ef449a11]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: ef449a1173c556b2a25d084de6408bd53b5da67c
User & Date: drh 2016-06-10 12:46:44
Context
2016-06-10
13:06
For Markdown hyperlinks, if the URL begins with "/" then prepend the root of repository. This allows repository-relative hyperlinks. check-in: 38a47074 user: drh tags: trunk
12:46
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. check-in: ef449a11 user: drh tags: trunk
12:03
Slightly more efficient than previous commit check-in: 1ca59832 user: jan.nijtmans tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/tar.c.

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
444
445
446
447
448
449
450
451

452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467






468
469
470
471
472
473
474
...
486
487
488
489
490
491
492


493

494
495
496
497
498
499
500
...
502
503
504
505
506
507
508



509
510
511
512
513
514
515
516
...
537
538
539
540
541
542
543





544


545
546
547
548
549
550
551




552




553
554
555
556
557
558
559
...
571
572
573
574
575
576
577
578


579
580
581
582
583
584
585
586
587
588
589
590
591
592
593

594
595
596
597
598
599







600
601
602
603
604





605
606
607
608
609
610
611
612
613





614
615
616
617
618
619
620
...
627
628
629
630
631
632
633
634

635







636
637
638
639






640
641
642
643
644
645
646
...
650
651
652
653
654
655
656
657
658
659
660
661
662


663
664
665
  db_multi_exec(
    "CREATE TEMP TABLE dir(name UNIQUE);"
  );
}


/*
** verify that lla characters in 'zName' are in the
** ISO646 (=ASCII) character set.
*/
static int is_iso646_name(
  const char *zName,     /* file path */
  int nName              /* path length */
){
  int i;
................................................................................
    if( c>0x7e ) return 0;
  }
  return 1;
}


/*
**   copy string pSrc into pDst, truncating or padding with 0 if necessary
*/
static void padded_copy(
  char *pDest,
  int nDest,
  const char *pSrc,
  int nSrc
){
................................................................................
  }
  tar_finish(&zip);
  blob_write_to_file(&zip, g.argv[2]);
}

/*
** Given the RID for a check-in, construct a tarball containing
** all files in that check-in

**
** If RID is for an object that is not a real manifest, then the
** resulting tarball contains a single file which is the RID
** object.
**
** If the RID object does not exist in the repository, then
** pTar is zeroed.
**
** zDir is a "synthetic" subdirectory which all files get
** added to as part of the tarball. It may be 0 or an empty string, in
** which case it is ignored. The intention is to create a tarball which
** politely expands into a subdir instead of filling your current dir
** with source files. For example, pass a UUID or "ProjectName".
**
*/
void tarball_of_checkin(int rid, Blob *pTar, const char *zDir){






  Blob mfile, hash, file;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;
  char *zName;
  unsigned int mTime;
................................................................................
  }
  nPrefix = blob_size(&filename);

  pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pManifest ){
    mTime = (pManifest->rDate - 2440587.5)*86400.0;
    tar_begin(mTime);


    if( db_get_boolean("manifest", 0) ){

      blob_append(&filename, "manifest", -1);
      zName = blob_str(&filename);
      sha1sum_blob(&mfile, &hash);
      sterilize_manifest(&mfile);
      tar_add_file(zName, &mfile, 0, mTime);
      blob_reset(&mfile);
      blob_append(&hash, "\n", 1);
................................................................................
      blob_append(&filename, "manifest.uuid", -1);
      zName = blob_str(&filename);
      tar_add_file(zName, &hash, 0, mTime);
      blob_reset(&hash);
    }
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest,0))!=0 ){



      int fid = uuid_to_rid(pFile->zUuid, 0);
      if( fid ){
        content_get(fid, &file);
        blob_resize(&filename, nPrefix);
        blob_append(&filename, pFile->zName, -1);
        zName = blob_str(&filename);
        tar_add_file(zName, &file, manifest_file_mperm(pFile), mTime);
        blob_reset(&file);
................................................................................
**
** Generate a compressed tarball for a specified version.  If the --name
** option is used, its argument becomes the name of the top-level directory
** in the resulting tarball.  If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.
**





** Options:


**   --name DIRECTORYNAME   The name of the top-level directory in the archive
**   -R REPOSITORY          Specify a Fossil repository
*/
void tarball_cmd(void){
  int rid;
  Blob tarball;
  const char *zName;




  zName = find_option("name", 0, 1);




  db_find_and_open_repository(0, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=4 ){
    usage("VERSION OUTPUTFILE");
................................................................................
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  tarball_of_checkin(rid, &tarball, zName);


  blob_write_to_file(&tarball, g.argv[3]);
  blob_reset(&tarball);
}

/*
** WEBPAGE: tarball
** URL: /tarball/RID.tar.gz
**
** Generate a compressed tarball for a check-in.
** Return that tarball as the HTTP reply content.
**
** Optional URL Parameters:
**
** - name=NAME[.tar.gz] is base name of the output file. Defaults to
** something project/version-specific. The prefix of the name, up to

** the last '.', are used as the top-most directory name in the tar
** output.
**
** - uuid=the version to tar (may be a tag/branch name).
** Defaults to "trunk".
**







*/
void tarball_page(void){
  int rid;
  char *zName, *zRid, *zKey;
  int nName, nRid;





  Blob tarball;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  load_control();
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);





  if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){
    /* Special case:  Remove the ".tar.gz" suffix.  */
    nName -= 7;
    zName[nName] = 0;
  }else{
    /* If the file suffix is not ".tar.gz" then just remove the
    ** suffix up to and including the last "." */
................................................................................
  }
  rid = name_to_typed_rid(nRid?zRid:zName, "ci");
  if( rid==0 ){
    @ Not found
    return;
  }
  if( nRid==0 && nName>10 ) zName[10] = 0;
  zKey = db_text(0, "SELECT '/tarball/'||uuid||'/%q'"

                    "  FROM blob WHERE rid=%d",zName,rid);







  if( P("debug")!=0 ){
    style_header("Tarball Generator Debug Screen");
    @ zName = "%h(zName)"<br>
    @ rid = %d(rid)<br>






    @ zKey = "%h(zKey)"
    style_footer();
    return;
  }
  if( referred_from_login() ){
    style_header("Tarball Download");
    @ <form action='%R/tarball'>
................................................................................
    @ <input type="submit" value="Download" />
    @ </form>
    style_footer();
    return;
  }
  blob_zero(&tarball);
  if( cache_read(&tarball, zKey)==0 ){
    tarball_of_checkin(rid, &tarball, zName);
    cache_write(&tarball, zKey);
  }
  free( zName );
  free( zRid );
  free( zKey );


  cgi_set_content(&tarball);
  cgi_set_content_type("application/x-compressed");
}







|







 







|







 







|
>



|











|
>
>
>
>
>
>







 







>
>
|
>







 







>
>
>
|







 







>
>
>
>
>

>
>
|
|





>
>
>
>

>
>
>
>







 







|
>
>






|

|
|

|

|
|
>
|
<

|
|

>
>
>
>
>
>
>





>
>
>
>
>
|








>
>
>
>
>







 







<
>
|
>
>
>
>
>
>
>




>
>
>
>
>
>







 







|


|
|
|
>
>



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
...
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
...
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
...
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
...
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625

626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
...
674
675
676
677
678
679
680

681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
...
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
  db_multi_exec(
    "CREATE TEMP TABLE dir(name UNIQUE);"
  );
}


/*
** Verify that all characters in 'zName' are in the
** ISO646 (=ASCII) character set.
*/
static int is_iso646_name(
  const char *zName,     /* file path */
  int nName              /* path length */
){
  int i;
................................................................................
    if( c>0x7e ) return 0;
  }
  return 1;
}


/*
**  copy string pSrc into pDst, truncating or padding with 0 if necessary
*/
static void padded_copy(
  char *pDest,
  int nDest,
  const char *pSrc,
  int nSrc
){
................................................................................
  }
  tar_finish(&zip);
  blob_write_to_file(&zip, g.argv[2]);
}

/*
** Given the RID for a check-in, construct a tarball containing
** all files in that check-in that match pGlob (or all files if
** pGlob is NULL).
**
** If RID is for an object that is not a real manifest, then the
** resulting tarball contains a single file which is the RID
** object.  pInclude and pExclude are ignored in this case.
**
** If the RID object does not exist in the repository, then
** pTar is zeroed.
**
** zDir is a "synthetic" subdirectory which all files get
** added to as part of the tarball. It may be 0 or an empty string, in
** which case it is ignored. The intention is to create a tarball which
** politely expands into a subdir instead of filling your current dir
** with source files. For example, pass a UUID or "ProjectName".
**
*/
void tarball_of_checkin(
  int rid,             /* The RID of the checkin from which to form a tarball */
  Blob *pTar,          /* Write the tarball into this blob */
  const char *zDir,    /* Directory prefix for all file added to tarball */
  Glob *pInclude,      /* Only add files matching this pattern */
  Glob *pExclude       /* Exclude files matching this pattern */
){
  Blob mfile, hash, file;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;
  char *zName;
  unsigned int mTime;
................................................................................
  }
  nPrefix = blob_size(&filename);

  pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pManifest ){
    mTime = (pManifest->rDate - 2440587.5)*86400.0;
    tar_begin(mTime);
    if( (pInclude==0 || glob_match(pInclude, "manifest"))
     && !glob_match(pExclude, "manifest")
     && db_get_boolean("manifest", 0)
    ){
      blob_append(&filename, "manifest", -1);
      zName = blob_str(&filename);
      sha1sum_blob(&mfile, &hash);
      sterilize_manifest(&mfile);
      tar_add_file(zName, &mfile, 0, mTime);
      blob_reset(&mfile);
      blob_append(&hash, "\n", 1);
................................................................................
      blob_append(&filename, "manifest.uuid", -1);
      zName = blob_str(&filename);
      tar_add_file(zName, &hash, 0, mTime);
      blob_reset(&hash);
    }
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest,0))!=0 ){
      int fid;
      if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue;
      if( glob_match(pExclude, pFile->zName) ) continue;
      fid = uuid_to_rid(pFile->zUuid, 0);
      if( fid ){
        content_get(fid, &file);
        blob_resize(&filename, nPrefix);
        blob_append(&filename, pFile->zName, -1);
        zName = blob_str(&filename);
        tar_add_file(zName, &file, manifest_file_mperm(pFile), mTime);
        blob_reset(&file);
................................................................................
**
** Generate a compressed tarball for a specified version.  If the --name
** option is used, its argument becomes the name of the top-level directory
** in the resulting tarball.  If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.
**
** The GLOBLIST argument to --exclude and --include can be a comma-separated
** list of glob patterns, where each glob pattern may optionally be enclosed
** in "..." or '...' so that it may contain commas.  If a file matches both
** --include and --exclude then it is excluded.
**
** Options:
**   -X|--exclude GLOBLIST   Comma-separated list of GLOBs of files to exclude
**   --include GLOBLIST      Comma-separated list of GLOBs of files to include
**   --name DIRECTORYNAME    The name of the top-level directory in the archive
**   -R REPOSITORY           Specify a Fossil repository
*/
void tarball_cmd(void){
  int rid;
  Blob tarball;
  const char *zName;
  Glob *pInclude = 0;
  Glob *pExclude = 0;
  const char *zInclude;
  const char *zExclude;
  zName = find_option("name", 0, 1);
  zExclude = find_option("exclude", "X", 1);
  if( zExclude ) pExclude = glob_create(zExclude);
  zInclude = find_option("include", 0, 1);
  if( zInclude ) pInclude = glob_create(zInclude);
  db_find_and_open_repository(0, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=4 ){
    usage("VERSION OUTPUTFILE");
................................................................................
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  tarball_of_checkin(rid, &tarball, zName, pInclude, pExclude);
  glob_free(pInclude);
  glob_free(pExclude);
  blob_write_to_file(&tarball, g.argv[3]);
  blob_reset(&tarball);
}

/*
** WEBPAGE: tarball
** URL: /tarball
**
** Generate a compressed tarball for a check-in and return that
** tarball as the HTTP reply content.
**
** Query parameters:
**
**   name=NAME[.tar.gz]  The base name of the output file.  The default
**                       value is a configuration parameter in the project
**                       settings.  A prefix of the name, omitting the extension,
**                       is used as the top-most directory name.

**
**   uuid=TAG            The check-in that is turned into a tarball.
**                       Defaults to "trunk".
**
**   in=PATTERN          Only include files that match the comma-separate
**                       list of GLOB patterns in PATTERN, as with ex=
**
**   ex=PATTERN          Omit any file that match PATTERN.  PATTERN is a
**                       comma-separated list of GLOB patterns, where each
**                       pattern can optionally be quoted using ".." or '..'.
**                       Any file matching both ex= and in= is excluded.
*/
void tarball_page(void){
  int rid;
  char *zName, *zRid, *zKey;
  int nName, nRid;
  const char *zInclude;         /* The in= query parameter */
  const char *zExclude;         /* The ex= query parameter */
  Blob cacheKey;                /* The key to cache */
  Glob *pInclude = 0;           /* The compiled in= glob pattern */
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob tarball;                 /* Tarball accumulated here */

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  load_control();
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  zInclude = P("in");
  if( zInclude ) pInclude = glob_create(zInclude);
  zExclude = P("ex");
  if( zExclude ) pExclude = glob_create(zExclude);

  if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){
    /* Special case:  Remove the ".tar.gz" suffix.  */
    nName -= 7;
    zName[nName] = 0;
  }else{
    /* If the file suffix is not ".tar.gz" then just remove the
    ** suffix up to and including the last "." */
................................................................................
  }
  rid = name_to_typed_rid(nRid?zRid:zName, "ci");
  if( rid==0 ){
    @ Not found
    return;
  }
  if( nRid==0 && nName>10 ) zName[10] = 0;


  /* Compute a unique key for the cache entry based on query parameters */
  blob_init(&cacheKey, 0, 0);
  blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid));
  if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude);
  if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude);
  blob_appendf(&cacheKey, "/%q", zName);
  zKey = blob_str(&cacheKey);
    
  if( P("debug")!=0 ){
    style_header("Tarball Generator Debug Screen");
    @ zName = "%h(zName)"<br>
    @ rid = %d(rid)<br>
    if( zInclude ){
      @ zInclude = "%h(zInclude)"<br>
    }
    if( zExclude ){
      @ zExclude = "%h(zExclude)"<br>
    }
    @ zKey = "%h(zKey)"
    style_footer();
    return;
  }
  if( referred_from_login() ){
    style_header("Tarball Download");
    @ <form action='%R/tarball'>
................................................................................
    @ <input type="submit" value="Download" />
    @ </form>
    style_footer();
    return;
  }
  blob_zero(&tarball);
  if( cache_read(&tarball, zKey)==0 ){
    tarball_of_checkin(rid, &tarball, zName, pInclude, pExclude);
    cache_write(&tarball, zKey);
  }
  glob_free(pInclude);
  glob_free(pExclude);
  fossil_free(zName);
  fossil_free(zRid);
  blob_reset(&cacheKey);
  cgi_set_content(&tarball);
  cgi_set_content_type("application/x-compressed");
}

Changes to src/zip.c.

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326






327
328
329
330
331
332
333
...
344
345
346
347
348
349
350


351

352
353
354
355
356
357
358
...
361
362
363
364
365
366
367



368
369
370
371
372
373
374
375
...
383
384
385
386
387
388
389
390
391
392
393
394
395
396











397
398
399
400
401




402




403
404
405
406
407
408
409
...
416
417
418
419
420
421
422
423
424


425
426
427
428
429
430
431
432
433
434
435
436
437

438
439

440
441

442
443







444
445
446
447
448
449
450





451
452
453
454
455
456
457
458




459
460
461
462
463
464
465
...
483
484
485
486
487
488
489
490









491
492
493
494
495
496
497
498


499
500
501

/*
** Given the RID for a manifest, construct a ZIP archive containing
** all files in the corresponding baseline.
**
** If RID is for an object that is not a real manifest, then the
** resulting ZIP archive contains a single file which is the RID
** object.
**
** If the RID object does not exist in the repository, then
** pZip is zeroed.
**
** zDir is a "synthetic" subdirectory which all zipped files get
** added to as part of the zip file. It may be 0 or an empty string,
** in which case it is ignored. The intention is to create a zip which
** politely expands into a subdir instead of filling your current dir
** with source files. For example, pass a UUID or "ProjectName".
**
*/
void zip_of_baseline(int rid, Blob *pZip, const char *zDir){






  Blob mfile, hash, file;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;

  content_get(rid, &mfile);
................................................................................
  }
  nPrefix = blob_size(&filename);

  pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pManifest ){
    char *zName;
    zip_set_timedate(pManifest->rDate);


    if( db_get_boolean("manifest", 0) ){

      blob_append(&filename, "manifest", -1);
      zName = blob_str(&filename);
      zip_add_folders(zName);
      sha1sum_blob(&mfile, &hash);
      sterilize_manifest(&mfile);
      zip_add_file(zName, &mfile, 0);
      blob_reset(&mfile);
................................................................................
      blob_append(&filename, "manifest.uuid", -1);
      zName = blob_str(&filename);
      zip_add_file(zName, &hash, 0);
      blob_reset(&hash);
    }
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest,0))!=0 ){



      int fid = uuid_to_rid(pFile->zUuid, 0);
      if( fid ){
        content_get(fid, &file);
        blob_resize(&filename, nPrefix);
        blob_append(&filename, pFile->zName, -1);
        zName = blob_str(&filename);
        zip_add_folders(zName);
        zip_add_file(zName, &file, manifest_file_mperm(pFile));
................................................................................
  blob_reset(&filename);
  zip_close(pZip);
}

/*
** COMMAND: zip*
**
** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO]
**
** Generate a ZIP archive for a specified version.  If the --name option is
** used, its argument becomes the name of the top-level directory in the
** resulting ZIP archive.  If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.











*/
void baseline_zip_cmd(void){
  int rid;
  Blob zip;
  const char *zName;




  zName = find_option("name", 0, 1);




  db_find_and_open_repository(0, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=4 ){
    usage("VERSION OUTPUTFILE");
................................................................................
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  zip_of_baseline(rid, &zip, zName);
  blob_write_to_file(&zip, g.argv[3]);


}

/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**
** Generate a ZIP archive for the baseline.
** Return that ZIP archive as the HTTP reply content.
**
** Optional URL Parameters:
**
** - name=NAME[.zip] is the name of the output file. Defaults to
** something project/version-specific. The base part of the

** name, up to the last dot, is used as the top-most directory
** name in the output file.

**
** - uuid=the version to zip (may be a tag/branch name).

** Defaults to "trunk".
**







*/
void baseline_zip_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob zip;
  char *zKey;






  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  load_control();
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);




  if( nName>4 && fossil_strcmp(&zName[nName-4], ".zip")==0 ){
    /* Special case:  Remove the ".zip" suffix.  */
    nName -= 4;
    zName[nName] = 0;
  }else{
    /* If the file suffix is not ".zip" then just remove the
    ** suffix up to and including the last "." */
................................................................................
    @ of check-in <b>%h(zRid)</b>:
    @ <input type="submit" value="Download" />
    @ </form>
    style_footer();
    return;
  }
  if( nRid==0 && nName>10 ) zName[10] = 0;
  zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid);









  blob_zero(&zip);
  if( cache_read(&zip, zKey)==0 ){
    zip_of_baseline(rid, &zip, zName);
    cache_write(&zip, zKey);
  }
  fossil_free( zName );
  fossil_free( zRid );
  fossil_free( zKey );


  cgi_set_content(&zip);
  cgi_set_content_type("application/zip");
}







|











|
>
>
>
>
>
>







 







>
>
|
>







 







>
>
>
|







 







|

|




>
>
>
>
>
>
>
>
>
>
>

|



>
>
>
>

>
>
>
>







 







|

>
>




|

|


|

|
|
>
|
<
>

<
>
|

>
>
>
>
>
>
>







>
>
>
>
>








>
>
>
>







 







<
>
>
>
>
>
>
>
>
>


|




|
>
>



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
...
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
...
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
...
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
...
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472

473
474

475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
...
533
534
535
536
537
538
539

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561

/*
** Given the RID for a manifest, construct a ZIP archive containing
** all files in the corresponding baseline.
**
** If RID is for an object that is not a real manifest, then the
** resulting ZIP archive contains a single file which is the RID
** object.  The pInclude and pExclude parameters are ignored in this case.
**
** If the RID object does not exist in the repository, then
** pZip is zeroed.
**
** zDir is a "synthetic" subdirectory which all zipped files get
** added to as part of the zip file. It may be 0 or an empty string,
** in which case it is ignored. The intention is to create a zip which
** politely expands into a subdir instead of filling your current dir
** with source files. For example, pass a UUID or "ProjectName".
**
*/
void zip_of_checkin(
  int rid,            /* The RID of the checkin to construct the ZIP archive from */
  Blob *pZip,         /* Write the ZIP archive content into this blob */
  const char *zDir,   /* Top-level directory of the ZIP archive */
  Glob *pInclude,     /* Only include files that match this pattern */
  Glob *pExclude      /* Exclude files that match this pattern */ 
){
  Blob mfile, hash, file;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;

  content_get(rid, &mfile);
................................................................................
  }
  nPrefix = blob_size(&filename);

  pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pManifest ){
    char *zName;
    zip_set_timedate(pManifest->rDate);
    if( (pInclude==0 || glob_match(pInclude, "manifest"))
     && !glob_match(pExclude, "manifest")
     && db_get_boolean("manifest", 0)
    ){
      blob_append(&filename, "manifest", -1);
      zName = blob_str(&filename);
      zip_add_folders(zName);
      sha1sum_blob(&mfile, &hash);
      sterilize_manifest(&mfile);
      zip_add_file(zName, &mfile, 0);
      blob_reset(&mfile);
................................................................................
      blob_append(&filename, "manifest.uuid", -1);
      zName = blob_str(&filename);
      zip_add_file(zName, &hash, 0);
      blob_reset(&hash);
    }
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest,0))!=0 ){
      int fid;
      if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue;
      if( glob_match(pExclude, pFile->zName) ) continue;
      fid = uuid_to_rid(pFile->zUuid, 0);
      if( fid ){
        content_get(fid, &file);
        blob_resize(&filename, nPrefix);
        blob_append(&filename, pFile->zName, -1);
        zName = blob_str(&filename);
        zip_add_folders(zName);
        zip_add_file(zName, &file, manifest_file_mperm(pFile));
................................................................................
  blob_reset(&filename);
  zip_close(pZip);
}

/*
** COMMAND: zip*
**
** Usage: %fossil zip VERSION OUTPUTFILE [OPTIONS]
**
** Generate a ZIP archive for a check-in.  If the --name option is
** used, its argument becomes the name of the top-level directory in the
** resulting ZIP archive.  If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.
**
** The GLOBLIST argument to --exclude and --include can be a comma-separated
** list of glob patterns, where each glob pattern may optionally be enclosed
** in "..." or '...' so that it may contain commas.  If a file matches both
** --include and --exclude then it is excluded.
**
** Options:
**   -X|--exclude GLOBLIST   Comma-separated list of GLOBs of files to exclude
**   --include GLOBLIST      Comma-separated list of GLOBs of files to include
**   --name DIRECTORYNAME    The name of the top-level directory in the archive
**   -R REPOSITORY           Specify a Fossil repository
*/
void zip_cmd(void){
  int rid;
  Blob zip;
  const char *zName;
  Glob *pInclude = 0;
  Glob *pExclude = 0;
  const char *zInclude;
  const char *zExclude;
  zName = find_option("name", 0, 1);
  zExclude = find_option("exclude", "X", 1);
  if( zExclude ) pExclude = glob_create(zExclude);
  zInclude = find_option("include", 0, 1);
  if( zInclude ) pInclude = glob_create(zInclude);
  db_find_and_open_repository(0, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=4 ){
    usage("VERSION OUTPUTFILE");
................................................................................
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  zip_of_checkin(rid, &zip, zName, pInclude, pExclude);
  blob_write_to_file(&zip, g.argv[3]);
  glob_free(pInclude);
  glob_free(pExclude);
}

/*
** WEBPAGE: zip
** URL: /zip
**
** Generate a ZIP archive for a checkin specified by the uuid= query parameter.
** Return that ZIP archive as the HTTP reply content.
**
** Query parameters:
**
**   name=NAME[.tar.gz]  The base name of the output file.  The default
**                       value is a configuration parameter in the project
**                       settings.  A prefix of the name, omitting the extension,
**                       is used as the top-most directory name.


**

**   uuid=TAG            The check-in that is turned into a tarball.
**                       Defaults to "trunk".
**
**   in=PATTERN          Only include files that match the comma-separate
**                       list of GLOB patterns in PATTERN, as with ex=
**
**   ex=PATTERN          Omit any file that match PATTERN.  PATTERN is a
**                       comma-separated list of GLOB patterns, where each
**                       pattern can optionally be quoted using ".." or '..'.
**                       Any file matching both ex= and in= is excluded.
*/
void baseline_zip_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob zip;
  char *zKey;
  const char *zInclude;         /* The in= query parameter */
  const char *zExclude;         /* The ex= query parameter */
  Blob cacheKey;                /* The key to cache */
  Glob *pInclude = 0;           /* The compiled in= glob pattern */
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  load_control();
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  zInclude = P("in");
  if( zInclude ) pInclude = glob_create(zInclude);
  zExclude = P("ex");
  if( zExclude ) pExclude = glob_create(zExclude);
  if( nName>4 && fossil_strcmp(&zName[nName-4], ".zip")==0 ){
    /* Special case:  Remove the ".zip" suffix.  */
    nName -= 4;
    zName[nName] = 0;
  }else{
    /* If the file suffix is not ".zip" then just remove the
    ** suffix up to and including the last "." */
................................................................................
    @ of check-in <b>%h(zRid)</b>:
    @ <input type="submit" value="Download" />
    @ </form>
    style_footer();
    return;
  }
  if( nRid==0 && nName>10 ) zName[10] = 0;


  /* Compute a unique key for the cache entry based on query parameters */
  blob_init(&cacheKey, 0, 0);
  blob_appendf(&cacheKey, "/zip/%z", rid_to_uuid(rid));
  if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude);
  if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude);
  blob_appendf(&cacheKey, "/%q", zName);
  zKey = blob_str(&cacheKey);

  blob_zero(&zip);
  if( cache_read(&zip, zKey)==0 ){
    zip_of_checkin(rid, &zip, zName, pInclude, pExclude);
    cache_write(&zip, zKey);
  }
  fossil_free( zName );
  fossil_free( zRid );
  blob_reset(&cacheKey);
  glob_free(pInclude);
  glob_free(pExclude);
  cgi_set_content(&zip);
  cgi_set_content_type("application/zip");
}