Fossil

Check-in [33861414ae]
Login

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

Overview
Comment: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.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | checkin-without-checkout
Files: files | file ages | folders
SHA3-256: 33861414aefee477466cbe224687e650e11ba4868a6fbc4b6639fc1bc4028cfc
User & Date: stephan 2020-05-02 15:03:12.426
Context
2020-05-02
16:43
Instead of injecting multiple mini-scripts, queue up script code and emit it in one anonymous function at the end. Update the version number and permalink after saving so that we're basing on the new version. check-in: 6407b6ca37 user: stephan tags: checkin-without-checkout
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
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/checkin.c.
2770
2771
2772
2773
2774
2775
2776




2777




2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811

/*
** Indicates that the content of the newly-checked-in file is
** converted, if needed, to use the same EOL style as the previous
** version of that file. Only the in-memory/in-repo copies are
** affected, not the original file (if any).
*/




CIMINI_CONVERT_EOL = 1<<5,




/*
** A hint to checkin_mini() to "prefer" creation of a delta manifest.
** It may decide not to for various reasons.
*/
CIMINI_PREFER_DELTA = 1<<6,
/*
** A "stronger hint" to checkin_mini() to prefer creation of a delta
** manifest if it at all can. It will decide not to only if creation
** of a delta is not a realistic option. For this to work, it must be
** set together with the CIMINI_PREFER_DELTA flag, but the two cannot
** be combined in this enum.
**
** This option is ONLY INTENDED FOR TESTING, used in bypassing
** heuristics which may otherwise disable generation of a delta on the
** grounds of efficiency (e.g. not generating a delta if the parent
** non-delta only has a few F-cards).
**
** The forbid-delta-manifests repo config option trumps this.
*/
CIMINI_STRONGLY_PREFER_DELTA = 1<<7,
/*
** Tells checkin_mini() to permit the addition of a new file. Normally
** this is disabled because there are many cases where it could cause
** the inadvertent addition of a new file when an update to an
** existing was intended, as a side-effect of name-case differences.
*/
CIMINI_ALLOW_NEW_FILE = 1<<8
};

/*
** Initializes p to a known-valid default state.
*/
static void CheckinMiniInfo_init( CheckinMiniInfo * p ){
  memset(p, 0, sizeof(CheckinMiniInfo));







>
>
>
>
|
>
>
>
>




|














|






|







2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819

/*
** Indicates that the content of the newly-checked-in file is
** converted, if needed, to use the same EOL style as the previous
** version of that file. Only the in-memory/in-repo copies are
** affected, not the original file (if any).
*/
CIMINI_CONVERT_EOL_INHERIT = 1<<5,
/*
** Indicates that the input's EOLs should be converted to Unix-style.
*/
CIMINI_CONVERT_EOL_UNIX = 1<<6,
/*
** Indicates that the input's EOLs should be converted to Windows-style.
*/
CIMINI_CONVERT_EOL_WINDOWS = 1<<7,
/*
** A hint to checkin_mini() to "prefer" creation of a delta manifest.
** It may decide not to for various reasons.
*/
CIMINI_PREFER_DELTA = 1<<8,
/*
** A "stronger hint" to checkin_mini() to prefer creation of a delta
** manifest if it at all can. It will decide not to only if creation
** of a delta is not a realistic option. For this to work, it must be
** set together with the CIMINI_PREFER_DELTA flag, but the two cannot
** be combined in this enum.
**
** This option is ONLY INTENDED FOR TESTING, used in bypassing
** heuristics which may otherwise disable generation of a delta on the
** grounds of efficiency (e.g. not generating a delta if the parent
** non-delta only has a few F-cards).
**
** The forbid-delta-manifests repo config option trumps this.
*/
CIMINI_STRONGLY_PREFER_DELTA = 1<<9,
/*
** Tells checkin_mini() to permit the addition of a new file. Normally
** this is disabled because there are many cases where it could cause
** the inadvertent addition of a new file when an update to an
** existing was intended, as a side-effect of name-case differences.
*/
CIMINI_ALLOW_NEW_FILE = 1<<10
};

/*
** Initializes p to a known-valid default state.
*/
static void CheckinMiniInfo_init( CheckinMiniInfo * p ){
  memset(p, 0, sizeof(CheckinMiniInfo));
2839
2840
2841
2842
2843
2844
2845











2846
2847
2848
2849
2850
2851
2852
static const char * mfile_permint_mstring(int perm){
  switch(perm){
    case PERM_EXE: return " x";
    case PERM_LNK: return " l";
    default: return "";
  }
}












static const char * mfile_perm_mstring(const ManifestFile * p){
  return mfile_permint_mstring(manifest_file_mperm(p));
}

/*
** Internal helper for checkin_mini() and friends. Appends an F-card







>
>
>
>
>
>
>
>
>
>
>







2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
static const char * mfile_permint_mstring(int perm){
  switch(perm){
    case PERM_EXE: return " x";
    case PERM_LNK: return " l";
    default: return "";
  }
}

/*
** Given a ManifestFile permission string (or NULL), it returns one of
** PERM_REG, PERM_EXE, or PERM_LNK.
*/
static int mfile_permstr_int(const char *zPerm){
  if(!zPerm || !*zPerm) return PERM_REG;
  else if(strstr(zPerm,"x")) return PERM_EXE;
  else if(strstr(zPerm,"l")) return PERM_LNK;
  else return PERM_REG/*???*/;
}

static const char * mfile_perm_mstring(const ManifestFile * p){
  return mfile_permint_mstring(manifest_file_mperm(p));
}

/*
** Internal helper for checkin_mini() and friends. Appends an F-card
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076


3077
3078
3079
3080
3081
3082
3083
**   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
** validation fails.
**
** On error, returns false (0) and, if pErr is not NULL, writes a







|
|
|
|
|
|



>
>







3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
**   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_INHERIT 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->comment may be converted to Unix-style newlines.
**
** pCI's ownership is not modified.
**
** This function validates several of the inputs and fails if any
** validation fails.
**
** On error, returns false (0) and, if pErr is not NULL, writes a
3172
3173
3174
3175
3176
3177
3178









3179
3180
3181
3182
3183
3184
3185
         pCI->zDate ? pCI->zDate : "now");
    if(zDVal==0 || zDVal[0]==0){
      fossil_free(zDVal);
      ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate));
    }
    fossil_free(pCI->zDate);
    pCI->zDate = zDVal;









  }
  /* Potential TODOs include:
  **
  ** - Commit allows an empty checkin only with a flag, but we
  **   currently disallow it entirely. Conform with commit?
  **
  ** Non-TODOs:







>
>
>
>
>
>
>
>
>







3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
         pCI->zDate ? pCI->zDate : "now");
    if(zDVal==0 || zDVal[0]==0){
      fossil_free(zDVal);
      ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate));
    }
    fossil_free(pCI->zDate);
    pCI->zDate = zDVal;
  }
  { /* Confirm that only one EOL policy is in place. */
    int n = 0;
    if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags) ++n;
    if(CIMINI_CONVERT_EOL_UNIX & pCI->flags) ++n;
    if(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags) ++n;
    if(n>1){
      ci_err((pErr,"More than 1 EOL conversion policy was specified."));
    }
  }
  /* Potential TODOs include:
  **
  ** - Commit allows an empty checkin only with a flag, but we
  **   currently disallow it entirely. Conform with commit?
  **
  ** Non-TODOs:
3210
3211
3212
3213
3214
3215
3216
3217
3218

3219

3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238


3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260













3261
3262
3263
3264

3265
3266
3267
3268
3269
3270
3271
3272
3273


3274
3275
3276
3277
3278
3279





3280
3281
3282
3283
3284
3285
3286
           && manifest_file_mperm(zFilePrev)==PERM_LNK){
    ci_err((pErr,"Cannot save a symlink via a mini-checkin."));
  }
  if(zFilePrev){
    prevFRid = fast_uuid_to_rid(zFilePrev->zUuid);
  }

  if((CIMINI_CONVERT_EOL & pCI->flags)
     && zFilePrev!=0

     && blob_size(&pCI->fileContent)>0){

    /* Confirm that the new content has the same EOL style as its
    ** predecessor and convert it, if needed, to the same style. Note
    ** that this inherently runs a risk of breaking content,
    ** e.g. string literals which contain embedded newlines. Note that
    ** HTML5 specifies that form-submitted TEXTAREA content gets
    ** normalized to CRLF-style:
    **
    ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
    **
    ** More performant/efficient would be to offer a flag which says
    ** which newline form to use, converting the new copy (if needed)
    ** without having to examine the original. Since the primary use
    ** case is a web interface, it would be easy to offer it as a
    ** checkbox there.
    */
    const int pseudoBinary = LOOK_LONG | LOOK_NUL;
    const int lookFlags = LOOK_CRLF | pseudoBinary;
    const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags );
    if(!(pseudoBinary & lookNew)){


      Blob contentPrev = empty_blob;
      int lookOrig, nOrig;
      content_get(prevFRid, &contentPrev);
      lookOrig = looks_like_utf8(&contentPrev, lookFlags);
      nOrig = blob_size(&contentPrev);
      blob_reset(&contentPrev);
      if(nOrig>0 && lookOrig!=lookNew){
        /* If there is a newline-style mismatch, adjust the new
        ** content version to the previous style, then re-hash the
        ** content. Note that this means that what we insert is NOT
        ** what's in the filesystem.
        */
        int rehash = 0;
        if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){
          /* Old has Unix-style, new has Windows-style. */
          blob_to_lf_only(&pCI->fileContent);
          rehash = 1;
        }else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){
          /* Old has Windows-style, new has Unix-style. */
          blob_add_cr(&pCI->fileContent);
          rehash = 1;
        }













        if(rehash!=0){
          hname_hash(&pCI->fileContent, 0, &pCI->fileHash);
        }
      }

    }
  }
  if(blob_size(&pCI->fileHash)==0){
    /* Hash the content if it's not done already... */
    hname_hash(&pCI->fileContent, 0, &pCI->fileHash);
    assert(blob_size(&pCI->fileHash)>0);
  }
  if(zFilePrev){
    /* Has this file been changed since its previous commit? */


    assert(blob_size(&pCI->fileHash));
    if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))
       && manifest_file_mperm(zFilePrev)==pCI->filePerm){
      ci_err((pErr,"File is unchanged. Not saving."));
    }
  }





  /* Create, save, deltify, and crosslink the manifest... */
  if(create_manifest_mini(&mf, pCI, pErr)==0){
    return 0;
  }
  isPrivate = content_is_private(pCI->pParent->rid);
  rid = content_put_ex(&mf, 0, 0, 0, isPrivate);
  if(pCI->flags & CIMINI_DUMP_MANIFEST){







|
|
>
|
>



















>
>
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
>
|
<






|
>
>






>
>
>
>
>







3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284

3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312

3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
           && manifest_file_mperm(zFilePrev)==PERM_LNK){
    ci_err((pErr,"Cannot save a symlink via a mini-checkin."));
  }
  if(zFilePrev){
    prevFRid = fast_uuid_to_rid(zFilePrev->zUuid);
  }

  if(((CIMINI_CONVERT_EOL_INHERIT & pCI->flags)
      || (CIMINI_CONVERT_EOL_UNIX & pCI->flags)
      || (CIMINI_CONVERT_EOL_WINDOWS & pCI->flags))
     && blob_size(&pCI->fileContent)>0
     ){
    /* Confirm that the new content has the same EOL style as its
    ** predecessor and convert it, if needed, to the same style. Note
    ** that this inherently runs a risk of breaking content,
    ** e.g. string literals which contain embedded newlines. Note that
    ** HTML5 specifies that form-submitted TEXTAREA content gets
    ** normalized to CRLF-style:
    **
    ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
    **
    ** More performant/efficient would be to offer a flag which says
    ** which newline form to use, converting the new copy (if needed)
    ** without having to examine the original. Since the primary use
    ** case is a web interface, it would be easy to offer it as a
    ** checkbox there.
    */
    const int pseudoBinary = LOOK_LONG | LOOK_NUL;
    const int lookFlags = LOOK_CRLF | pseudoBinary;
    const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags );
    if(!(pseudoBinary & lookNew)){
      int rehash = 0;
      if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags){
        Blob contentPrev = empty_blob;
        int lookOrig, nOrig;
        content_get(prevFRid, &contentPrev);
        lookOrig = looks_like_utf8(&contentPrev, lookFlags);
        nOrig = blob_size(&contentPrev);
        blob_reset(&contentPrev);
        if(nOrig>0 && lookOrig!=lookNew){
          /* If there is a newline-style mismatch, adjust the new
          ** content version to the previous style, then re-hash the
          ** content. Note that this means that what we insert is NOT
          ** what's in the filesystem.
          */

          if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){
            /* Old has Unix-style, new has Windows-style. */
            blob_to_lf_only(&pCI->fileContent);
            rehash = 1;
          }else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){
            /* Old has Windows-style, new has Unix-style. */
            blob_add_cr(&pCI->fileContent);
            rehash = 1;
          }
        }
      }else{
        const int oldSize = blob_size(&pCI->fileContent);
        if(CIMINI_CONVERT_EOL_UNIX & pCI->flags){
          blob_to_lf_only(&pCI->fileContent);
        }else{
          blob_add_cr(&pCI->fileContent);
          assert(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags);
        }
        if(blob_size(&pCI->fileContent)!=oldSize){
          rehash = 1;
        }
      }
      if(rehash!=0){
        hname_hash(&pCI->fileContent, 0, &pCI->fileHash);
      }
    }
  }/* end EOL conversion */


  if(blob_size(&pCI->fileHash)==0){
    /* Hash the content if it's not done already... */
    hname_hash(&pCI->fileContent, 0, &pCI->fileHash);
    assert(blob_size(&pCI->fileHash)>0);
  }
  if(zFilePrev){
    /* Has this file been changed since its previous commit?  Note
    ** that we have to delay this check until after the potentially
    ** expensive EOL conversion. */
    assert(blob_size(&pCI->fileHash));
    if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))
       && manifest_file_mperm(zFilePrev)==pCI->filePerm){
      ci_err((pErr,"File is unchanged. Not saving."));
    }
  }
#if 1
  /* Do we really want to normalize comment EOLs? Web-posting will
  ** submit them in CRLF format. */
  blob_to_lf_only(&pCI->comment);
#endif
  /* Create, save, deltify, and crosslink the manifest... */
  if(create_manifest_mini(&mf, pCI, pErr)==0){
    return 0;
  }
  isPrivate = content_is_private(pCI->pParent->rid);
  rid = content_put_ex(&mf, 0, 0, 0, isPrivate);
  if(pCI->flags & CIMINI_DUMP_MANIFEST){
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
  }
  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;







|
|







3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
  }
  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-prev",0,0)!=0){
    cimi.flags |= CIMINI_CONVERT_EOL_INHERIT;
  }
  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;
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514


3515

3516







3517
3518

















































3519
3520
3521
3522
3523
3524
3525
                   "'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.
**
** Currently only looks at the user's permissions, pending decisions
** on whether we want to filter them based on a glob list or mimetype
** list.
*/
int file_is_online_editable(const char *zFilename){


  if(g.perm.Write){

    return 1;







  }
  return 0;    

















































}

/*
** WEBPAGE: fileedit
**
** EXPERIMENTAL and subject to change and removal at any time. The goal
** is to allow online edits of files.







|
|

|
|
|

|
>
>
|
>
|
>
>
>
>
>
>
>

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
                   "'update'd or 'close'd and re-'open'ed.");
  }
  CheckinMiniInfo_cleanup(&cimi);
}


/*
** Returns true if the given filename qualifies for online editing by
** the current user, else returns false.
**
** Editing requires that the user have the Write permission and that
** the filename match the glob defined by the fileedit-glob setting.
** A missing or empty value for that glob disables all editing.
*/
int fileedit_is_editable(const char *zFilename){
  static Glob * pGlobs = 0;
  static int once = 0;
  if(0==g.perm.Write || zFilename==0 || *zFilename==0
     || (once!=0 && pGlobs==0)){
    return 0;
  }else if(0==pGlobs){
    char * zGlobs = db_get("fileedit-glob",0);
    once = 1;
    if(0==zGlobs) return 0;
    pGlobs = glob_create(zGlobs);
    fossil_free(zGlobs);
    once = 1;
  }
  return glob_match(pGlobs, zFilename);
}

static void fileedit_emit_script(int phase){
  if(0==phase){
    fossil_print("<script nonce='%s'>", style_nonce());
  }else{
    fossil_print("</script>\n");
  }
}
static void fileedit_emit_script_fetch(){
#define fp fossil_print
  fileedit_emit_script(0);
  fp("window.fossilFetch = function(path,opt){\n");
  fp("  if('function'===typeof opt){\n");
  fp("    opt={onload:opt};\n");
  fp("  }else{\n");
  fp("    opt=opt||{onload:function(r){console.debug('response:',r)}}\n");
  fp("  }\n");
  fp("  const url='%R/'+path, x=new XMLHttpRequest();\n");
  fp("  x.open(opt.method||'GET', url, true);\n");
  fp("  x.responseType=opt.responseType||'text';\n");
  fp("  if(opt.onload){\n");
  fp("    x.onload = function(e){\n");
  fp("      if(200!==this.status){\n");
  fp("        if(opt.onerror) opt.onerror(e);\n");
  fp("        return;\n");
  fp("      }\n");
  fp("      opt.onload(this.response);\n");
  fp("    }\n");
  fp("  }\n");
  fp("  x.send();");
  fp("};\n");
  fileedit_emit_script(1);
#undef fp
};

static void fileedit_checkbox(const char *zFieldName,
                              const char * zLabel,
                              const char * zValue,
                              const char * zTip,
                              int isChecked){
  fossil_print("<span class='input-with-label'");
  if(zTip && *zTip){
    fossil_print(" title='%h'", zTip);
  }
  fossil_print("><input type='checkbox' name='%s' value='%T'%s/>",
               zFieldName,
               zValue ? zValue : "", isChecked ? " checked" : "");
  fossil_print("<span>%h</span></span>", zLabel);
}

/*
** WEBPAGE: fileedit
**
** EXPERIMENTAL and subject to change and removal at any time. The goal
** is to allow online edits of files.
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
  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");
  







|

>











>
>
>














>


>
>
>
>













|
|









>
>



<
<












<



>
>







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
3740

3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
  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, newVid = 0;                    /* checkin rid */
  char * zFileUuid = 0;                   /* File content UUID */
  int frid = 0;                           /* File content rid */
  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);
  submitMode = atoi(PD("submit","0"))
    /* Submit modes: 0=initial request,
    ** 1=submit (save), 2=preview, 3=diff */;
  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
  **
  */
  style_header("File Editor");
  if(!zRev || !*zRev || !zFilename || !*zFilename){
    fail((&err,"Missing required URL parameters."));
  }
  if(0==fileedit_is_editable(zFilename)){
    fail((&err,"Filename is disallowed by the fileedit-glob "
          "repository setting."));
  }
  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);
    cimi.filePerm = mfile_permstr_int(zPerm);
    if(PERM_LNK==cimi.filePerm){
      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));
  }
  frid = fast_uuid_to_rid(zFileUuid);
  assert(frid);

  /* Read file content from submit request or repo... */
  if(zContent==0){


    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... */

#define fp fossil_print
  /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */

  fileedit_emit_script_fetch();
  
  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");
  
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
  }
  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
}







|
|
|
<

<









|
|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|





|








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




<
<
<
<





<
<








<



|
|
>
|
>
>
>
|
>





>

|






>
>
>









3766
3767
3768
3769
3770
3771
3772
3773
3774
3775

3776

3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881




3882
3883
3884
3885
3886


3887
3888
3889
3890
3891
3892
3893
3894

3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
  }
  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' id='fileedit-content' "
     "rows='20' cols='80'>");
  fp("Loading...");

  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...
  **
  ** dry-run, convert-eol
  ** 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).
  */
  if(0==submitMode || P("dry_run")!=0){
    cimi.flags |= CIMINI_DRY_RUN;
  }
  fileedit_checkbox("dry_run", "Dry-run?", "1",
                    "In dry-run mode, do not really save.",
                    cimi.flags & CIMINI_DRY_RUN);
  if(P("allow_fork")!=0){
    cimi.flags |= CIMINI_ALLOW_FORK;
  }
  fileedit_checkbox("allow_fork", "Allow fork?", "1",
                    "Allow saving to create a fork?",
                    cimi.flags & CIMINI_ALLOW_FORK);
  if(P("exec_bit")!=0){
    cimi.filePerm = PERM_EXE;
  }
  fileedit_checkbox("exec_bit", "Executable?", "1",
                    "Set executable bit?",
                    PERM_EXE==cimi.filePerm);
  if(P("allow_merge_conflict")!=0){
    cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
  }
  fileedit_checkbox("allow_merge_conflict",
                    "Allow merge conflict markers?", "1",
                    "Allow saving even if the content contains what "
                    "appear to be fossil merge conflict markers?",
                    cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
  {/* EOL conversion policy... */
    const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0"));
    switch(eolMode){
      case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
      case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
      default: cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; break;
    }
    fp("<select name='eol' "
       "title='EOL conversion policy, noting that form-processing "
       "may implicitly change the line endings of the input.'>");
    fp("<option value='0'%s>Inherit EOLs</option>",
       eolMode==0 ? " selected" : "");
    fp("<option value='1'%s/>Unix EOLs</option>",
       eolMode==1 ? " selected" : "");
    fp("<option value='2'%s>Windows EOLs</option>",
       eolMode==2 ? " selected" : "");
    fp("</select>");
  }
  fp("</div></fieldset>") /* end of checkboxes */;

  /******* Buttons *******/  
  fp("<fieldset class='fileedit-options'>"
     "<legend>Several buttons are TODO</legend><div>");
  fp("<button type='submit' name='submit' value='1'>"
     "Submit</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");

  /* Populate doc...
  ** To avoid all escaping-related issues, we have to do this one
  ** of two ways:
  **
  ** 1) Fetch the content via AJAX. That only works if the content
  **    is already in the db, but not for edited versions.
  **
  ** 2) Store the content as JSON and feed it into the textarea
  **    using JavaScript.
  */
  fileedit_emit_script(0);
  {
    char const * zQuoted = 0;
    if(blob_size(&cimi.fileContent)>0){
      db_prepare(&stmt, "SELECT json_quote(%B)",&cimi.fileContent);
      db_step(&stmt);
      zQuoted = db_column_text(&stmt,0);
    }
    fp("document.getElementById('fileedit-content').value=%s;",
       zQuoted ? zQuoted : "''");
    if(stmt.pStmt){
      db_finalize(&stmt);
    }
  }
  fileedit_emit_script(1);

  zContent = 0;
  fossil_free(zRevResolved);
  fossil_free(zFileUuid);





  if(1==submitMode/*save*/){
    Blob manifest = empty_blob;
    int rc;

    /* TODO: pull these flags from P() */


    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;

    if(zComment && *zComment){
      blob_append(&cimi.comment, zComment, -1);
    }
    rc = checkin_mini(&cimi, &newVid, &err);
    if(newVid!=0){
      char * zNewUuid = rid_to_uuid(newVid);
      fp("<h3>Manifest%s: %S</h3><pre>"
         "<code class='fileedit-manifest'>%h</code>"
         "</pre>",
         (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
         zNewUuid, blob_str(&manifest));
      fossil_free(zNewUuid);
    }
    blob_reset(&manifest);
    db_end_transaction(rc ? 0 : 1);
  }else if(2==submitMode/*preview*/){
    /* TODO */
    fail((&err,"Preview mode is still TODO."));
  }else if(3==submitMode/*diff*/){
    fail((&err,"Diff mode is still TODO."));
  }else{
    /* Ignore invalid submitMode value */
    goto end_footer;
  }

end_footer:
  if(stmt.pStmt){
    db_finalize(&stmt);
  }
  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/configure.c.
146
147
148
149
150
151
152

153
154
155
156
157
158
159
  { "dotfiles",               CONFIGSET_PROJ },
  { "parent-project-code",    CONFIGSET_PROJ },
  { "parent-project-name",    CONFIGSET_PROJ },
  { "hash-policy",            CONFIGSET_PROJ },
  { "comment-format",         CONFIGSET_PROJ },
  { "mimetypes",              CONFIGSET_PROJ },
  { "forbid-delta-manifests", CONFIGSET_PROJ },


#ifdef FOSSIL_ENABLE_LEGACY_MV_RM
  { "mv-rm-files",            CONFIGSET_PROJ },
#endif

  { "ticket-table",           CONFIGSET_TKT  },
  { "ticket-common",          CONFIGSET_TKT  },







>







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  { "dotfiles",               CONFIGSET_PROJ },
  { "parent-project-code",    CONFIGSET_PROJ },
  { "parent-project-name",    CONFIGSET_PROJ },
  { "hash-policy",            CONFIGSET_PROJ },
  { "comment-format",         CONFIGSET_PROJ },
  { "mimetypes",              CONFIGSET_PROJ },
  { "forbid-delta-manifests", CONFIGSET_PROJ },
  { "fileedit-glob",          CONFIGSET_PROJ },

#ifdef FOSSIL_ENABLE_LEGACY_MV_RM
  { "mv-rm-files",            CONFIGSET_PROJ },
#endif

  { "ticket-table",           CONFIGSET_TKT  },
  { "ticket-common",          CONFIGSET_TKT  },
Changes to src/db.c.
3407
3408
3409
3410
3411
3412
3413









3414
3415
3416
3417
3418
3419
3420
#if !defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
/*
** SETTING: exec-rel-paths   boolean default=off
** When executing certain external commands (e.g. diff and
** gdiff), use relative paths.
*/
#endif









/*
** SETTING: gdiff-command    width=40 default=gdiff
** The value is an external command to run when performing a graphical
** diff. If undefined, text diff will be used.
*/
/*
** SETTING: gmerge-command   width=40







>
>
>
>
>
>
>
>
>







3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
#if !defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
/*
** SETTING: exec-rel-paths   boolean default=off
** When executing certain external commands (e.g. diff and
** gdiff), use relative paths.
*/
#endif

/*
** SETTING: fileedit-glob       width=40 block-text
** A comma- or newline-separated list of globs of filenames
** which are allowed to be edited using the /fileedit page.
** An empty list prohibits editing via that page. Note that
** it cannot edit binary files, so the glob should not
** contain any globs for, e.g., images or PDFs.
*/
/*
** SETTING: gdiff-command    width=40 default=gdiff
** The value is an external command to run when performing a graphical
** diff. If undefined, text diff will be used.
*/
/*
** SETTING: gmerge-command   width=40
Changes to src/default_css.txt.
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
























.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;
}































<







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
879
880
881
882
883
884
885

886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
.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;
}
code.fileedit-manifest {
  height: 12em;
  scroll: auto;
}
.input-with-label {
  border: 1px inset #808080;
  border-radius: 0.5em;
  padding: 0.25em 0.4em;
  margin: 0 0.5em;
  display: inline-block;
}
.input-with-label > input {
    margin: 0;
}
.input-with-label > input[type=checkbox] {
    vertical-align: sub;
}
.input-with-label > input[type=radio] {
    vertical-align: sub;
}
.input-with-label > span {
    margin: 0 0 0 0.4em;
    vertical-align: text-bottom;
}