Fossil SCM
Initial work on /fileedit page. First dry-run save was just performed.
Commit
fbd31f2049740cd4506526835c8590af5f15a07f127645c4e881a894f371f380
Parent
b5e3bc9e41127bb…
2 files changed
+212
-99
+21
-1
+212
-99
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2706,11 +2706,11 @@ | ||
| 2706 | 2706 | ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). |
| 2707 | 2707 | */ |
| 2708 | 2708 | struct CheckinMiniInfo { |
| 2709 | 2709 | Manifest * pParent; /* parent checkin. Memory is owned by this |
| 2710 | 2710 | object. */ |
| 2711 | - char *zParentUuid; /* UUID of pParent */ | |
| 2711 | + char *zParentUuid; /* Full UUID of pParent */ | |
| 2712 | 2712 | char *zFilename; /* Name of single file to commit. Must be |
| 2713 | 2713 | relative to the top of the repo. */ |
| 2714 | 2714 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2715 | 2715 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2716 | 2716 | preferred hash method. */ |
| @@ -3054,21 +3054,25 @@ | ||
| 3054 | 3054 | ** save a manifest for that change. Ownership of pCI and its contents |
| 3055 | 3055 | ** are unchanged. |
| 3056 | 3056 | ** |
| 3057 | 3057 | ** This function may may modify pCI as follows: |
| 3058 | 3058 | ** |
| 3059 | -** - If Manifest pCI->pParent is NULL then it will be loaded | |
| 3060 | -** using pCI->zParentUuid. pCI->zParentUuid may not be NULL. | |
| 3059 | +** - If one of Manifest pCI->pParent or pCI->zParentUuid are NULL, | |
| 3060 | +** then the other will be assigned based on its counterpart. Both | |
| 3061 | +** may not be NULL. | |
| 3061 | 3062 | ** |
| 3062 | 3063 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 3063 | 3064 | ** string. If its original value cannot be validated then |
| 3064 | 3065 | ** this function fails. If pCI->zDate is NULL, the current time |
| 3065 | 3066 | ** is used. |
| 3066 | 3067 | ** |
| 3067 | -** - If pCI->fileContent is not binary and its line-ending style | |
| 3068 | -** differs from its previous version, it is converted to the same | |
| 3069 | -** EOL style. If this is done, the pCI->fileHash is re-computed. | |
| 3068 | +** - If the CIMINI_CONVERT_EOL flag is set, pCI->fileContent appears | |
| 3069 | +** to be plain text, and its line-ending style differs from its | |
| 3070 | +** previous version, it is converted to the same EOL style as the | |
| 3071 | +** previous version. If this is done, the pCI->fileHash is | |
| 3072 | +** re-computed. Note that only pCI->fileContent, not the original | |
| 3073 | +** file, is affected by the conversion. | |
| 3070 | 3074 | ** |
| 3071 | 3075 | ** - If pCI->fileHash is empty, this routine populates it with the |
| 3072 | 3076 | ** repository's preferred hash algorithm. |
| 3073 | 3077 | ** |
| 3074 | 3078 | ** pCI's ownership is not modified. |
| @@ -3108,15 +3112,21 @@ | ||
| 3108 | 3112 | } |
| 3109 | 3113 | fossil_free(zProjCode); |
| 3110 | 3114 | } |
| 3111 | 3115 | db_begin_transaction(); |
| 3112 | 3116 | |
| 3113 | - if(pCI->pParent==0){ | |
| 3117 | + if(pCI->pParent==0 && pCI->zParentUuid){ | |
| 3118 | + ci_err((pErr, "Cannot determine parent version.")); | |
| 3119 | + } | |
| 3120 | + else if(pCI->pParent==0){ | |
| 3114 | 3121 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| 3115 | 3122 | if(pCI->pParent==0){ |
| 3116 | 3123 | ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); |
| 3117 | 3124 | } |
| 3125 | + }else if(pCI->zParentUuid==0){ | |
| 3126 | + pCI->zParentUuid = rid_to_uuid(pCI->pParent->rid); | |
| 3127 | + assert(pCI->zParentUuid); | |
| 3118 | 3128 | } |
| 3119 | 3129 | |
| 3120 | 3130 | assert(pCI->pParent->rid>0); |
| 3121 | 3131 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 3122 | 3132 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| @@ -3361,11 +3371,11 @@ | ||
| 3361 | 3371 | ** |
| 3362 | 3372 | ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| 3363 | 3373 | ** |
| 3364 | 3374 | */ |
| 3365 | 3375 | void test_ci_mini_cmd(){ |
| 3366 | - CheckinMiniInfo cinf; /* checkin state */ | |
| 3376 | + CheckinMiniInfo cimi; /* checkin state */ | |
| 3367 | 3377 | int newRid = 0; /* RID of new version */ |
| 3368 | 3378 | const char * zFilename; /* argv[2] */ |
| 3369 | 3379 | const char * zComment; /* -m comment */ |
| 3370 | 3380 | const char * zCommentFile; /* -M FILE */ |
| 3371 | 3381 | const char * zAsFilename; /* --as filename */ |
| @@ -3378,45 +3388,45 @@ | ||
| 3378 | 3388 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3379 | 3389 | ** then pass it on to checkin_mini() to do most of the validation |
| 3380 | 3390 | ** and work. The point of this is to avoid duplicate code when a web |
| 3381 | 3391 | ** front-end is added for checkin_mini(). |
| 3382 | 3392 | */ |
| 3383 | - CheckinMiniInfo_init(&cinf); | |
| 3393 | + CheckinMiniInfo_init(&cimi); | |
| 3384 | 3394 | zComment = find_option("comment","m",1); |
| 3385 | 3395 | zCommentFile = find_option("comment-file","M",1); |
| 3386 | 3396 | zAsFilename = find_option("as",0,1); |
| 3387 | 3397 | zRevision = find_option("revision","r",1); |
| 3388 | 3398 | zUser = find_option("user-override",0,1); |
| 3389 | 3399 | zDate = find_option("date-override",0,1); |
| 3390 | 3400 | zManifestFile = find_option("save-manifest",0,1); |
| 3391 | 3401 | if(find_option("wet-run",0,0)==0){ |
| 3392 | - cinf.flags |= CIMINI_DRY_RUN; | |
| 3402 | + cimi.flags |= CIMINI_DRY_RUN; | |
| 3393 | 3403 | } |
| 3394 | 3404 | if(find_option("allow-fork",0,0)!=0){ |
| 3395 | - cinf.flags |= CIMINI_ALLOW_FORK; | |
| 3405 | + cimi.flags |= CIMINI_ALLOW_FORK; | |
| 3396 | 3406 | } |
| 3397 | 3407 | if(find_option("dump-manifest","d",0)!=0){ |
| 3398 | - cinf.flags |= CIMINI_DUMP_MANIFEST; | |
| 3408 | + cimi.flags |= CIMINI_DUMP_MANIFEST; | |
| 3399 | 3409 | } |
| 3400 | 3410 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3401 | - cinf.flags |= CIMINI_ALLOW_MERGE_MARKER; | |
| 3411 | + cimi.flags |= CIMINI_ALLOW_MERGE_MARKER; | |
| 3402 | 3412 | } |
| 3403 | 3413 | if(find_option("allow-older",0,0)!=0){ |
| 3404 | - cinf.flags |= CIMINI_ALLOW_OLDER; | |
| 3414 | + cimi.flags |= CIMINI_ALLOW_OLDER; | |
| 3405 | 3415 | } |
| 3406 | 3416 | if(find_option("convert-eol",0,0)!=0){ |
| 3407 | - cinf.flags |= CIMINI_CONVERT_EOL; | |
| 3417 | + cimi.flags |= CIMINI_CONVERT_EOL; | |
| 3408 | 3418 | } |
| 3409 | 3419 | if(find_option("delta",0,0)!=0){ |
| 3410 | - cinf.flags |= CIMINI_PREFER_DELTA; | |
| 3420 | + cimi.flags |= CIMINI_PREFER_DELTA; | |
| 3411 | 3421 | } |
| 3412 | 3422 | if(find_option("delta2",0,0)!=0){ |
| 3413 | 3423 | /* Undocumented. For testing only. */ |
| 3414 | - cinf.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA; | |
| 3424 | + cimi.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA; | |
| 3415 | 3425 | } |
| 3416 | 3426 | if(find_option("allow-new-file",0,0)!=0){ |
| 3417 | - cinf.flags |= CIMINI_ALLOW_NEW_FILE; | |
| 3427 | + cimi.flags |= CIMINI_ALLOW_NEW_FILE; | |
| 3418 | 3428 | } |
| 3419 | 3429 | db_find_and_open_repository(0, 0); |
| 3420 | 3430 | verify_all_options(); |
| 3421 | 3431 | user_select(); |
| 3422 | 3432 | if(g.argc!=3){ |
| @@ -3424,46 +3434,46 @@ | ||
| 3424 | 3434 | } |
| 3425 | 3435 | if(zComment && zCommentFile){ |
| 3426 | 3436 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3427 | 3437 | }else{ |
| 3428 | 3438 | if(zCommentFile && *zCommentFile){ |
| 3429 | - blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); | |
| 3439 | + blob_read_from_file(&cimi.comment, zCommentFile, ExtFILE); | |
| 3430 | 3440 | }else if(zComment && *zComment){ |
| 3431 | - blob_append(&cinf.comment, zComment, -1); | |
| 3441 | + blob_append(&cimi.comment, zComment, -1); | |
| 3432 | 3442 | } |
| 3433 | - if(!blob_size(&cinf.comment)){ | |
| 3443 | + if(!blob_size(&cimi.comment)){ | |
| 3434 | 3444 | fossil_fatal("Non-empty checkin comment is required."); |
| 3435 | 3445 | } |
| 3436 | 3446 | } |
| 3437 | 3447 | db_begin_transaction(); |
| 3438 | 3448 | zFilename = g.argv[2]; |
| 3439 | - cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); | |
| 3440 | - cinf.filePerm = file_perm(zFilename, ExtFILE); | |
| 3441 | - cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); | |
| 3449 | + cimi.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); | |
| 3450 | + cimi.filePerm = file_perm(zFilename, ExtFILE); | |
| 3451 | + cimi.zUser = mprintf("%s", zUser ? zUser : login_name()); | |
| 3442 | 3452 | if(zDate){ |
| 3443 | - cinf.zDate = mprintf("%s", zDate); | |
| 3453 | + cimi.zDate = mprintf("%s", zDate); | |
| 3444 | 3454 | } |
| 3445 | 3455 | if(zRevision==0 || zRevision[0]==0){ |
| 3446 | 3456 | if(g.localOpen/*checkout*/){ |
| 3447 | 3457 | zRevision = db_lget("checkout-hash", 0)/*leak*/; |
| 3448 | 3458 | }else{ |
| 3449 | 3459 | zRevision = "trunk"; |
| 3450 | 3460 | } |
| 3451 | 3461 | } |
| 3452 | - name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); | |
| 3453 | - if(cinf.zParentUuid==0){ | |
| 3462 | + name_to_uuid2(zRevision, "ci", &cimi.zParentUuid); | |
| 3463 | + if(cimi.zParentUuid==0){ | |
| 3454 | 3464 | fossil_fatal("Cannot determine version to commit to."); |
| 3455 | 3465 | } |
| 3456 | - blob_read_from_file(&cinf.fileContent, zFilename, ExtFILE); | |
| 3466 | + blob_read_from_file(&cimi.fileContent, zFilename, ExtFILE); | |
| 3457 | 3467 | { |
| 3458 | 3468 | Blob theManifest = empty_blob; /* --save-manifest target */ |
| 3459 | 3469 | Blob errMsg = empty_blob; |
| 3460 | 3470 | int rc; |
| 3461 | 3471 | if(zManifestFile){ |
| 3462 | - cinf.pMfOut = &theManifest; | |
| 3472 | + cimi.pMfOut = &theManifest; | |
| 3463 | 3473 | } |
| 3464 | - rc = checkin_mini(&cinf, &newRid, &errMsg); | |
| 3474 | + rc = checkin_mini(&cimi, &newRid, &errMsg); | |
| 3465 | 3475 | if(rc){ |
| 3466 | 3476 | assert(blob_size(&errMsg)==0); |
| 3467 | 3477 | }else{ |
| 3468 | 3478 | assert(blob_size(&errMsg)); |
| 3469 | 3479 | fossil_fatal("%b", &errMsg); |
| @@ -3475,23 +3485,23 @@ | ||
| 3475 | 3485 | blob_reset(&theManifest); |
| 3476 | 3486 | } |
| 3477 | 3487 | } |
| 3478 | 3488 | if(newRid!=0){ |
| 3479 | 3489 | fossil_print("New version%s: %z\n", |
| 3480 | - (cinf.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", | |
| 3490 | + (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", | |
| 3481 | 3491 | rid_to_uuid(newRid)); |
| 3482 | 3492 | } |
| 3483 | 3493 | db_end_transaction(0/*checkin_mini() will have triggered it to roll |
| 3484 | 3494 | ** back in dry-run mode, but we need access to |
| 3485 | 3495 | ** the transaction-written db state in this |
| 3486 | 3496 | ** routine.*/); |
| 3487 | - if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ | |
| 3497 | + if(!(cimi.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ | |
| 3488 | 3498 | fossil_warning("The checkout state is now out of sync " |
| 3489 | 3499 | "with regards to this commit. It needs to be " |
| 3490 | 3500 | "'update'd or 'close'd and re-'open'ed."); |
| 3491 | 3501 | } |
| 3492 | - CheckinMiniInfo_cleanup(&cinf); | |
| 3502 | + CheckinMiniInfo_cleanup(&cimi); | |
| 3493 | 3503 | } |
| 3494 | 3504 | |
| 3495 | 3505 | |
| 3496 | 3506 | /* |
| 3497 | 3507 | ** Returns true if the given filename qualified for online editing |
| @@ -3522,105 +3532,208 @@ | ||
| 3522 | 3532 | ** Parameters intended to be passed in only via the editor's own form: |
| 3523 | 3533 | ** |
| 3524 | 3534 | ** diff If true, show diff from prev version. |
| 3525 | 3535 | ** preview If true, preview (how depends on mimetype). |
| 3526 | 3536 | ** content File content. |
| 3537 | +** comment Checkin comment. | |
| 3538 | +** n Optional comment mimetype ("n" as in N-card). | |
| 3527 | 3539 | ** |
| 3528 | 3540 | ** |
| 3529 | 3541 | */ |
| 3530 | 3542 | void fileedit_page(){ |
| 3531 | 3543 | const char * zFilename = PD("file",P("name")); /* filename */ |
| 3532 | - const char * zRev = P("r"); /* checkin version */ | |
| 3533 | - const char * zContent = P("content"); /* file content */ | |
| 3534 | - const char * zComment = P("comment"); /* checkin comment */ | |
| 3535 | - char * zRevResolved = 0; /* Resolved zRev */ | |
| 3536 | - int vid, frid; /* checkin/file rids */ | |
| 3537 | - char * zFileUuid = 0; | |
| 3538 | - Blob content = empty_blob; | |
| 3544 | + const char * zRev = P("r"); /* checkin version */ | |
| 3545 | + const char * zContent = P("content"); /* file content */ | |
| 3546 | + const char * zComment = P("comment"); /* checkin comment */ | |
| 3547 | + CheckinMiniInfo cimi; /* Checkin state */ | |
| 3548 | + int submitMode = 0; /* See mapping below */ | |
| 3549 | + char * zRevResolved = 0; /* Resolved zRev */ | |
| 3550 | + int vid; /* checkin rid */ | |
| 3551 | + char * zFileUuid = 0; /* File content UUID */ | |
| 3552 | + Blob err = empty_blob; /* Error report */ | |
| 3553 | + const char * zFlagCheck = 0; /* Temp url flag holder */ | |
| 3554 | + Stmt stmt = empty_Stmt; | |
| 3555 | +#define fail(EXPR) blob_appendf EXPR; goto end_footer | |
| 3539 | 3556 | |
| 3540 | 3557 | login_check_credentials(); |
| 3541 | 3558 | if( !g.perm.Write ){ |
| 3542 | 3559 | login_needed(g.anon.Write); |
| 3543 | 3560 | return; |
| 3544 | 3561 | } |
| 3562 | + CheckinMiniInfo_init(&cimi); | |
| 3563 | + zFlagCheck = P("comment"); | |
| 3564 | + if(zFlagCheck){ | |
| 3565 | + cimi.zMimetype = mprintf("%s",zFlagCheck); | |
| 3566 | + zFlagCheck = 0; | |
| 3567 | + } | |
| 3568 | + cimi.zUser = mprintf("%s",g.zLogin); | |
| 3545 | 3569 | /* |
| 3546 | 3570 | ** TODOs include, but are not limited to: |
| 3547 | 3571 | ** |
| 3548 | - ** - On initial hit, fetch file content and stuff it in a textarea. | |
| 3549 | - ** | |
| 3550 | 3572 | ** - Preview button + view |
| 3551 | 3573 | ** |
| 3552 | 3574 | ** - Diff button + view |
| 3553 | 3575 | ** |
| 3554 | - ** - Checkbox options: allow fork, dry-run, convert EOL, | |
| 3555 | - ** allow merge conflict, allow older (just in case server time | |
| 3556 | - ** is messed up or someone checked something in w/ a future | |
| 3557 | - ** timestamp) | |
| 3558 | - ** | |
| 3559 | 3576 | */ |
| 3560 | 3577 | if(!zRev || !*zRev || !zFilename || !*zFilename){ |
| 3561 | - webpage_error("Missing required URL parameters."); | |
| 3578 | + fail((&err,"Missing required URL parameters.")); | |
| 3562 | 3579 | } |
| 3563 | 3580 | vid = symbolic_name_to_rid(zRev, "ci"); |
| 3564 | 3581 | if(0==vid){ |
| 3565 | - webpage_error("Could not resolve checkin version."); | |
| 3566 | - } | |
| 3567 | - zRevResolved = rid_to_uuid(vid); | |
| 3568 | - zFileUuid = db_text(0,"SELECT uuid FROM files_of_checkin WHERE " | |
| 3569 | - "filename=%Q %s AND checkinID=%d", | |
| 3570 | - zFilename, filename_collation(), vid); | |
| 3571 | - if(!zFileUuid){ | |
| 3572 | - webpage_error("Checkin [%S] does not contain file: %T", | |
| 3573 | - zRevResolved, zFilename); | |
| 3574 | - } | |
| 3575 | - | |
| 3576 | - frid = fast_uuid_to_rid(zFileUuid); | |
| 3577 | - assert(frid); | |
| 3578 | - if(zContent==0){ | |
| 3579 | - content_get(frid, &content); | |
| 3580 | - zContent = blob_size(&content) ? blob_str(&content) : NULL; | |
| 3581 | - }else{ | |
| 3582 | - blob_init(&content,zContent,-1); | |
| 3583 | - } | |
| 3584 | - if(looks_like_binary(&content)){ | |
| 3585 | - webpage_error("File appears to be binary. Cannot edit: %T", | |
| 3586 | - zFilename); | |
| 3587 | - } | |
| 3588 | - | |
| 3582 | + fail((&err,"Could not resolve checkin version.")); | |
| 3583 | + } | |
| 3584 | + | |
| 3585 | + /* Find the repo-side file entry or fail... */ | |
| 3586 | + zRevResolved = rid_to_uuid(vid); | |
| 3587 | + db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " | |
| 3588 | + "WHERE filename=%Q %s AND checkinID=%d", | |
| 3589 | + zFilename, filename_collation(), vid); | |
| 3590 | + if(SQLITE_ROW==db_step(&stmt)){ | |
| 3591 | + const char * zPerm = db_column_text(&stmt, 1); | |
| 3592 | + const int isLink = zPerm ? strstr(zPerm,"l")!=0 : 0; | |
| 3593 | + if(isLink){ | |
| 3594 | + fail((&err,"Editing symlinks is not permitted.")); | |
| 3595 | + } | |
| 3596 | + zFileUuid = mprintf("%s",db_column_text(&stmt, 0)); | |
| 3597 | + } | |
| 3598 | + db_finalize(&stmt); | |
| 3599 | + if(!zFileUuid){ | |
| 3600 | + fail((&err,"Checkin [%S] does not contain file: %h", | |
| 3601 | + zRevResolved, zFilename)); | |
| 3602 | + } | |
| 3603 | + | |
| 3604 | + /* Read file content from submit request or repo... */ | |
| 3605 | + if(zContent==0){ | |
| 3606 | + const int frid = fast_uuid_to_rid(zFileUuid); | |
| 3607 | + assert(frid); | |
| 3608 | + content_get(frid, &cimi.fileContent); | |
| 3609 | + zContent = blob_size(&cimi.fileContent) | |
| 3610 | + ? blob_str(&cimi.fileContent) : NULL; | |
| 3611 | + }else{ | |
| 3612 | + blob_init(&cimi.fileContent,zContent,-1); | |
| 3613 | + } | |
| 3614 | + if(looks_like_binary(&cimi.fileContent)){ | |
| 3615 | + fail((&err,"File appears to be binary. Cannot edit: %h", | |
| 3616 | + zFilename)); | |
| 3617 | + } | |
| 3618 | + | |
| 3619 | + /* All set. Here we go... */ | |
| 3589 | 3620 | style_header("File Editor"); |
| 3590 | 3621 | #define fp fossil_print |
| 3591 | 3622 | /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */ |
| 3592 | 3623 | |
| 3593 | - fp("<h1>Editing: %T</h1>",zFilename); | |
| 3594 | - fp("<p>This page is <em>far from complete</em>.</p>"); | |
| 3595 | - | |
| 3596 | - fp("<form action=\"%R/fileedit\" method=\"POST\" class=\"fileedit-form\">"); | |
| 3597 | - fp("<input type=\"hidden\" name=\"r\" value=\"%s\">", zRevResolved); | |
| 3598 | - fp("<input type=\"hidden\" name=\"file\" value=\"%T\">", | |
| 3599 | - zFilename); | |
| 3600 | - fp("<h3>Comment</h3>"); | |
| 3601 | - fp("<textarea name=\"comment\" rows=\"3\" cols=\"80\">"); | |
| 3624 | + fp("<h1>Editing: %h</h1>",zFilename); | |
| 3625 | + fp("<p class='hint'>Permalink: " | |
| 3626 | + "<a href='%R/fileedit?file=%T&r=%s'>" | |
| 3627 | + "%R/fileedit?file=%T&r=%s</a></p>", | |
| 3628 | + zFilename, zRev, zFilename, zRev); | |
| 3629 | + fp("<p>This page is <em>far from complete</em>.</p>\n"); | |
| 3630 | + | |
| 3631 | + fp("<form action='%R/fileedit' method='POST' " | |
| 3632 | + "class='fileedit-form'>\n"); | |
| 3633 | + | |
| 3634 | + /******* Hidden fields *******/ | |
| 3635 | + fp("<input type='hidden' name='r' value='%s'>", zRevResolved); | |
| 3636 | + fp("<input type='hidden' name='file' value='%T'>", | |
| 3637 | + zFilename); | |
| 3638 | + | |
| 3639 | + /******* Comment *******/ | |
| 3640 | + fp("<h3>Comment</h3>\n"); | |
| 3641 | + fp("<textarea name='comment' rows='3' cols='80'>"); | |
| 3602 | 3642 | if(zComment && *zComment){ |
| 3603 | - fp("%T"/*%T?*/, zComment); | |
| 3604 | - } | |
| 3605 | - fp("</textarea>"); | |
| 3606 | - fp("<h3>Content</h3>"); | |
| 3607 | - fp("<textarea name=\"content\" rows=\"20\" cols=\"80\">"); | |
| 3608 | - if(zContent && *zContent){ | |
| 3609 | - fp("%s", zContent); | |
| 3610 | - } | |
| 3611 | - fp("</textarea>"); | |
| 3612 | - | |
| 3613 | - fp("<div class=\"fileedit-options\">"); | |
| 3614 | - /* Put checkboxes here... */ | |
| 3615 | - fp("Many buttons and checkboxes to put here"); | |
| 3616 | - fp("</div>"); | |
| 3617 | - | |
| 3618 | - fp("</form>"); | |
| 3619 | - | |
| 3620 | - blob_reset(&content); | |
| 3643 | + fp("%h"/*%h? %s?*/, zComment); | |
| 3644 | + } | |
| 3645 | + fp("</textarea>\n"); | |
| 3646 | + fp("<div class='hint'>Comments use the Fossil wiki markup " | |
| 3647 | + "syntax.</div>"/*TODO: radiobuttons for fossil/me/plain text*/); | |
| 3648 | + | |
| 3649 | + /******* Content *******/ | |
| 3650 | + fp("<h3>Content</h3>\n"); | |
| 3651 | + fp("<textarea name='content' rows='20' cols='80'>"); | |
| 3652 | + if(zContent && *zContent){ | |
| 3653 | + fp("%h", zContent); | |
| 3654 | + } | |
| 3655 | + fp("</textarea>\n"); | |
| 3656 | + | |
| 3657 | + /******* Flags/options *******/ | |
| 3658 | + fp("<fieldset class='fileedit-options'>" | |
| 3659 | + "<legend>Many checkboxes are TODO</legend><div>" | |
| 3660 | + /* Chrome does not sanely lay out multiple | |
| 3661 | + ** fieldset children after the <legend>, so | |
| 3662 | + ** a containing div is necessary. */); | |
| 3663 | + /* | |
| 3664 | + ** TODO: Put checkboxes here... | |
| 3665 | + ** | |
| 3666 | + ** allow-fork, dry-run, convert-eol, allow-merge-conflict, | |
| 3667 | + ** set-exec-bit, date-override, allow-older (in case server time is | |
| 3668 | + ** messed up or someone checked something in w/ a future timestamp), | |
| 3669 | + ** prefer-delta, strongly-prefer-delta (undocumented - for | |
| 3670 | + ** development/admin use only). | |
| 3671 | + */ | |
| 3672 | + fp("</div></fieldset>"); | |
| 3673 | + | |
| 3674 | + /******* Buttons *******/ | |
| 3675 | + fp("<fieldset class='fileedit-options'>" | |
| 3676 | + "<legend>Several buttons are TODO</legend><div>"); | |
| 3677 | + fp("<button type='submit' name='submit' value='1'>" | |
| 3678 | + "Submit (dry-run)</button>"); | |
| 3679 | + fp("<button type='submit' name='submit' value='2'>" | |
| 3680 | + "Preview (TODO)</button>"); | |
| 3681 | + fp("<button type='submit' name='submit' value='3'>" | |
| 3682 | + "Diff (TODO)</button>"); | |
| 3683 | + fp("</div></fieldset>"); | |
| 3684 | + | |
| 3685 | + /******* End of form *******/ | |
| 3686 | + fp("</form>\n"); | |
| 3687 | + zContent = 0; | |
| 3621 | 3688 | fossil_free(zRevResolved); |
| 3622 | 3689 | fossil_free(zFileUuid); |
| 3623 | 3690 | |
| 3691 | + submitMode = atoi(PD("submit","0")) | |
| 3692 | + /* | |
| 3693 | + ** Submit modes: 1=submit (save), 2=preview, 3=diff | |
| 3694 | + */; | |
| 3695 | + if(1==submitMode/*save*/){ | |
| 3696 | + Blob manifest = empty_blob; | |
| 3697 | + int rc; | |
| 3698 | + | |
| 3699 | + /* TODO: pull these flags from P() */ | |
| 3700 | + cimi.flags |= CIMINI_DRY_RUN; | |
| 3701 | + cimi.flags |= CIMINI_CONVERT_EOL; | |
| 3702 | + cimi.flags |= CIMINI_PREFER_DELTA; | |
| 3703 | + /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/ | |
| 3704 | + | |
| 3705 | + db_begin_transaction(); | |
| 3706 | + cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0); | |
| 3707 | + assert(cimi.pParent && "We know vid is valid."); | |
| 3708 | + cimi.zFilename = mprintf("%s",zFilename); | |
| 3709 | + cimi.pMfOut = &manifest; | |
| 3710 | + cimi.filePerm = PERM_REG; | |
| 3711 | + if(zComment && *zComment){ | |
| 3712 | + blob_append(&cimi.comment, zComment, -1); | |
| 3713 | + } | |
| 3714 | + rc = checkin_mini(&cimi, 0, &err); | |
| 3715 | + if(rc!=0){ | |
| 3716 | + fp("<h3>Manifest</h3><pre><code>%h</code></pre>", | |
| 3717 | + blob_str(&manifest)); | |
| 3718 | + } | |
| 3719 | + blob_reset(&manifest); | |
| 3720 | + db_end_transaction(rc ? 0 : 1); | |
| 3721 | + }else if(2==submitMode/*preview*/){ | |
| 3722 | + /* TODO */ | |
| 3723 | + }else if(3==submitMode/*diff*/){ | |
| 3724 | + /* TODO */ | |
| 3725 | + }else{ | |
| 3726 | + /* Ignore invalid submitMode value */ | |
| 3727 | + goto end_footer; | |
| 3728 | + } | |
| 3729 | + | |
| 3730 | +end_footer: | |
| 3731 | + if(blob_size(&err)){ | |
| 3732 | + fp("<div class='fileedit-error-report'>%h</div>", | |
| 3733 | + blob_str(&err)); | |
| 3734 | + } | |
| 3735 | + blob_reset(&err); | |
| 3736 | + CheckinMiniInfo_cleanup(&cimi); | |
| 3624 | 3737 | style_footer(); |
| 3625 | 3738 | #undef fp |
| 3626 | 3739 | } |
| 3627 | 3740 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2706,11 +2706,11 @@ | |
| 2706 | ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). |
| 2707 | */ |
| 2708 | struct CheckinMiniInfo { |
| 2709 | Manifest * pParent; /* parent checkin. Memory is owned by this |
| 2710 | object. */ |
| 2711 | char *zParentUuid; /* UUID of pParent */ |
| 2712 | char *zFilename; /* Name of single file to commit. Must be |
| 2713 | relative to the top of the repo. */ |
| 2714 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2715 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2716 | preferred hash method. */ |
| @@ -3054,21 +3054,25 @@ | |
| 3054 | ** save a manifest for that change. Ownership of pCI and its contents |
| 3055 | ** are unchanged. |
| 3056 | ** |
| 3057 | ** This function may may modify pCI as follows: |
| 3058 | ** |
| 3059 | ** - If Manifest pCI->pParent is NULL then it will be loaded |
| 3060 | ** using pCI->zParentUuid. pCI->zParentUuid may not be NULL. |
| 3061 | ** |
| 3062 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 3063 | ** string. If its original value cannot be validated then |
| 3064 | ** this function fails. If pCI->zDate is NULL, the current time |
| 3065 | ** is used. |
| 3066 | ** |
| 3067 | ** - If pCI->fileContent is not binary and its line-ending style |
| 3068 | ** differs from its previous version, it is converted to the same |
| 3069 | ** EOL style. If this is done, the pCI->fileHash is re-computed. |
| 3070 | ** |
| 3071 | ** - If pCI->fileHash is empty, this routine populates it with the |
| 3072 | ** repository's preferred hash algorithm. |
| 3073 | ** |
| 3074 | ** pCI's ownership is not modified. |
| @@ -3108,15 +3112,21 @@ | |
| 3108 | } |
| 3109 | fossil_free(zProjCode); |
| 3110 | } |
| 3111 | db_begin_transaction(); |
| 3112 | |
| 3113 | if(pCI->pParent==0){ |
| 3114 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| 3115 | if(pCI->pParent==0){ |
| 3116 | ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); |
| 3117 | } |
| 3118 | } |
| 3119 | |
| 3120 | assert(pCI->pParent->rid>0); |
| 3121 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 3122 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| @@ -3361,11 +3371,11 @@ | |
| 3361 | ** |
| 3362 | ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| 3363 | ** |
| 3364 | */ |
| 3365 | void test_ci_mini_cmd(){ |
| 3366 | CheckinMiniInfo cinf; /* checkin state */ |
| 3367 | int newRid = 0; /* RID of new version */ |
| 3368 | const char * zFilename; /* argv[2] */ |
| 3369 | const char * zComment; /* -m comment */ |
| 3370 | const char * zCommentFile; /* -M FILE */ |
| 3371 | const char * zAsFilename; /* --as filename */ |
| @@ -3378,45 +3388,45 @@ | |
| 3378 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3379 | ** then pass it on to checkin_mini() to do most of the validation |
| 3380 | ** and work. The point of this is to avoid duplicate code when a web |
| 3381 | ** front-end is added for checkin_mini(). |
| 3382 | */ |
| 3383 | CheckinMiniInfo_init(&cinf); |
| 3384 | zComment = find_option("comment","m",1); |
| 3385 | zCommentFile = find_option("comment-file","M",1); |
| 3386 | zAsFilename = find_option("as",0,1); |
| 3387 | zRevision = find_option("revision","r",1); |
| 3388 | zUser = find_option("user-override",0,1); |
| 3389 | zDate = find_option("date-override",0,1); |
| 3390 | zManifestFile = find_option("save-manifest",0,1); |
| 3391 | if(find_option("wet-run",0,0)==0){ |
| 3392 | cinf.flags |= CIMINI_DRY_RUN; |
| 3393 | } |
| 3394 | if(find_option("allow-fork",0,0)!=0){ |
| 3395 | cinf.flags |= CIMINI_ALLOW_FORK; |
| 3396 | } |
| 3397 | if(find_option("dump-manifest","d",0)!=0){ |
| 3398 | cinf.flags |= CIMINI_DUMP_MANIFEST; |
| 3399 | } |
| 3400 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3401 | cinf.flags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3402 | } |
| 3403 | if(find_option("allow-older",0,0)!=0){ |
| 3404 | cinf.flags |= CIMINI_ALLOW_OLDER; |
| 3405 | } |
| 3406 | if(find_option("convert-eol",0,0)!=0){ |
| 3407 | cinf.flags |= CIMINI_CONVERT_EOL; |
| 3408 | } |
| 3409 | if(find_option("delta",0,0)!=0){ |
| 3410 | cinf.flags |= CIMINI_PREFER_DELTA; |
| 3411 | } |
| 3412 | if(find_option("delta2",0,0)!=0){ |
| 3413 | /* Undocumented. For testing only. */ |
| 3414 | cinf.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA; |
| 3415 | } |
| 3416 | if(find_option("allow-new-file",0,0)!=0){ |
| 3417 | cinf.flags |= CIMINI_ALLOW_NEW_FILE; |
| 3418 | } |
| 3419 | db_find_and_open_repository(0, 0); |
| 3420 | verify_all_options(); |
| 3421 | user_select(); |
| 3422 | if(g.argc!=3){ |
| @@ -3424,46 +3434,46 @@ | |
| 3424 | } |
| 3425 | if(zComment && zCommentFile){ |
| 3426 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3427 | }else{ |
| 3428 | if(zCommentFile && *zCommentFile){ |
| 3429 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| 3430 | }else if(zComment && *zComment){ |
| 3431 | blob_append(&cinf.comment, zComment, -1); |
| 3432 | } |
| 3433 | if(!blob_size(&cinf.comment)){ |
| 3434 | fossil_fatal("Non-empty checkin comment is required."); |
| 3435 | } |
| 3436 | } |
| 3437 | db_begin_transaction(); |
| 3438 | zFilename = g.argv[2]; |
| 3439 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3440 | cinf.filePerm = file_perm(zFilename, ExtFILE); |
| 3441 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3442 | if(zDate){ |
| 3443 | cinf.zDate = mprintf("%s", zDate); |
| 3444 | } |
| 3445 | if(zRevision==0 || zRevision[0]==0){ |
| 3446 | if(g.localOpen/*checkout*/){ |
| 3447 | zRevision = db_lget("checkout-hash", 0)/*leak*/; |
| 3448 | }else{ |
| 3449 | zRevision = "trunk"; |
| 3450 | } |
| 3451 | } |
| 3452 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3453 | if(cinf.zParentUuid==0){ |
| 3454 | fossil_fatal("Cannot determine version to commit to."); |
| 3455 | } |
| 3456 | blob_read_from_file(&cinf.fileContent, zFilename, ExtFILE); |
| 3457 | { |
| 3458 | Blob theManifest = empty_blob; /* --save-manifest target */ |
| 3459 | Blob errMsg = empty_blob; |
| 3460 | int rc; |
| 3461 | if(zManifestFile){ |
| 3462 | cinf.pMfOut = &theManifest; |
| 3463 | } |
| 3464 | rc = checkin_mini(&cinf, &newRid, &errMsg); |
| 3465 | if(rc){ |
| 3466 | assert(blob_size(&errMsg)==0); |
| 3467 | }else{ |
| 3468 | assert(blob_size(&errMsg)); |
| 3469 | fossil_fatal("%b", &errMsg); |
| @@ -3475,23 +3485,23 @@ | |
| 3475 | blob_reset(&theManifest); |
| 3476 | } |
| 3477 | } |
| 3478 | if(newRid!=0){ |
| 3479 | fossil_print("New version%s: %z\n", |
| 3480 | (cinf.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", |
| 3481 | rid_to_uuid(newRid)); |
| 3482 | } |
| 3483 | db_end_transaction(0/*checkin_mini() will have triggered it to roll |
| 3484 | ** back in dry-run mode, but we need access to |
| 3485 | ** the transaction-written db state in this |
| 3486 | ** routine.*/); |
| 3487 | if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3488 | fossil_warning("The checkout state is now out of sync " |
| 3489 | "with regards to this commit. It needs to be " |
| 3490 | "'update'd or 'close'd and re-'open'ed."); |
| 3491 | } |
| 3492 | CheckinMiniInfo_cleanup(&cinf); |
| 3493 | } |
| 3494 | |
| 3495 | |
| 3496 | /* |
| 3497 | ** Returns true if the given filename qualified for online editing |
| @@ -3522,105 +3532,208 @@ | |
| 3522 | ** Parameters intended to be passed in only via the editor's own form: |
| 3523 | ** |
| 3524 | ** diff If true, show diff from prev version. |
| 3525 | ** preview If true, preview (how depends on mimetype). |
| 3526 | ** content File content. |
| 3527 | ** |
| 3528 | ** |
| 3529 | */ |
| 3530 | void fileedit_page(){ |
| 3531 | const char * zFilename = PD("file",P("name")); /* filename */ |
| 3532 | const char * zRev = P("r"); /* checkin version */ |
| 3533 | const char * zContent = P("content"); /* file content */ |
| 3534 | const char * zComment = P("comment"); /* checkin comment */ |
| 3535 | char * zRevResolved = 0; /* Resolved zRev */ |
| 3536 | int vid, frid; /* checkin/file rids */ |
| 3537 | char * zFileUuid = 0; |
| 3538 | Blob content = empty_blob; |
| 3539 | |
| 3540 | login_check_credentials(); |
| 3541 | if( !g.perm.Write ){ |
| 3542 | login_needed(g.anon.Write); |
| 3543 | return; |
| 3544 | } |
| 3545 | /* |
| 3546 | ** TODOs include, but are not limited to: |
| 3547 | ** |
| 3548 | ** - On initial hit, fetch file content and stuff it in a textarea. |
| 3549 | ** |
| 3550 | ** - Preview button + view |
| 3551 | ** |
| 3552 | ** - Diff button + view |
| 3553 | ** |
| 3554 | ** - Checkbox options: allow fork, dry-run, convert EOL, |
| 3555 | ** allow merge conflict, allow older (just in case server time |
| 3556 | ** is messed up or someone checked something in w/ a future |
| 3557 | ** timestamp) |
| 3558 | ** |
| 3559 | */ |
| 3560 | if(!zRev || !*zRev || !zFilename || !*zFilename){ |
| 3561 | webpage_error("Missing required URL parameters."); |
| 3562 | } |
| 3563 | vid = symbolic_name_to_rid(zRev, "ci"); |
| 3564 | if(0==vid){ |
| 3565 | webpage_error("Could not resolve checkin version."); |
| 3566 | } |
| 3567 | zRevResolved = rid_to_uuid(vid); |
| 3568 | zFileUuid = db_text(0,"SELECT uuid FROM files_of_checkin WHERE " |
| 3569 | "filename=%Q %s AND checkinID=%d", |
| 3570 | zFilename, filename_collation(), vid); |
| 3571 | if(!zFileUuid){ |
| 3572 | webpage_error("Checkin [%S] does not contain file: %T", |
| 3573 | zRevResolved, zFilename); |
| 3574 | } |
| 3575 | |
| 3576 | frid = fast_uuid_to_rid(zFileUuid); |
| 3577 | assert(frid); |
| 3578 | if(zContent==0){ |
| 3579 | content_get(frid, &content); |
| 3580 | zContent = blob_size(&content) ? blob_str(&content) : NULL; |
| 3581 | }else{ |
| 3582 | blob_init(&content,zContent,-1); |
| 3583 | } |
| 3584 | if(looks_like_binary(&content)){ |
| 3585 | webpage_error("File appears to be binary. Cannot edit: %T", |
| 3586 | zFilename); |
| 3587 | } |
| 3588 | |
| 3589 | style_header("File Editor"); |
| 3590 | #define fp fossil_print |
| 3591 | /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */ |
| 3592 | |
| 3593 | fp("<h1>Editing: %T</h1>",zFilename); |
| 3594 | fp("<p>This page is <em>far from complete</em>.</p>"); |
| 3595 | |
| 3596 | fp("<form action=\"%R/fileedit\" method=\"POST\" class=\"fileedit-form\">"); |
| 3597 | fp("<input type=\"hidden\" name=\"r\" value=\"%s\">", zRevResolved); |
| 3598 | fp("<input type=\"hidden\" name=\"file\" value=\"%T\">", |
| 3599 | zFilename); |
| 3600 | fp("<h3>Comment</h3>"); |
| 3601 | fp("<textarea name=\"comment\" rows=\"3\" cols=\"80\">"); |
| 3602 | if(zComment && *zComment){ |
| 3603 | fp("%T"/*%T?*/, zComment); |
| 3604 | } |
| 3605 | fp("</textarea>"); |
| 3606 | fp("<h3>Content</h3>"); |
| 3607 | fp("<textarea name=\"content\" rows=\"20\" cols=\"80\">"); |
| 3608 | if(zContent && *zContent){ |
| 3609 | fp("%s", zContent); |
| 3610 | } |
| 3611 | fp("</textarea>"); |
| 3612 | |
| 3613 | fp("<div class=\"fileedit-options\">"); |
| 3614 | /* Put checkboxes here... */ |
| 3615 | fp("Many buttons and checkboxes to put here"); |
| 3616 | fp("</div>"); |
| 3617 | |
| 3618 | fp("</form>"); |
| 3619 | |
| 3620 | blob_reset(&content); |
| 3621 | fossil_free(zRevResolved); |
| 3622 | fossil_free(zFileUuid); |
| 3623 | |
| 3624 | style_footer(); |
| 3625 | #undef fp |
| 3626 | } |
| 3627 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2706,11 +2706,11 @@ | |
| 2706 | ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). |
| 2707 | */ |
| 2708 | struct CheckinMiniInfo { |
| 2709 | Manifest * pParent; /* parent checkin. Memory is owned by this |
| 2710 | object. */ |
| 2711 | char *zParentUuid; /* Full UUID of pParent */ |
| 2712 | char *zFilename; /* Name of single file to commit. Must be |
| 2713 | relative to the top of the repo. */ |
| 2714 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2715 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2716 | preferred hash method. */ |
| @@ -3054,21 +3054,25 @@ | |
| 3054 | ** save a manifest for that change. Ownership of pCI and its contents |
| 3055 | ** are unchanged. |
| 3056 | ** |
| 3057 | ** This function may may modify pCI as follows: |
| 3058 | ** |
| 3059 | ** - If one of Manifest pCI->pParent or pCI->zParentUuid are NULL, |
| 3060 | ** then the other will be assigned based on its counterpart. Both |
| 3061 | ** may not be NULL. |
| 3062 | ** |
| 3063 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 3064 | ** string. If its original value cannot be validated then |
| 3065 | ** this function fails. If pCI->zDate is NULL, the current time |
| 3066 | ** is used. |
| 3067 | ** |
| 3068 | ** - If the CIMINI_CONVERT_EOL flag is set, pCI->fileContent appears |
| 3069 | ** to be plain text, and its line-ending style differs from its |
| 3070 | ** previous version, it is converted to the same EOL style as the |
| 3071 | ** previous version. If this is done, the pCI->fileHash is |
| 3072 | ** re-computed. Note that only pCI->fileContent, not the original |
| 3073 | ** file, is affected by the conversion. |
| 3074 | ** |
| 3075 | ** - If pCI->fileHash is empty, this routine populates it with the |
| 3076 | ** repository's preferred hash algorithm. |
| 3077 | ** |
| 3078 | ** pCI's ownership is not modified. |
| @@ -3108,15 +3112,21 @@ | |
| 3112 | } |
| 3113 | fossil_free(zProjCode); |
| 3114 | } |
| 3115 | db_begin_transaction(); |
| 3116 | |
| 3117 | if(pCI->pParent==0 && pCI->zParentUuid){ |
| 3118 | ci_err((pErr, "Cannot determine parent version.")); |
| 3119 | } |
| 3120 | else if(pCI->pParent==0){ |
| 3121 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| 3122 | if(pCI->pParent==0){ |
| 3123 | ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); |
| 3124 | } |
| 3125 | }else if(pCI->zParentUuid==0){ |
| 3126 | pCI->zParentUuid = rid_to_uuid(pCI->pParent->rid); |
| 3127 | assert(pCI->zParentUuid); |
| 3128 | } |
| 3129 | |
| 3130 | assert(pCI->pParent->rid>0); |
| 3131 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 3132 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| @@ -3361,11 +3371,11 @@ | |
| 3371 | ** |
| 3372 | ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| 3373 | ** |
| 3374 | */ |
| 3375 | void test_ci_mini_cmd(){ |
| 3376 | CheckinMiniInfo cimi; /* checkin state */ |
| 3377 | int newRid = 0; /* RID of new version */ |
| 3378 | const char * zFilename; /* argv[2] */ |
| 3379 | const char * zComment; /* -m comment */ |
| 3380 | const char * zCommentFile; /* -M FILE */ |
| 3381 | const char * zAsFilename; /* --as filename */ |
| @@ -3378,45 +3388,45 @@ | |
| 3388 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3389 | ** then pass it on to checkin_mini() to do most of the validation |
| 3390 | ** and work. The point of this is to avoid duplicate code when a web |
| 3391 | ** front-end is added for checkin_mini(). |
| 3392 | */ |
| 3393 | CheckinMiniInfo_init(&cimi); |
| 3394 | zComment = find_option("comment","m",1); |
| 3395 | zCommentFile = find_option("comment-file","M",1); |
| 3396 | zAsFilename = find_option("as",0,1); |
| 3397 | zRevision = find_option("revision","r",1); |
| 3398 | zUser = find_option("user-override",0,1); |
| 3399 | zDate = find_option("date-override",0,1); |
| 3400 | zManifestFile = find_option("save-manifest",0,1); |
| 3401 | if(find_option("wet-run",0,0)==0){ |
| 3402 | cimi.flags |= CIMINI_DRY_RUN; |
| 3403 | } |
| 3404 | if(find_option("allow-fork",0,0)!=0){ |
| 3405 | cimi.flags |= CIMINI_ALLOW_FORK; |
| 3406 | } |
| 3407 | if(find_option("dump-manifest","d",0)!=0){ |
| 3408 | cimi.flags |= CIMINI_DUMP_MANIFEST; |
| 3409 | } |
| 3410 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3411 | cimi.flags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3412 | } |
| 3413 | if(find_option("allow-older",0,0)!=0){ |
| 3414 | cimi.flags |= CIMINI_ALLOW_OLDER; |
| 3415 | } |
| 3416 | if(find_option("convert-eol",0,0)!=0){ |
| 3417 | cimi.flags |= CIMINI_CONVERT_EOL; |
| 3418 | } |
| 3419 | if(find_option("delta",0,0)!=0){ |
| 3420 | cimi.flags |= CIMINI_PREFER_DELTA; |
| 3421 | } |
| 3422 | if(find_option("delta2",0,0)!=0){ |
| 3423 | /* Undocumented. For testing only. */ |
| 3424 | cimi.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA; |
| 3425 | } |
| 3426 | if(find_option("allow-new-file",0,0)!=0){ |
| 3427 | cimi.flags |= CIMINI_ALLOW_NEW_FILE; |
| 3428 | } |
| 3429 | db_find_and_open_repository(0, 0); |
| 3430 | verify_all_options(); |
| 3431 | user_select(); |
| 3432 | if(g.argc!=3){ |
| @@ -3424,46 +3434,46 @@ | |
| 3434 | } |
| 3435 | if(zComment && zCommentFile){ |
| 3436 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3437 | }else{ |
| 3438 | if(zCommentFile && *zCommentFile){ |
| 3439 | blob_read_from_file(&cimi.comment, zCommentFile, ExtFILE); |
| 3440 | }else if(zComment && *zComment){ |
| 3441 | blob_append(&cimi.comment, zComment, -1); |
| 3442 | } |
| 3443 | if(!blob_size(&cimi.comment)){ |
| 3444 | fossil_fatal("Non-empty checkin comment is required."); |
| 3445 | } |
| 3446 | } |
| 3447 | db_begin_transaction(); |
| 3448 | zFilename = g.argv[2]; |
| 3449 | cimi.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3450 | cimi.filePerm = file_perm(zFilename, ExtFILE); |
| 3451 | cimi.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3452 | if(zDate){ |
| 3453 | cimi.zDate = mprintf("%s", zDate); |
| 3454 | } |
| 3455 | if(zRevision==0 || zRevision[0]==0){ |
| 3456 | if(g.localOpen/*checkout*/){ |
| 3457 | zRevision = db_lget("checkout-hash", 0)/*leak*/; |
| 3458 | }else{ |
| 3459 | zRevision = "trunk"; |
| 3460 | } |
| 3461 | } |
| 3462 | name_to_uuid2(zRevision, "ci", &cimi.zParentUuid); |
| 3463 | if(cimi.zParentUuid==0){ |
| 3464 | fossil_fatal("Cannot determine version to commit to."); |
| 3465 | } |
| 3466 | blob_read_from_file(&cimi.fileContent, zFilename, ExtFILE); |
| 3467 | { |
| 3468 | Blob theManifest = empty_blob; /* --save-manifest target */ |
| 3469 | Blob errMsg = empty_blob; |
| 3470 | int rc; |
| 3471 | if(zManifestFile){ |
| 3472 | cimi.pMfOut = &theManifest; |
| 3473 | } |
| 3474 | rc = checkin_mini(&cimi, &newRid, &errMsg); |
| 3475 | if(rc){ |
| 3476 | assert(blob_size(&errMsg)==0); |
| 3477 | }else{ |
| 3478 | assert(blob_size(&errMsg)); |
| 3479 | fossil_fatal("%b", &errMsg); |
| @@ -3475,23 +3485,23 @@ | |
| 3485 | blob_reset(&theManifest); |
| 3486 | } |
| 3487 | } |
| 3488 | if(newRid!=0){ |
| 3489 | fossil_print("New version%s: %z\n", |
| 3490 | (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", |
| 3491 | rid_to_uuid(newRid)); |
| 3492 | } |
| 3493 | db_end_transaction(0/*checkin_mini() will have triggered it to roll |
| 3494 | ** back in dry-run mode, but we need access to |
| 3495 | ** the transaction-written db state in this |
| 3496 | ** routine.*/); |
| 3497 | if(!(cimi.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3498 | fossil_warning("The checkout state is now out of sync " |
| 3499 | "with regards to this commit. It needs to be " |
| 3500 | "'update'd or 'close'd and re-'open'ed."); |
| 3501 | } |
| 3502 | CheckinMiniInfo_cleanup(&cimi); |
| 3503 | } |
| 3504 | |
| 3505 | |
| 3506 | /* |
| 3507 | ** Returns true if the given filename qualified for online editing |
| @@ -3522,105 +3532,208 @@ | |
| 3532 | ** Parameters intended to be passed in only via the editor's own form: |
| 3533 | ** |
| 3534 | ** diff If true, show diff from prev version. |
| 3535 | ** preview If true, preview (how depends on mimetype). |
| 3536 | ** content File content. |
| 3537 | ** comment Checkin comment. |
| 3538 | ** n Optional comment mimetype ("n" as in N-card). |
| 3539 | ** |
| 3540 | ** |
| 3541 | */ |
| 3542 | void fileedit_page(){ |
| 3543 | const char * zFilename = PD("file",P("name")); /* filename */ |
| 3544 | const char * zRev = P("r"); /* checkin version */ |
| 3545 | const char * zContent = P("content"); /* file content */ |
| 3546 | const char * zComment = P("comment"); /* checkin comment */ |
| 3547 | CheckinMiniInfo cimi; /* Checkin state */ |
| 3548 | int submitMode = 0; /* See mapping below */ |
| 3549 | char * zRevResolved = 0; /* Resolved zRev */ |
| 3550 | int vid; /* checkin rid */ |
| 3551 | char * zFileUuid = 0; /* File content UUID */ |
| 3552 | Blob err = empty_blob; /* Error report */ |
| 3553 | const char * zFlagCheck = 0; /* Temp url flag holder */ |
| 3554 | Stmt stmt = empty_Stmt; |
| 3555 | #define fail(EXPR) blob_appendf EXPR; goto end_footer |
| 3556 | |
| 3557 | login_check_credentials(); |
| 3558 | if( !g.perm.Write ){ |
| 3559 | login_needed(g.anon.Write); |
| 3560 | return; |
| 3561 | } |
| 3562 | CheckinMiniInfo_init(&cimi); |
| 3563 | zFlagCheck = P("comment"); |
| 3564 | if(zFlagCheck){ |
| 3565 | cimi.zMimetype = mprintf("%s",zFlagCheck); |
| 3566 | zFlagCheck = 0; |
| 3567 | } |
| 3568 | cimi.zUser = mprintf("%s",g.zLogin); |
| 3569 | /* |
| 3570 | ** TODOs include, but are not limited to: |
| 3571 | ** |
| 3572 | ** - Preview button + view |
| 3573 | ** |
| 3574 | ** - Diff button + view |
| 3575 | ** |
| 3576 | */ |
| 3577 | if(!zRev || !*zRev || !zFilename || !*zFilename){ |
| 3578 | fail((&err,"Missing required URL parameters.")); |
| 3579 | } |
| 3580 | vid = symbolic_name_to_rid(zRev, "ci"); |
| 3581 | if(0==vid){ |
| 3582 | fail((&err,"Could not resolve checkin version.")); |
| 3583 | } |
| 3584 | |
| 3585 | /* Find the repo-side file entry or fail... */ |
| 3586 | zRevResolved = rid_to_uuid(vid); |
| 3587 | db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " |
| 3588 | "WHERE filename=%Q %s AND checkinID=%d", |
| 3589 | zFilename, filename_collation(), vid); |
| 3590 | if(SQLITE_ROW==db_step(&stmt)){ |
| 3591 | const char * zPerm = db_column_text(&stmt, 1); |
| 3592 | const int isLink = zPerm ? strstr(zPerm,"l")!=0 : 0; |
| 3593 | if(isLink){ |
| 3594 | fail((&err,"Editing symlinks is not permitted.")); |
| 3595 | } |
| 3596 | zFileUuid = mprintf("%s",db_column_text(&stmt, 0)); |
| 3597 | } |
| 3598 | db_finalize(&stmt); |
| 3599 | if(!zFileUuid){ |
| 3600 | fail((&err,"Checkin [%S] does not contain file: %h", |
| 3601 | zRevResolved, zFilename)); |
| 3602 | } |
| 3603 | |
| 3604 | /* Read file content from submit request or repo... */ |
| 3605 | if(zContent==0){ |
| 3606 | const int frid = fast_uuid_to_rid(zFileUuid); |
| 3607 | assert(frid); |
| 3608 | content_get(frid, &cimi.fileContent); |
| 3609 | zContent = blob_size(&cimi.fileContent) |
| 3610 | ? blob_str(&cimi.fileContent) : NULL; |
| 3611 | }else{ |
| 3612 | blob_init(&cimi.fileContent,zContent,-1); |
| 3613 | } |
| 3614 | if(looks_like_binary(&cimi.fileContent)){ |
| 3615 | fail((&err,"File appears to be binary. Cannot edit: %h", |
| 3616 | zFilename)); |
| 3617 | } |
| 3618 | |
| 3619 | /* All set. Here we go... */ |
| 3620 | style_header("File Editor"); |
| 3621 | #define fp fossil_print |
| 3622 | /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */ |
| 3623 | |
| 3624 | fp("<h1>Editing: %h</h1>",zFilename); |
| 3625 | fp("<p class='hint'>Permalink: " |
| 3626 | "<a href='%R/fileedit?file=%T&r=%s'>" |
| 3627 | "%R/fileedit?file=%T&r=%s</a></p>", |
| 3628 | zFilename, zRev, zFilename, zRev); |
| 3629 | fp("<p>This page is <em>far from complete</em>.</p>\n"); |
| 3630 | |
| 3631 | fp("<form action='%R/fileedit' method='POST' " |
| 3632 | "class='fileedit-form'>\n"); |
| 3633 | |
| 3634 | /******* Hidden fields *******/ |
| 3635 | fp("<input type='hidden' name='r' value='%s'>", zRevResolved); |
| 3636 | fp("<input type='hidden' name='file' value='%T'>", |
| 3637 | zFilename); |
| 3638 | |
| 3639 | /******* Comment *******/ |
| 3640 | fp("<h3>Comment</h3>\n"); |
| 3641 | fp("<textarea name='comment' rows='3' cols='80'>"); |
| 3642 | if(zComment && *zComment){ |
| 3643 | fp("%h"/*%h? %s?*/, zComment); |
| 3644 | } |
| 3645 | fp("</textarea>\n"); |
| 3646 | fp("<div class='hint'>Comments use the Fossil wiki markup " |
| 3647 | "syntax.</div>"/*TODO: radiobuttons for fossil/me/plain text*/); |
| 3648 | |
| 3649 | /******* Content *******/ |
| 3650 | fp("<h3>Content</h3>\n"); |
| 3651 | fp("<textarea name='content' rows='20' cols='80'>"); |
| 3652 | if(zContent && *zContent){ |
| 3653 | fp("%h", zContent); |
| 3654 | } |
| 3655 | fp("</textarea>\n"); |
| 3656 | |
| 3657 | /******* Flags/options *******/ |
| 3658 | fp("<fieldset class='fileedit-options'>" |
| 3659 | "<legend>Many checkboxes are TODO</legend><div>" |
| 3660 | /* Chrome does not sanely lay out multiple |
| 3661 | ** fieldset children after the <legend>, so |
| 3662 | ** a containing div is necessary. */); |
| 3663 | /* |
| 3664 | ** TODO: Put checkboxes here... |
| 3665 | ** |
| 3666 | ** allow-fork, dry-run, convert-eol, allow-merge-conflict, |
| 3667 | ** set-exec-bit, date-override, allow-older (in case server time is |
| 3668 | ** messed up or someone checked something in w/ a future timestamp), |
| 3669 | ** prefer-delta, strongly-prefer-delta (undocumented - for |
| 3670 | ** development/admin use only). |
| 3671 | */ |
| 3672 | fp("</div></fieldset>"); |
| 3673 | |
| 3674 | /******* Buttons *******/ |
| 3675 | fp("<fieldset class='fileedit-options'>" |
| 3676 | "<legend>Several buttons are TODO</legend><div>"); |
| 3677 | fp("<button type='submit' name='submit' value='1'>" |
| 3678 | "Submit (dry-run)</button>"); |
| 3679 | fp("<button type='submit' name='submit' value='2'>" |
| 3680 | "Preview (TODO)</button>"); |
| 3681 | fp("<button type='submit' name='submit' value='3'>" |
| 3682 | "Diff (TODO)</button>"); |
| 3683 | fp("</div></fieldset>"); |
| 3684 | |
| 3685 | /******* End of form *******/ |
| 3686 | fp("</form>\n"); |
| 3687 | zContent = 0; |
| 3688 | fossil_free(zRevResolved); |
| 3689 | fossil_free(zFileUuid); |
| 3690 | |
| 3691 | submitMode = atoi(PD("submit","0")) |
| 3692 | /* |
| 3693 | ** Submit modes: 1=submit (save), 2=preview, 3=diff |
| 3694 | */; |
| 3695 | if(1==submitMode/*save*/){ |
| 3696 | Blob manifest = empty_blob; |
| 3697 | int rc; |
| 3698 | |
| 3699 | /* TODO: pull these flags from P() */ |
| 3700 | cimi.flags |= CIMINI_DRY_RUN; |
| 3701 | cimi.flags |= CIMINI_CONVERT_EOL; |
| 3702 | cimi.flags |= CIMINI_PREFER_DELTA; |
| 3703 | /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/ |
| 3704 | |
| 3705 | db_begin_transaction(); |
| 3706 | cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0); |
| 3707 | assert(cimi.pParent && "We know vid is valid."); |
| 3708 | cimi.zFilename = mprintf("%s",zFilename); |
| 3709 | cimi.pMfOut = &manifest; |
| 3710 | cimi.filePerm = PERM_REG; |
| 3711 | if(zComment && *zComment){ |
| 3712 | blob_append(&cimi.comment, zComment, -1); |
| 3713 | } |
| 3714 | rc = checkin_mini(&cimi, 0, &err); |
| 3715 | if(rc!=0){ |
| 3716 | fp("<h3>Manifest</h3><pre><code>%h</code></pre>", |
| 3717 | blob_str(&manifest)); |
| 3718 | } |
| 3719 | blob_reset(&manifest); |
| 3720 | db_end_transaction(rc ? 0 : 1); |
| 3721 | }else if(2==submitMode/*preview*/){ |
| 3722 | /* TODO */ |
| 3723 | }else if(3==submitMode/*diff*/){ |
| 3724 | /* TODO */ |
| 3725 | }else{ |
| 3726 | /* Ignore invalid submitMode value */ |
| 3727 | goto end_footer; |
| 3728 | } |
| 3729 | |
| 3730 | end_footer: |
| 3731 | if(blob_size(&err)){ |
| 3732 | fp("<div class='fileedit-error-report'>%h</div>", |
| 3733 | blob_str(&err)); |
| 3734 | } |
| 3735 | blob_reset(&err); |
| 3736 | CheckinMiniInfo_cleanup(&cimi); |
| 3737 | style_footer(); |
| 3738 | #undef fp |
| 3739 | } |
| 3740 |
+21
-1
| --- src/default_css.txt | ||
| +++ src/default_css.txt | ||
| @@ -863,11 +863,31 @@ | ||
| 863 | 863 | // overflow: auto; |
| 864 | 864 | // } |
| 865 | 865 | // .fileedit-XXX => /fileedit page |
| 866 | 866 | .fileedit-form textarea { |
| 867 | 867 | width: 100%; |
| 868 | +} | |
| 869 | +.fileedit-form fieldset { | |
| 870 | + border-radius: 0.5em; | |
| 868 | 871 | } |
| 869 | 872 | .fileedit-form .fileedit-options { |
| 870 | 873 | padding: 0.5em; |
| 871 | 874 | margin: 1em 0 0 0; |
| 872 | - border: 2px inset #a0a0a0; | |
| 875 | +} | |
| 876 | +.fileedit-form .fileedit-buttons > div > * { | |
| 877 | + margin: 0 0.25em 0.25em 0.25em; | |
| 878 | +} | |
| 879 | +.fileedit-form .fileedit-options > div > * { | |
| 880 | + margin: 0 0.25em 0.25em 0.25em; | |
| 881 | +} | |
| 882 | +.fileedit-form .hint { | |
| 883 | + font-size: 80%; | |
| 884 | + opacity: 0.75; | |
| 885 | +} | |
| 886 | + | |
| 887 | +.fileedit-error-report { | |
| 888 | + background: yellow; | |
| 889 | + color: darkred; | |
| 890 | + margin: 1em 0; | |
| 891 | + padding: 0.5em; | |
| 892 | + border-radius: 0.5em; | |
| 873 | 893 | } |
| 874 | 894 |
| --- src/default_css.txt | |
| +++ src/default_css.txt | |
| @@ -863,11 +863,31 @@ | |
| 863 | // overflow: auto; |
| 864 | // } |
| 865 | // .fileedit-XXX => /fileedit page |
| 866 | .fileedit-form textarea { |
| 867 | width: 100%; |
| 868 | } |
| 869 | .fileedit-form .fileedit-options { |
| 870 | padding: 0.5em; |
| 871 | margin: 1em 0 0 0; |
| 872 | border: 2px inset #a0a0a0; |
| 873 | } |
| 874 |
| --- src/default_css.txt | |
| +++ src/default_css.txt | |
| @@ -863,11 +863,31 @@ | |
| 863 | // overflow: auto; |
| 864 | // } |
| 865 | // .fileedit-XXX => /fileedit page |
| 866 | .fileedit-form textarea { |
| 867 | width: 100%; |
| 868 | } |
| 869 | .fileedit-form fieldset { |
| 870 | border-radius: 0.5em; |
| 871 | } |
| 872 | .fileedit-form .fileedit-options { |
| 873 | padding: 0.5em; |
| 874 | margin: 1em 0 0 0; |
| 875 | } |
| 876 | .fileedit-form .fileedit-buttons > div > * { |
| 877 | margin: 0 0.25em 0.25em 0.25em; |
| 878 | } |
| 879 | .fileedit-form .fileedit-options > div > * { |
| 880 | margin: 0 0.25em 0.25em 0.25em; |
| 881 | } |
| 882 | .fileedit-form .hint { |
| 883 | font-size: 80%; |
| 884 | opacity: 0.75; |
| 885 | } |
| 886 | |
| 887 | .fileedit-error-report { |
| 888 | background: yellow; |
| 889 | color: darkred; |
| 890 | margin: 1em 0; |
| 891 | padding: 0.5em; |
| 892 | border-radius: 0.5em; |
| 893 | } |
| 894 |