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