| | @@ -19,10 +19,11 @@ |
| 19 | 19 | ** from the local repository. |
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "checkout.h" |
| 23 | 23 | #include <assert.h> |
| 24 | +#include <zlib.h> |
| 24 | 25 | |
| 25 | 26 | /* |
| 26 | 27 | ** Check to see if there is an existing check-out that has been |
| 27 | 28 | ** modified. Return values: |
| 28 | 29 | ** |
| | @@ -429,5 +430,183 @@ |
| 429 | 430 | } |
| 430 | 431 | unlink_local_database(1); |
| 431 | 432 | db_close(1); |
| 432 | 433 | unlink_local_database(0); |
| 433 | 434 | } |
| 435 | + |
| 436 | + |
| 437 | +/* |
| 438 | +** COMMAND: get |
| 439 | +** |
| 440 | +** Usage: %fossil get URL ?VERSION? ?OPTIONS? |
| 441 | +** |
| 442 | +** Download a single check-in from a remote repository named URL and |
| 443 | +** unpack all of the files locally. The check-in is identified by VERSION. |
| 444 | +** |
| 445 | +** URL can be a traditional URL like one of: |
| 446 | +** |
| 447 | +** * https://domain.com/project |
| 448 | +** * ssh://my-server/project.fossil |
| 449 | +** * file:/home/user/Fossils/project.fossil |
| 450 | +** |
| 451 | +** Or URL can be just the name of a local repository without the "file:" |
| 452 | +** prefix. |
| 453 | +** |
| 454 | +** This command works by downloading an SQL archive of the requested |
| 455 | +** check-in and then extracting all the files from the archive. |
| 456 | +** |
| 457 | +** Options: |
| 458 | +** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ." |
| 459 | +** to extract into the local directory. |
| 460 | +** |
| 461 | +** -f|--force Overwrite existing files |
| 462 | +** |
| 463 | +** --sqlar ARCHIVE Store the check-out in an SQL-archive rather |
| 464 | +** than unpacking them into separate files. |
| 465 | +** |
| 466 | +** -v|--verbose Show all files as they are extracted |
| 467 | +*/ |
| 468 | +void get_cmd(void){ |
| 469 | + int forceFlag = find_option("force","f",0)!=0; |
| 470 | + int bVerbose = find_option("verbose","v",0)!=0; |
| 471 | + int bDebug = find_option("debug",0,0)!=0; |
| 472 | + const char *zSqlArchive = find_option("sqlar",0,1); |
| 473 | + const char *z; |
| 474 | + char *zDest = 0; /* Where to store results */ |
| 475 | + const char *zUrl; /* Url to get */ |
| 476 | + const char *zVers; /* Version name to get */ |
| 477 | + unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
| 478 | + Blob in, out; /* I/O for the HTTP request */ |
| 479 | + Blob file; /* A file to extract */ |
| 480 | + sqlite3 *db; /* Database containing downloaded sqlar */ |
| 481 | + sqlite3_stmt *pStmt; /* Statement for querying the database */ |
| 482 | + int rc; /* Result of subroutine calls */ |
| 483 | + |
| 484 | + z = find_option("dest",0,1); |
| 485 | + if( z ) zDest = fossil_strdup(z); |
| 486 | + verify_all_options(); |
| 487 | + if( g.argc<3 || g.argc>4 ){ |
| 488 | + usage("get URL ?VERSION? ?OPTIONS?"); |
| 489 | + } |
| 490 | + zUrl = g.argv[2]; |
| 491 | + zVers = g.argc==4 ? g.argv[3] : "trunk"; |
| 492 | + |
| 493 | + /* Parse the URL of the repository */ |
| 494 | + url_parse(zUrl, 0); |
| 495 | + |
| 496 | + /* Construct an appropriate name for the destination directory */ |
| 497 | + if( zDest==0 ){ |
| 498 | + int i; |
| 499 | + const char *zTail; |
| 500 | + const char *zDot; |
| 501 | + int n; |
| 502 | + if( g.url.isFile ){ |
| 503 | + zTail = file_tail(g.url.name); |
| 504 | + }else{ |
| 505 | + zTail = file_tail(g.url.path); |
| 506 | + } |
| 507 | + zDot = strchr(zTail,'.'); |
| 508 | + if( zDot==0 ) zDot = zTail+strlen(zTail); |
| 509 | + n = (int)(zDot - zTail); |
| 510 | + zDest = mprintf("%.*s-%s", n, zTail, zVers); |
| 511 | + for(i=0; zDest[i]; i++){ |
| 512 | + char c = zDest[i]; |
| 513 | + if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){ |
| 514 | + zDest[i] = '-'; |
| 515 | + } |
| 516 | + } |
| 517 | + } |
| 518 | + if( bDebug ){ |
| 519 | + fossil_print("dest = %s\n", zDest); |
| 520 | + } |
| 521 | + |
| 522 | + /* Error checking */ |
| 523 | + if( !forceFlag ){ |
| 524 | + if( zSqlArchive ){ |
| 525 | + if( file_isdir(zSqlArchive, ExtFILE)>0 ){ |
| 526 | + fossil_fatal("file already exists: \"%s\"", zSqlArchive); |
| 527 | + } |
| 528 | + }else if( file_isdir(zDest, ExtFILE)>0 ){ |
| 529 | + if( fossil_strcmp(zDest,".")==0 ){ |
| 530 | + if( file_directory_size(zDest,0,1) ){ |
| 531 | + fossil_fatal("current directory is not empty"); |
| 532 | + } |
| 533 | + }else{ |
| 534 | + fossil_fatal("\"%s\" already exists", zDest); |
| 535 | + } |
| 536 | + } |
| 537 | + } |
| 538 | + |
| 539 | + /* Construct a subpath on the URL if necessary */ |
| 540 | + if( g.url.isSsh || g.url.isFile ){ |
| 541 | + g.url.subpath = mprintf("/sqlar?name=%t&r=%t", zDest, zVers); |
| 542 | + } |
| 543 | + |
| 544 | + if( bDebug ){ |
| 545 | + urlparse_print(0); |
| 546 | + } |
| 547 | + |
| 548 | + /* Fetch the ZIP archive for the requested check-in */ |
| 549 | + blob_init(&in, 0, 0); |
| 550 | + blob_init(&out, 0, 0); |
| 551 | + if( bDebug ) mHttpFlags |= HTTP_VERBOSE; |
| 552 | + http_exchange(&in, &out, mHttpFlags, 4, 0); |
| 553 | + |
| 554 | + if( zSqlArchive ){ |
| 555 | + blob_write_to_file(&out, zSqlArchive); |
| 556 | + if( bVerbose ) fossil_print("%s\n", zSqlArchive); |
| 557 | + return; |
| 558 | + } |
| 559 | + |
| 560 | + rc = sqlite3_open(":memory:", &db); |
| 561 | + if( rc==SQLITE_OK ){ |
| 562 | + int sz = blob_size(&out); |
| 563 | + rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz, |
| 564 | + SQLITE_DESERIALIZE_READONLY); |
| 565 | + } |
| 566 | + if( rc!=SQLITE_OK ){ |
| 567 | + fossil_fatal("Cannot create an in-memory database: %s", |
| 568 | + sqlite3_errmsg(db)); |
| 569 | + } |
| 570 | + rc = sqlite3_prepare_v2(db, |
| 571 | + "SELECT name, mode, sz, data" |
| 572 | + " FROM sqlar", -1, &pStmt, 0); |
| 573 | + if( rc!=0 ){ |
| 574 | + fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db)); |
| 575 | + } |
| 576 | + blob_init(&file, 0, 0); |
| 577 | + while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 578 | + const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0); |
| 579 | + int mode = sqlite3_column_int(pStmt, 1); |
| 580 | + int sz = sqlite3_column_int(pStmt, 2); |
| 581 | + if( mode & 0x4000 ){ |
| 582 | + /* A directory name */ |
| 583 | + file_mkdir(zFilename, ExtFILE, 1); |
| 584 | + }else{ |
| 585 | + /* A file */ |
| 586 | + unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3); |
| 587 | + unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3); |
| 588 | + unsigned long int nOut2 = (unsigned long int)sz; |
| 589 | + blob_resize(&file, sz); |
| 590 | + if( nIn<sz ){ |
| 591 | + rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2, |
| 592 | + inBuf, nIn); |
| 593 | + if( rc!=Z_OK ){ |
| 594 | + fossil_fatal("Failed to uncompress file %s", zFilename); |
| 595 | + } |
| 596 | + }else{ |
| 597 | + memcpy(blob_buffer(&file), inBuf, sz); |
| 598 | + } |
| 599 | + blob_write_to_file(&file, zFilename); |
| 600 | + if( mode & 0x40 ){ |
| 601 | + file_setexe(zFilename, 1); |
| 602 | + } |
| 603 | + blob_zero(&file); |
| 604 | + if( bVerbose ){ |
| 605 | + fossil_print("%s\n", zFilename); |
| 606 | + } |
| 607 | + } |
| 608 | + } |
| 609 | + sqlite3_finalize(pStmt); |
| 610 | + sqlite3_close(db); |
| 611 | + blob_zero(&out); |
| 612 | +} |
| 434 | 613 | |