Fossil SCM
Added support for optionally converted mini-commit file content to use the same EOL style as the previous version (HTML5 specifies that TEXTAREA form posts be normalized to CRLF).
Commit
5a1a73c356823566f7e7119ac503af85a254bb17749e58beef698f596f59473e
Parent
f8c18060470d017…
1 file changed
+94
-24
+94
-24
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2765,19 +2765,28 @@ | ||
| 2765 | 2765 | ** which predates their parent. This flag bypasses that |
| 2766 | 2766 | ** check. |
| 2767 | 2767 | */ |
| 2768 | 2768 | CIMINI_ALLOW_OLDER = 1<<4, |
| 2769 | 2769 | |
| 2770 | +/* | |
| 2771 | +** Indicates that the content of the newly-checked-in file is | |
| 2772 | +** converted, if needed, to use the same EOL style as the previous | |
| 2773 | +** version of that file. Only the in-memory/in-repo copies are | |
| 2774 | +** affected, not the original file (if any). | |
| 2775 | +*/ | |
| 2776 | +CIMINI_CONVERT_EOL = 1<<5, | |
| 2777 | + | |
| 2770 | 2778 | /* |
| 2771 | 2779 | ** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer |
| 2772 | 2780 | ** creation of a delta manifest. |
| 2773 | 2781 | */ |
| 2774 | -CIMINI_PREFER_DELTA = 1<<5, | |
| 2782 | +CIMINI_PREFER_DELTA = 1<<6, | |
| 2783 | + | |
| 2775 | 2784 | /* |
| 2776 | 2785 | ** NOT YET IMPLEMENTED. |
| 2777 | 2786 | */ |
| 2778 | -CIMINI_ALLOW_CLOSED_LEAF = 1<<6 | |
| 2787 | +CIMINI_ALLOW_CLOSED_LEAF = 1<<7 | |
| 2779 | 2788 | }; |
| 2780 | 2789 | |
| 2781 | 2790 | /* |
| 2782 | 2791 | ** Creates a manifest file, written to pOut, from the state in the |
| 2783 | 2792 | ** fully-populated pCI argument. pCI is not *semantically* modified |
| @@ -2904,17 +2913,21 @@ | ||
| 2904 | 2913 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2905 | 2914 | ** are unchanged. |
| 2906 | 2915 | ** |
| 2907 | 2916 | ** pCI may be modified as follows: |
| 2908 | 2917 | ** |
| 2909 | -** - If pCI->fileHash is empty, this routine populates it with the | |
| 2910 | -** repository's preferred hash algorithm. | |
| 2911 | -** | |
| 2912 | 2918 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 2913 | 2919 | ** string. If its original value cannot be validated then |
| 2914 | 2920 | ** this function fails. If pCI->zDate is NULL, the current time |
| 2915 | 2921 | ** is used. |
| 2922 | +** | |
| 2923 | +** - If pCI->fileContent is not binary and its line-ending style | |
| 2924 | +** differs from its previous version, it is converted to the same | |
| 2925 | +** EOL style. If this is done, the pCI->fileHash is re-computed. | |
| 2926 | +** | |
| 2927 | +** - If pCI->fileHash is empty, this routine populates it with the | |
| 2928 | +** repository's preferred hash algorithm. | |
| 2916 | 2929 | ** |
| 2917 | 2930 | ** pCI's ownership is not modified. |
| 2918 | 2931 | ** |
| 2919 | 2932 | ** This function validates several of the inputs and fails if any |
| 2920 | 2933 | ** validation fails. |
| @@ -2932,13 +2945,12 @@ | ||
| 2932 | 2945 | static int checkin_mini( CheckinMiniInfo * pCI, int *pRid, |
| 2933 | 2946 | int ciminiFlags, Blob * pErr ){ |
| 2934 | 2947 | Blob mf = empty_blob; /* output manifest */ |
| 2935 | 2948 | int rid = 0, frid = 0; /* various RIDs */ |
| 2936 | 2949 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2937 | - ManifestFile * zFile; /* file from pCI->pParent */; | |
| 2938 | - const char * zFilePrevUuid = 0; /* UUID of previous version of | |
| 2939 | - the file */ | |
| 2950 | + ManifestFile * zFilePrev; /* file entry from pCI->pParent */ | |
| 2951 | + int prevFRid = 0; /* RID of file's prev. version */ | |
| 2940 | 2952 | |
| 2941 | 2953 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 2942 | 2954 | |
| 2943 | 2955 | db_begin_transaction(); |
| 2944 | 2956 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| @@ -3009,51 +3021,102 @@ | ||
| 3009 | 3021 | ** some such. That said, the remainder of this function is written |
| 3010 | 3022 | ** as if this check did not exist, so enabling it "should" just be a |
| 3011 | 3023 | ** matter of removing this check or guarding it with a flag. |
| 3012 | 3024 | */ |
| 3013 | 3025 | manifest_file_rewind(pCI->pParent); |
| 3014 | - zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); | |
| 3015 | - if(!zFile){ | |
| 3026 | + zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); | |
| 3027 | + if(!zFilePrev){ | |
| 3016 | 3028 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3017 | 3029 | "Adding new files is currently not allowed.", |
| 3018 | 3030 | pCI->zFilename, pCI->zParentUuid)); |
| 3019 | - }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ | |
| 3031 | + }else if(zFilePrev->zPerm && strstr(zFilePrev->zPerm, "l")){ | |
| 3020 | 3032 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 3033 | + } | |
| 3034 | + if(zFilePrev){ | |
| 3035 | + prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); | |
| 3036 | + } | |
| 3037 | + | |
| 3038 | + /* Confirm that the new content has the same EOL style as its | |
| 3039 | + ** predecessor and convert it, if needed, to the same style. Note | |
| 3040 | + ** that this inherently runs a risk of breaking content, e.g. string | |
| 3041 | + ** literals which contain embedded newlines. Note that HTML5 | |
| 3042 | + ** specifies that form-submitted TEXTAREA content gets normalized to | |
| 3043 | + ** CRLF-style: | |
| 3044 | + ** | |
| 3045 | + ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element | |
| 3046 | + ** | |
| 3047 | + ** More performant/efficient would be to offer a flag which says | |
| 3048 | + ** which newline form to use, converting the new copy (if needed) | |
| 3049 | + ** without having to examine the original. Since the primary use | |
| 3050 | + ** case is a web interface, it would be easy to offer it as a | |
| 3051 | + ** checkbox there. | |
| 3052 | + */ | |
| 3053 | + if((CIMINI_CONVERT_EOL & ciminiFlags) | |
| 3054 | + && zFilePrev!=0 | |
| 3055 | + && blob_size(&pCI->fileContent)>0){ | |
| 3056 | + const int pseudoBinary = LOOK_LONG | LOOK_NUL; | |
| 3057 | + const int lookFlags = LOOK_CRLF | pseudoBinary; | |
| 3058 | + const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags ); | |
| 3059 | + if(!(pseudoBinary & lookNew)){ | |
| 3060 | + Blob contentPrev = empty_blob; | |
| 3061 | + int lookOrig, nOrig; | |
| 3062 | + content_get(prevFRid, &contentPrev); | |
| 3063 | + lookOrig = looks_like_utf8(&contentPrev, lookFlags); | |
| 3064 | + nOrig = blob_size(&contentPrev); | |
| 3065 | + blob_reset(&contentPrev); | |
| 3066 | + if(nOrig>0 && lookOrig!=lookNew){ | |
| 3067 | + /* If there is a newline-style mismatch, adjust the new | |
| 3068 | + ** content version to the previous style, then re-hash the | |
| 3069 | + ** content. Note that this means that what we insert is NOT | |
| 3070 | + ** what's in the filesystem. | |
| 3071 | + */ | |
| 3072 | + int rehash = 0; | |
| 3073 | + if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){ | |
| 3074 | + /* Old has Unix-style, new has Windows-style. */ | |
| 3075 | + blob_to_lf_only(&pCI->fileContent); | |
| 3076 | + rehash = 1; | |
| 3077 | + }else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){ | |
| 3078 | + /* Old has Windows-style, new has Unix-style. */ | |
| 3079 | + blob_add_cr(&pCI->fileContent); | |
| 3080 | + rehash = 1; | |
| 3081 | + } | |
| 3082 | + if(rehash!=0){ | |
| 3083 | + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); | |
| 3084 | + } | |
| 3085 | + } | |
| 3086 | + } | |
| 3021 | 3087 | } |
| 3022 | 3088 | if(blob_size(&pCI->fileHash)==0){ |
| 3023 | 3089 | /* Hash the content if it's not done already... */ |
| 3024 | 3090 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 3025 | 3091 | assert(blob_size(&pCI->fileHash)>0); |
| 3026 | 3092 | } |
| 3027 | - if(zFile){ | |
| 3093 | + if(zFilePrev){ | |
| 3028 | 3094 | /* Has this file been changed since its previous commit? */ |
| 3029 | 3095 | assert(blob_size(&pCI->fileHash)); |
| 3030 | - if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ | |
| 3096 | + if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))){ | |
| 3031 | 3097 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3032 | 3098 | } |
| 3033 | - zFilePrevUuid = zFile->zUuid; | |
| 3034 | 3099 | } |
| 3035 | - /* Create the manifest... */ | |
| 3100 | + /* Create, save, deltify, and crosslink the manifest... */ | |
| 3036 | 3101 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3037 | 3102 | return 0; |
| 3038 | 3103 | } |
| 3039 | - /* Save and deltify the file content... */ | |
| 3040 | - frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), | |
| 3041 | - 0, 0, isPrivate); | |
| 3042 | - if(zFilePrevUuid!=0){ | |
| 3043 | - int prevFRid = fast_uuid_to_rid(zFilePrevUuid); | |
| 3044 | - assert(prevFRid>0); | |
| 3045 | - content_deltify(frid, &prevFRid, 1, 0); | |
| 3046 | - } | |
| 3047 | - /* Save, deltify, and crosslink the manifest... */ | |
| 3048 | 3104 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3049 | 3105 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3050 | 3106 | if(ciminiFlags & CIMINI_DUMP_MANIFEST){ |
| 3051 | 3107 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3052 | 3108 | } |
| 3053 | 3109 | manifest_crosslink(rid, &mf, 0); |
| 3054 | 3110 | blob_reset(&mf); |
| 3111 | + /* Save and deltify the file content... */ | |
| 3112 | + frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), | |
| 3113 | + 0, 0, isPrivate); | |
| 3114 | + if(zFilePrev!=0){ | |
| 3115 | + assert(prevFRid>0); | |
| 3116 | + content_deltify(frid, &prevFRid, 1, 0); | |
| 3117 | + } | |
| 3055 | 3118 | db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); |
| 3056 | 3119 | if(pRid!=0){ |
| 3057 | 3120 | *pRid = rid; |
| 3058 | 3121 | } |
| 3059 | 3122 | return 1; |
| @@ -3093,10 +3156,14 @@ | ||
| 3093 | 3156 | ** to contain a fossil merge conflict marker. |
| 3094 | 3157 | ** --user-override USER USER to use instead of the current default. |
| 3095 | 3158 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3096 | 3159 | ** --allow-older Allow a commit to be older than its |
| 3097 | 3160 | ** ancestor. |
| 3161 | +** --convert-eol Convert EOL style of the checkin to match | |
| 3162 | +** the previous version's content. Does not | |
| 3163 | +** modify the original file, only the | |
| 3164 | +** checked-in content. | |
| 3098 | 3165 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3099 | 3166 | ** --wet-run Disables the default dry-run mode. |
| 3100 | 3167 | ** |
| 3101 | 3168 | ** Example: |
| 3102 | 3169 | ** |
| @@ -3134,10 +3201,13 @@ | ||
| 3134 | 3201 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3135 | 3202 | ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3136 | 3203 | } |
| 3137 | 3204 | if(find_option("allow-older",0,0)!=0){ |
| 3138 | 3205 | ciminiFlags |= CIMINI_ALLOW_OLDER; |
| 3206 | + } | |
| 3207 | + if(find_option("convert-eol",0,0)!=0){ | |
| 3208 | + ciminiFlags |= CIMINI_CONVERT_EOL; | |
| 3139 | 3209 | } |
| 3140 | 3210 | |
| 3141 | 3211 | db_find_and_open_repository(0, 0); |
| 3142 | 3212 | verify_all_options(); |
| 3143 | 3213 | user_select(); |
| 3144 | 3214 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2765,19 +2765,28 @@ | |
| 2765 | ** which predates their parent. This flag bypasses that |
| 2766 | ** check. |
| 2767 | */ |
| 2768 | CIMINI_ALLOW_OLDER = 1<<4, |
| 2769 | |
| 2770 | /* |
| 2771 | ** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer |
| 2772 | ** creation of a delta manifest. |
| 2773 | */ |
| 2774 | CIMINI_PREFER_DELTA = 1<<5, |
| 2775 | /* |
| 2776 | ** NOT YET IMPLEMENTED. |
| 2777 | */ |
| 2778 | CIMINI_ALLOW_CLOSED_LEAF = 1<<6 |
| 2779 | }; |
| 2780 | |
| 2781 | /* |
| 2782 | ** Creates a manifest file, written to pOut, from the state in the |
| 2783 | ** fully-populated pCI argument. pCI is not *semantically* modified |
| @@ -2904,17 +2913,21 @@ | |
| 2904 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2905 | ** are unchanged. |
| 2906 | ** |
| 2907 | ** pCI may be modified as follows: |
| 2908 | ** |
| 2909 | ** - If pCI->fileHash is empty, this routine populates it with the |
| 2910 | ** repository's preferred hash algorithm. |
| 2911 | ** |
| 2912 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 2913 | ** string. If its original value cannot be validated then |
| 2914 | ** this function fails. If pCI->zDate is NULL, the current time |
| 2915 | ** is used. |
| 2916 | ** |
| 2917 | ** pCI's ownership is not modified. |
| 2918 | ** |
| 2919 | ** This function validates several of the inputs and fails if any |
| 2920 | ** validation fails. |
| @@ -2932,13 +2945,12 @@ | |
| 2932 | static int checkin_mini( CheckinMiniInfo * pCI, int *pRid, |
| 2933 | int ciminiFlags, Blob * pErr ){ |
| 2934 | Blob mf = empty_blob; /* output manifest */ |
| 2935 | int rid = 0, frid = 0; /* various RIDs */ |
| 2936 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2937 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2938 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| 2939 | the file */ |
| 2940 | |
| 2941 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 2942 | |
| 2943 | db_begin_transaction(); |
| 2944 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| @@ -3009,51 +3021,102 @@ | |
| 3009 | ** some such. That said, the remainder of this function is written |
| 3010 | ** as if this check did not exist, so enabling it "should" just be a |
| 3011 | ** matter of removing this check or guarding it with a flag. |
| 3012 | */ |
| 3013 | manifest_file_rewind(pCI->pParent); |
| 3014 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 3015 | if(!zFile){ |
| 3016 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3017 | "Adding new files is currently not allowed.", |
| 3018 | pCI->zFilename, pCI->zParentUuid)); |
| 3019 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 3020 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 3021 | } |
| 3022 | if(blob_size(&pCI->fileHash)==0){ |
| 3023 | /* Hash the content if it's not done already... */ |
| 3024 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 3025 | assert(blob_size(&pCI->fileHash)>0); |
| 3026 | } |
| 3027 | if(zFile){ |
| 3028 | /* Has this file been changed since its previous commit? */ |
| 3029 | assert(blob_size(&pCI->fileHash)); |
| 3030 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 3031 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3032 | } |
| 3033 | zFilePrevUuid = zFile->zUuid; |
| 3034 | } |
| 3035 | /* Create the manifest... */ |
| 3036 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3037 | return 0; |
| 3038 | } |
| 3039 | /* Save and deltify the file content... */ |
| 3040 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 3041 | 0, 0, isPrivate); |
| 3042 | if(zFilePrevUuid!=0){ |
| 3043 | int prevFRid = fast_uuid_to_rid(zFilePrevUuid); |
| 3044 | assert(prevFRid>0); |
| 3045 | content_deltify(frid, &prevFRid, 1, 0); |
| 3046 | } |
| 3047 | /* Save, deltify, and crosslink the manifest... */ |
| 3048 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3049 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3050 | if(ciminiFlags & CIMINI_DUMP_MANIFEST){ |
| 3051 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3052 | } |
| 3053 | manifest_crosslink(rid, &mf, 0); |
| 3054 | blob_reset(&mf); |
| 3055 | db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); |
| 3056 | if(pRid!=0){ |
| 3057 | *pRid = rid; |
| 3058 | } |
| 3059 | return 1; |
| @@ -3093,10 +3156,14 @@ | |
| 3093 | ** to contain a fossil merge conflict marker. |
| 3094 | ** --user-override USER USER to use instead of the current default. |
| 3095 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3096 | ** --allow-older Allow a commit to be older than its |
| 3097 | ** ancestor. |
| 3098 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3099 | ** --wet-run Disables the default dry-run mode. |
| 3100 | ** |
| 3101 | ** Example: |
| 3102 | ** |
| @@ -3134,10 +3201,13 @@ | |
| 3134 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3135 | ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3136 | } |
| 3137 | if(find_option("allow-older",0,0)!=0){ |
| 3138 | ciminiFlags |= CIMINI_ALLOW_OLDER; |
| 3139 | } |
| 3140 | |
| 3141 | db_find_and_open_repository(0, 0); |
| 3142 | verify_all_options(); |
| 3143 | user_select(); |
| 3144 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2765,19 +2765,28 @@ | |
| 2765 | ** which predates their parent. This flag bypasses that |
| 2766 | ** check. |
| 2767 | */ |
| 2768 | CIMINI_ALLOW_OLDER = 1<<4, |
| 2769 | |
| 2770 | /* |
| 2771 | ** Indicates that the content of the newly-checked-in file is |
| 2772 | ** converted, if needed, to use the same EOL style as the previous |
| 2773 | ** version of that file. Only the in-memory/in-repo copies are |
| 2774 | ** affected, not the original file (if any). |
| 2775 | */ |
| 2776 | CIMINI_CONVERT_EOL = 1<<5, |
| 2777 | |
| 2778 | /* |
| 2779 | ** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer |
| 2780 | ** creation of a delta manifest. |
| 2781 | */ |
| 2782 | CIMINI_PREFER_DELTA = 1<<6, |
| 2783 | |
| 2784 | /* |
| 2785 | ** NOT YET IMPLEMENTED. |
| 2786 | */ |
| 2787 | CIMINI_ALLOW_CLOSED_LEAF = 1<<7 |
| 2788 | }; |
| 2789 | |
| 2790 | /* |
| 2791 | ** Creates a manifest file, written to pOut, from the state in the |
| 2792 | ** fully-populated pCI argument. pCI is not *semantically* modified |
| @@ -2904,17 +2913,21 @@ | |
| 2913 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2914 | ** are unchanged. |
| 2915 | ** |
| 2916 | ** pCI may be modified as follows: |
| 2917 | ** |
| 2918 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 2919 | ** string. If its original value cannot be validated then |
| 2920 | ** this function fails. If pCI->zDate is NULL, the current time |
| 2921 | ** is used. |
| 2922 | ** |
| 2923 | ** - If pCI->fileContent is not binary and its line-ending style |
| 2924 | ** differs from its previous version, it is converted to the same |
| 2925 | ** EOL style. If this is done, the pCI->fileHash is re-computed. |
| 2926 | ** |
| 2927 | ** - If pCI->fileHash is empty, this routine populates it with the |
| 2928 | ** repository's preferred hash algorithm. |
| 2929 | ** |
| 2930 | ** pCI's ownership is not modified. |
| 2931 | ** |
| 2932 | ** This function validates several of the inputs and fails if any |
| 2933 | ** validation fails. |
| @@ -2932,13 +2945,12 @@ | |
| 2945 | static int checkin_mini( CheckinMiniInfo * pCI, int *pRid, |
| 2946 | int ciminiFlags, Blob * pErr ){ |
| 2947 | Blob mf = empty_blob; /* output manifest */ |
| 2948 | int rid = 0, frid = 0; /* various RIDs */ |
| 2949 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2950 | ManifestFile * zFilePrev; /* file entry from pCI->pParent */ |
| 2951 | int prevFRid = 0; /* RID of file's prev. version */ |
| 2952 | |
| 2953 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 2954 | |
| 2955 | db_begin_transaction(); |
| 2956 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| @@ -3009,51 +3021,102 @@ | |
| 3021 | ** some such. That said, the remainder of this function is written |
| 3022 | ** as if this check did not exist, so enabling it "should" just be a |
| 3023 | ** matter of removing this check or guarding it with a flag. |
| 3024 | */ |
| 3025 | manifest_file_rewind(pCI->pParent); |
| 3026 | zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 3027 | if(!zFilePrev){ |
| 3028 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3029 | "Adding new files is currently not allowed.", |
| 3030 | pCI->zFilename, pCI->zParentUuid)); |
| 3031 | }else if(zFilePrev->zPerm && strstr(zFilePrev->zPerm, "l")){ |
| 3032 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 3033 | } |
| 3034 | if(zFilePrev){ |
| 3035 | prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); |
| 3036 | } |
| 3037 | |
| 3038 | /* Confirm that the new content has the same EOL style as its |
| 3039 | ** predecessor and convert it, if needed, to the same style. Note |
| 3040 | ** that this inherently runs a risk of breaking content, e.g. string |
| 3041 | ** literals which contain embedded newlines. Note that HTML5 |
| 3042 | ** specifies that form-submitted TEXTAREA content gets normalized to |
| 3043 | ** CRLF-style: |
| 3044 | ** |
| 3045 | ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element |
| 3046 | ** |
| 3047 | ** More performant/efficient would be to offer a flag which says |
| 3048 | ** which newline form to use, converting the new copy (if needed) |
| 3049 | ** without having to examine the original. Since the primary use |
| 3050 | ** case is a web interface, it would be easy to offer it as a |
| 3051 | ** checkbox there. |
| 3052 | */ |
| 3053 | if((CIMINI_CONVERT_EOL & ciminiFlags) |
| 3054 | && zFilePrev!=0 |
| 3055 | && blob_size(&pCI->fileContent)>0){ |
| 3056 | const int pseudoBinary = LOOK_LONG | LOOK_NUL; |
| 3057 | const int lookFlags = LOOK_CRLF | pseudoBinary; |
| 3058 | const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags ); |
| 3059 | if(!(pseudoBinary & lookNew)){ |
| 3060 | Blob contentPrev = empty_blob; |
| 3061 | int lookOrig, nOrig; |
| 3062 | content_get(prevFRid, &contentPrev); |
| 3063 | lookOrig = looks_like_utf8(&contentPrev, lookFlags); |
| 3064 | nOrig = blob_size(&contentPrev); |
| 3065 | blob_reset(&contentPrev); |
| 3066 | if(nOrig>0 && lookOrig!=lookNew){ |
| 3067 | /* If there is a newline-style mismatch, adjust the new |
| 3068 | ** content version to the previous style, then re-hash the |
| 3069 | ** content. Note that this means that what we insert is NOT |
| 3070 | ** what's in the filesystem. |
| 3071 | */ |
| 3072 | int rehash = 0; |
| 3073 | if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){ |
| 3074 | /* Old has Unix-style, new has Windows-style. */ |
| 3075 | blob_to_lf_only(&pCI->fileContent); |
| 3076 | rehash = 1; |
| 3077 | }else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){ |
| 3078 | /* Old has Windows-style, new has Unix-style. */ |
| 3079 | blob_add_cr(&pCI->fileContent); |
| 3080 | rehash = 1; |
| 3081 | } |
| 3082 | if(rehash!=0){ |
| 3083 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 3084 | } |
| 3085 | } |
| 3086 | } |
| 3087 | } |
| 3088 | if(blob_size(&pCI->fileHash)==0){ |
| 3089 | /* Hash the content if it's not done already... */ |
| 3090 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 3091 | assert(blob_size(&pCI->fileHash)>0); |
| 3092 | } |
| 3093 | if(zFilePrev){ |
| 3094 | /* Has this file been changed since its previous commit? */ |
| 3095 | assert(blob_size(&pCI->fileHash)); |
| 3096 | if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))){ |
| 3097 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3098 | } |
| 3099 | } |
| 3100 | /* Create, save, deltify, and crosslink the manifest... */ |
| 3101 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3102 | return 0; |
| 3103 | } |
| 3104 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3105 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3106 | if(ciminiFlags & CIMINI_DUMP_MANIFEST){ |
| 3107 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3108 | } |
| 3109 | manifest_crosslink(rid, &mf, 0); |
| 3110 | blob_reset(&mf); |
| 3111 | /* Save and deltify the file content... */ |
| 3112 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 3113 | 0, 0, isPrivate); |
| 3114 | if(zFilePrev!=0){ |
| 3115 | assert(prevFRid>0); |
| 3116 | content_deltify(frid, &prevFRid, 1, 0); |
| 3117 | } |
| 3118 | db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); |
| 3119 | if(pRid!=0){ |
| 3120 | *pRid = rid; |
| 3121 | } |
| 3122 | return 1; |
| @@ -3093,10 +3156,14 @@ | |
| 3156 | ** to contain a fossil merge conflict marker. |
| 3157 | ** --user-override USER USER to use instead of the current default. |
| 3158 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3159 | ** --allow-older Allow a commit to be older than its |
| 3160 | ** ancestor. |
| 3161 | ** --convert-eol Convert EOL style of the checkin to match |
| 3162 | ** the previous version's content. Does not |
| 3163 | ** modify the original file, only the |
| 3164 | ** checked-in content. |
| 3165 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3166 | ** --wet-run Disables the default dry-run mode. |
| 3167 | ** |
| 3168 | ** Example: |
| 3169 | ** |
| @@ -3134,10 +3201,13 @@ | |
| 3201 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3202 | ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3203 | } |
| 3204 | if(find_option("allow-older",0,0)!=0){ |
| 3205 | ciminiFlags |= CIMINI_ALLOW_OLDER; |
| 3206 | } |
| 3207 | if(find_option("convert-eol",0,0)!=0){ |
| 3208 | ciminiFlags |= CIMINI_CONVERT_EOL; |
| 3209 | } |
| 3210 | |
| 3211 | db_find_and_open_repository(0, 0); |
| 3212 | verify_all_options(); |
| 3213 | user_select(); |
| 3214 |