Fossil SCM
Mini-checkin can now create deltas from deltas, inheriting the F-cards from the delta while using the same baseline as its parent delta. Honor the new forbid-delta-manifests config setting.
Commit
d8d9929c403eeed2cc14459033ad8ef84ae69878024f14a4aaec819fa5c643b2
Parent
58600bc1a59ced3…
1 file changed
+171
-84
+171
-84
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2796,10 +2796,17 @@ | ||
| 2796 | 2796 | ** A "stronger hint" to checkin_mini() to prefer creation of a delta |
| 2797 | 2797 | ** manifest if it at all can. It will decide not to only if creation |
| 2798 | 2798 | ** of a delta is not a realistic option. For this to work, it must be |
| 2799 | 2799 | ** set together with the CIMINI_PREFER_DELTA flag, but the two cannot |
| 2800 | 2800 | ** be combined in this enum. |
| 2801 | +** | |
| 2802 | +** This option is ONLY INTENDED FOR TESTING, used in bypassing | |
| 2803 | +** heuristics which may otherwise disable generation of a delta on the | |
| 2804 | +** grounds of efficiency (e.g. not generating a delta if the parent | |
| 2805 | +** non-delta only has a few F-cards). | |
| 2806 | +** | |
| 2807 | +** The forbid-delta-manifests repo config option trumps this. | |
| 2801 | 2808 | */ |
| 2802 | 2809 | CIMINI_STRONGLY_PREFER_DELTA = 1<<7, |
| 2803 | 2810 | /* |
| 2804 | 2811 | ** Tells checkin_mini() to permit the addition of a new file. Normally |
| 2805 | 2812 | ** this is disabled because there are many cases where it could cause |
| @@ -2806,109 +2813,184 @@ | ||
| 2806 | 2813 | ** the inadvertent addition of a new file when an update to an |
| 2807 | 2814 | ** existing was intended, as a side-effect of name-case differences. |
| 2808 | 2815 | */ |
| 2809 | 2816 | CIMINI_ALLOW_NEW_FILE = 1<<8 |
| 2810 | 2817 | }; |
| 2818 | + | |
| 2819 | +/* | |
| 2820 | +** Internal helper which returns an F-card perms string suitable for | |
| 2821 | +** writing into a manifest. | |
| 2822 | +*/ | |
| 2823 | +static const char * mfile_permint_mstring(int perm){ | |
| 2824 | + switch(perm){ | |
| 2825 | + case PERM_EXE: return " x"; | |
| 2826 | + case PERM_LNK: return " l"; | |
| 2827 | + default: return ""; | |
| 2828 | + } | |
| 2829 | +} | |
| 2830 | + | |
| 2831 | +static const char * mfile_perm_mstring(const ManifestFile * p){ | |
| 2832 | + return mfile_permint_mstring(manifest_file_mperm(p)); | |
| 2833 | +} | |
| 2811 | 2834 | |
| 2812 | 2835 | /* |
| 2813 | 2836 | ** Handles the F-card parts for create_manifest_mini(). |
| 2814 | 2837 | ** |
| 2815 | -** If asDelta is true, pCI->pParent MUST be a baseline, else an | |
| 2816 | -** assert() is triggered. Still TODO is creating a delta from | |
| 2817 | -** pCI->pParent when that object is itself a delta. | |
| 2838 | +** If asDelta is true, F-cards will be handled as for a delta | |
| 2839 | +** manifest, and the caller MUST have added a B-card to pOut before | |
| 2840 | +** calling this. | |
| 2818 | 2841 | ** |
| 2819 | 2842 | ** Returns 1 on success, 0 on error, and writes any error message to |
| 2820 | 2843 | ** pErr (if it's not NULL). |
| 2821 | 2844 | */ |
| 2822 | 2845 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2823 | 2846 | CheckinMiniInfo * pCI, |
| 2824 | 2847 | int asDelta, |
| 2825 | 2848 | Blob * pErr){ |
| 2826 | - ManifestFile *zFile; /* One file entry from the pCI->pParent*/ | |
| 2827 | - const char *zPerm = 0; /* permissions for new F-card */ | |
| 2849 | + ManifestFile *zFile; /* One file entry from pCI->pParent */ | |
| 2828 | 2850 | const char *zFilename = 0; /* filename for new F-card */ |
| 2829 | 2851 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2830 | 2852 | int cmp = 0; /* filename comparison result */ |
| 2853 | + int iFCursor = 0; /* Cursor into pCI->pParent->aFile if | |
| 2854 | + pCI->pParent is a delta. */ | |
| 2855 | + int postProcess = -1; /* How to traverse post-pCI->zFilename | |
| 2856 | + F-cards at the end of the function: | |
| 2857 | + 0=none, 1=normal traversal, | |
| 2858 | + 2=delta-clone tranversal. */ | |
| 2831 | 2859 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2832 | 2860 | filenames_are_case_sensitive() |
| 2833 | 2861 | ? fossil_strcmp |
| 2834 | 2862 | : fossil_stricmp; |
| 2835 | 2863 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2836 | 2864 | |
| 2837 | 2865 | assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); |
| 2838 | - assert(pCI->filePerm>=0 && "Must have been set by the caller."); | |
| 2839 | - | |
| 2840 | - manifest_file_rewind(pCI->pParent); | |
| 2841 | - if(asDelta){ | |
| 2842 | - /* Parent is a baseline and we have only 1 file to modify, so this | |
| 2843 | - ** is the simplest case... | |
| 2844 | - */ | |
| 2845 | - assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI."); | |
| 2846 | - zFile = manifest_file_find(pCI->pParent, pCI->zFilename); | |
| 2847 | - if(zFile==0){ | |
| 2848 | - /* New file */ | |
| 2849 | - zFilename = pCI->zFilename; | |
| 2850 | - }else{ | |
| 2851 | - /* Replacement file */ | |
| 2852 | - if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2853 | - goto err_no_symlink; | |
| 2854 | - } | |
| 2855 | - zFilename = zFile->zName | |
| 2856 | - /* use original name in case of name-case difference */; | |
| 2857 | - zFile = 0; | |
| 2858 | - } | |
| 2859 | - }else{ | |
| 2860 | - /* Non-delta: write F-cards which lexically preceed pCI->zFilename */ | |
| 2861 | - while((zFile = manifest_file_next(pCI->pParent, 0)) | |
| 2862 | - && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ | |
| 2863 | - blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, | |
| 2864 | - (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2865 | - (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2866 | - } | |
| 2867 | - /* Figure out file perms and name to save... */ | |
| 2868 | - if(zFile!=0 && cmp==0){ | |
| 2869 | - /* Match: override this F-card */ | |
| 2870 | - if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2871 | - goto err_no_symlink; | |
| 2872 | - } | |
| 2873 | - zFilename = zFile->zName | |
| 2874 | - /* use original name in case of name-case difference */; | |
| 2875 | - zFile = 0; | |
| 2876 | - }else{ | |
| 2877 | - /* This is a new file. */ | |
| 2878 | - assert(zFile==0); | |
| 2879 | - zFilename = pCI->zFilename; | |
| 2880 | - } | |
| 2881 | - } | |
| 2882 | - if(PERM_LNK==pCI->filePerm){ | |
| 2883 | - goto err_no_symlink; | |
| 2884 | - }else if(PERM_EXE==pCI->filePerm){ | |
| 2885 | - zPerm = " x"; | |
| 2886 | - }else{ | |
| 2887 | - zPerm = ""; | |
| 2888 | - } | |
| 2889 | - zUuid = blob_str(&pCI->fileHash); | |
| 2890 | - assert(zFile ? cmp>0&&asDelta==0 : 1); | |
| 2891 | - assert(zFilename); | |
| 2892 | - assert(zUuid); | |
| 2893 | - assert(zPerm); | |
| 2894 | - blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); | |
| 2895 | - /* Non-delta: write F-cards which lexically follow pCI->zFilename */ | |
| 2896 | - while(zFile!=0){ | |
| 2866 | + assert(pCI->filePerm==PERM_REG || pCI->filePerm==PERM_EXE); | |
| 2867 | + if(PERM_LNK==pCI->filePerm){ | |
| 2868 | + goto err_no_symlink; | |
| 2869 | + } | |
| 2870 | + manifest_file_rewind(pCI->pParent); | |
| 2871 | + if(asDelta){ | |
| 2872 | + if(pCI->pParent->zBaseline==0 || pCI->pParent->nFile==0 ){ | |
| 2873 | + /* Parent is a baseline or a delta with no F-cards, so this is | |
| 2874 | + ** the simplest case: create a delta with a single F-card. | |
| 2875 | + */ | |
| 2876 | + zFile = manifest_file_find(pCI->pParent, pCI->zFilename); | |
| 2877 | + if(zFile==0){/* New file */ | |
| 2878 | + zFilename = pCI->zFilename; | |
| 2879 | + }else{/* Replacement file */ | |
| 2880 | + if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2881 | + goto err_no_symlink; | |
| 2882 | + } | |
| 2883 | + zFilename = zFile->zName | |
| 2884 | + /* use original name in case of name-case difference */; | |
| 2885 | + } | |
| 2886 | + postProcess = 0; | |
| 2887 | + }else{ | |
| 2888 | + /* Parent is a delta manifest with F-cards. Traversal of delta | |
| 2889 | + ** manifest file entries is normally done via | |
| 2890 | + ** manifest_file_next(), which takes into account the | |
| 2891 | + ** differences between the delta and its parent and returns | |
| 2892 | + ** F-cards from both. Each successive delta from the same | |
| 2893 | + ** baseline includes all F-card changes from the previous | |
| 2894 | + ** deltas, so we instead "clone" the parent's F-cards except for | |
| 2895 | + ** the one (if any) which matches the new file. | |
| 2896 | + */ | |
| 2897 | + Manifest * p = pCI->pParent; | |
| 2898 | + cmp = -1; | |
| 2899 | + assert(p->nFile > 0); | |
| 2900 | + iFCursor = 0; | |
| 2901 | + zFile = &p->aFile[iFCursor]; | |
| 2902 | + /* Write F-cards which lexically preceed pCI->zFilename */ | |
| 2903 | + for( ; iFCursor<p->nFile; ){ | |
| 2904 | + zFile = &p->aFile[iFCursor]; | |
| 2905 | + cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 2906 | + if(cmp<0){ | |
| 2907 | + ++iFCursor; | |
| 2908 | + if(zFile->zUuid){ | |
| 2909 | + blob_appendf(pOut, "F %F %s%s\n", zFile->zName, | |
| 2910 | + zFile->zUuid, mfile_perm_mstring(zFile)); | |
| 2911 | + }else{/* File was removed in parent delta */ | |
| 2912 | + blob_appendf(pOut, "F %F\n", zFile->zName); | |
| 2913 | + } | |
| 2914 | + }else{ | |
| 2915 | + break; | |
| 2916 | + } | |
| 2917 | + } | |
| 2918 | + if(0==cmp){/* Match: override this F-card */ | |
| 2919 | + assert(zFile); | |
| 2920 | + if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2921 | + goto err_no_symlink; | |
| 2922 | + } | |
| 2923 | + ++iFCursor; | |
| 2924 | + zFilename = zFile->zName | |
| 2925 | + /* use original name in case of name-case difference */; | |
| 2926 | + }else{/* This is a new file */ | |
| 2927 | + zFilename = pCI->zFilename; | |
| 2928 | + } | |
| 2929 | + postProcess = 2; | |
| 2930 | + } | |
| 2931 | + }else{/* Non-delta: write F-cards which lexically preceed | |
| 2932 | + pCI->zFilename */ | |
| 2933 | + cmp = -1; | |
| 2934 | + while((zFile = manifest_file_next(pCI->pParent, 0)) | |
| 2935 | + && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ | |
| 2936 | + blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, | |
| 2937 | + mfile_perm_mstring(zFile)); | |
| 2938 | + } | |
| 2939 | + if(cmp==0){/* Match: override this F-card*/ | |
| 2940 | + if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2941 | + goto err_no_symlink; | |
| 2942 | + } | |
| 2943 | + zFilename = zFile->zName | |
| 2944 | + /* use original name in case of name-case difference */; | |
| 2945 | + }else{/* This is a new file. */ | |
| 2946 | + zFilename = pCI->zFilename; | |
| 2947 | + if(zFile!=0){ | |
| 2948 | + assert(cmp>0); | |
| 2949 | + assert(pCI->pParent->iFile>0); | |
| 2950 | + --pCI->pParent->iFile | |
| 2951 | + /* So that the post-processing loop picks up this file | |
| 2952 | + again.*/; | |
| 2953 | + } | |
| 2954 | + } | |
| 2955 | + postProcess = 1; | |
| 2956 | + } | |
| 2957 | + /* Finally add the new file's F-card... */ | |
| 2958 | + zFile = 0; | |
| 2959 | + zUuid = blob_str(&pCI->fileHash); | |
| 2960 | + assert(zFilename); | |
| 2961 | + assert(zUuid); | |
| 2962 | + assert(postProcess==0 || postProcess==1 || postProcess==2); | |
| 2963 | + blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, | |
| 2964 | + mfile_permint_mstring(pCI->filePerm)); | |
| 2965 | + while(postProcess>0){ | |
| 2966 | + /* Write F-cards which lexically follow pCI->zFilename */ | |
| 2967 | + if(postProcess==1){ /* non-delta parent */ | |
| 2968 | + zFile = manifest_file_next(pCI->pParent, 0); | |
| 2969 | + }else{ /* clone directly from delta parent */ | |
| 2970 | + zFile = iFCursor<pCI->pParent->nFile | |
| 2971 | + ? &pCI->pParent->aFile[iFCursor++] : 0; | |
| 2972 | + } | |
| 2973 | + if(zFile==0){ | |
| 2974 | + break; | |
| 2975 | + } | |
| 2897 | 2976 | #ifndef NDEBUG |
| 2898 | 2977 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2899 | 2978 | assert(cmp>0); |
| 2900 | 2979 | if(cmp<=0){ |
| 2901 | 2980 | mf_err((pErr,"Internal error: mis-ordering of " |
| 2902 | 2981 | "F-cards detected.")); |
| 2903 | 2982 | } |
| 2904 | 2983 | #endif |
| 2905 | - blob_appendf(pOut, "F %F %s%s%s\n", | |
| 2906 | - zFile->zName, zFile->zUuid, | |
| 2907 | - (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2908 | - (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2909 | - zFile = manifest_file_next(pCI->pParent, 0); | |
| 2984 | + if(zFile->zUuid){ | |
| 2985 | + blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, | |
| 2986 | + mfile_perm_mstring(zFile)); | |
| 2987 | + }else{ | |
| 2988 | + assert(postProcess==2); | |
| 2989 | + /* File was removed from parent delta. */ | |
| 2990 | + blob_appendf(pOut, "F %F\n", zFile->zName); | |
| 2991 | + } | |
| 2910 | 2992 | } |
| 2911 | 2993 | return 1; |
| 2912 | 2994 | err_no_symlink: |
| 2913 | 2995 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 2914 | 2996 | "via mini-checkin.")); |
| @@ -2951,23 +3033,21 @@ | ||
| 2951 | 3033 | ** sensible way to handle a symlink add/checkin without a |
| 2952 | 3034 | ** checkout. |
| 2953 | 3035 | */ |
| 2954 | 3036 | blob_zero(pOut); |
| 2955 | 3037 | manifest_file_rewind(pCI->pParent) /* force load of baseline */; |
| 2956 | - if(((CIMINI_PREFER_DELTA & pCI->flags) | |
| 2957 | - && pCI->pParent->zBaseline==0 /* parent is not a delta */ | |
| 2958 | - /* ^^^ TODO allow creation of a delta from a delta */ | |
| 2959 | - )&&( | |
| 2960 | - CIMINI_STRONGLY_PREFER_DELTA & pCI->flags | |
| 2961 | - || (pCI->pParent->pBaseline | |
| 2962 | - ? pCI->pParent->pBaseline | |
| 2963 | - : pCI->pParent)->nFile > 10 | |
| 2964 | - /* 10 is arbitrary: don't create a delta when there is only a | |
| 2965 | - ** tiny gain for doing so. */)){ | |
| 3038 | + | |
| 3039 | + if((CIMINI_PREFER_DELTA & pCI->flags) | |
| 3040 | + && ((CIMINI_STRONGLY_PREFER_DELTA & pCI->flags) | |
| 3041 | + || (pCI->pParent->pBaseline | |
| 3042 | + ? pCI->pParent->pBaseline | |
| 3043 | + : pCI->pParent)->nFile > 10 | |
| 3044 | + /* 10 is arbitrary: don't create a delta when there is only a | |
| 3045 | + ** tiny gain for doing so. */) | |
| 3046 | + && !db_get_boolean("forbid-delta-manifests",0) | |
| 3047 | + ){ | |
| 2966 | 3048 | asDelta = 1; |
| 2967 | - } | |
| 2968 | - if(asDelta){ | |
| 2969 | 3049 | blob_appendf(pOut, "B %s\n", |
| 2970 | 3050 | pCI->pParent->zBaseline |
| 2971 | 3051 | ? pCI->pParent->zBaseline |
| 2972 | 3052 | : pCI->zParentUuid); |
| 2973 | 3053 | } |
| @@ -3058,10 +3138,11 @@ | ||
| 3058 | 3138 | zProjCode)){ |
| 3059 | 3139 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| 3060 | 3140 | "in non-dry-run mode until it's been well-vetted. " |
| 3061 | 3141 | "Use a temp/test repo."); |
| 3062 | 3142 | } |
| 3143 | + fossil_free(zProjCode); | |
| 3063 | 3144 | } |
| 3064 | 3145 | db_begin_transaction(); |
| 3065 | 3146 | |
| 3066 | 3147 | if(pCI->pParent==0){ |
| 3067 | 3148 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| @@ -3140,11 +3221,14 @@ | ||
| 3140 | 3221 | ** case-sensitivity mismatch between the user/repo/filesystem, or |
| 3141 | 3222 | ** some such. |
| 3142 | 3223 | */ |
| 3143 | 3224 | manifest_file_rewind(pCI->pParent); |
| 3144 | 3225 | zFilePrev = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 3145 | - if(!zFilePrev && !(CIMINI_ALLOW_NEW_FILE & pCI->flags)){ | |
| 3226 | + if(!(CIMINI_ALLOW_NEW_FILE & pCI->flags) | |
| 3227 | + && (!zFilePrev | |
| 3228 | + || !zFilePrev->zUuid/*was removed from parent delta manifest*/) | |
| 3229 | + ){ | |
| 3146 | 3230 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3147 | 3231 | "Adding new files is currently not permitted.", |
| 3148 | 3232 | pCI->zFilename, pCI->zParentUuid)); |
| 3149 | 3233 | }else if(zFilePrev |
| 3150 | 3234 | && manifest_file_mperm(zFilePrev)==PERM_LNK){ |
| @@ -3210,11 +3294,12 @@ | ||
| 3210 | 3294 | assert(blob_size(&pCI->fileHash)>0); |
| 3211 | 3295 | } |
| 3212 | 3296 | if(zFilePrev){ |
| 3213 | 3297 | /* Has this file been changed since its previous commit? */ |
| 3214 | 3298 | assert(blob_size(&pCI->fileHash)); |
| 3215 | - if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))){ | |
| 3299 | + if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash)) | |
| 3300 | + && manifest_file_mperm(zFilePrev)==pCI->filePerm){ | |
| 3216 | 3301 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3217 | 3302 | } |
| 3218 | 3303 | } |
| 3219 | 3304 | /* Create, save, deltify, and crosslink the manifest... */ |
| 3220 | 3305 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| @@ -3284,11 +3369,13 @@ | ||
| 3284 | 3369 | ** --convert-eol Convert EOL style of the checkin to match |
| 3285 | 3370 | ** the previous version's content. Does not |
| 3286 | 3371 | ** modify the input file, only the checked-in |
| 3287 | 3372 | ** content. |
| 3288 | 3373 | ** --delta Prefer to generate a delta manifest, if |
| 3289 | -** able. | |
| 3374 | +** able. The forbid-delta-manifests repo | |
| 3375 | +** config option trumps this, as do certain | |
| 3376 | +** heuristics. | |
| 3290 | 3377 | ** --allow-new-file Allow addition of a new file this way. |
| 3291 | 3378 | ** Disabled by default to avoid that case- |
| 3292 | 3379 | ** sensitivity errors inadvertently lead to |
| 3293 | 3380 | ** adding a new file where an update is |
| 3294 | 3381 | ** intended. |
| 3295 | 3382 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2796,10 +2796,17 @@ | |
| 2796 | ** A "stronger hint" to checkin_mini() to prefer creation of a delta |
| 2797 | ** manifest if it at all can. It will decide not to only if creation |
| 2798 | ** of a delta is not a realistic option. For this to work, it must be |
| 2799 | ** set together with the CIMINI_PREFER_DELTA flag, but the two cannot |
| 2800 | ** be combined in this enum. |
| 2801 | */ |
| 2802 | CIMINI_STRONGLY_PREFER_DELTA = 1<<7, |
| 2803 | /* |
| 2804 | ** Tells checkin_mini() to permit the addition of a new file. Normally |
| 2805 | ** this is disabled because there are many cases where it could cause |
| @@ -2806,109 +2813,184 @@ | |
| 2806 | ** the inadvertent addition of a new file when an update to an |
| 2807 | ** existing was intended, as a side-effect of name-case differences. |
| 2808 | */ |
| 2809 | CIMINI_ALLOW_NEW_FILE = 1<<8 |
| 2810 | }; |
| 2811 | |
| 2812 | /* |
| 2813 | ** Handles the F-card parts for create_manifest_mini(). |
| 2814 | ** |
| 2815 | ** If asDelta is true, pCI->pParent MUST be a baseline, else an |
| 2816 | ** assert() is triggered. Still TODO is creating a delta from |
| 2817 | ** pCI->pParent when that object is itself a delta. |
| 2818 | ** |
| 2819 | ** Returns 1 on success, 0 on error, and writes any error message to |
| 2820 | ** pErr (if it's not NULL). |
| 2821 | */ |
| 2822 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2823 | CheckinMiniInfo * pCI, |
| 2824 | int asDelta, |
| 2825 | Blob * pErr){ |
| 2826 | ManifestFile *zFile; /* One file entry from the pCI->pParent*/ |
| 2827 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2828 | const char *zFilename = 0; /* filename for new F-card */ |
| 2829 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2830 | int cmp = 0; /* filename comparison result */ |
| 2831 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2832 | filenames_are_case_sensitive() |
| 2833 | ? fossil_strcmp |
| 2834 | : fossil_stricmp; |
| 2835 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2836 | |
| 2837 | assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); |
| 2838 | assert(pCI->filePerm>=0 && "Must have been set by the caller."); |
| 2839 | |
| 2840 | manifest_file_rewind(pCI->pParent); |
| 2841 | if(asDelta){ |
| 2842 | /* Parent is a baseline and we have only 1 file to modify, so this |
| 2843 | ** is the simplest case... |
| 2844 | */ |
| 2845 | assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI."); |
| 2846 | zFile = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 2847 | if(zFile==0){ |
| 2848 | /* New file */ |
| 2849 | zFilename = pCI->zFilename; |
| 2850 | }else{ |
| 2851 | /* Replacement file */ |
| 2852 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2853 | goto err_no_symlink; |
| 2854 | } |
| 2855 | zFilename = zFile->zName |
| 2856 | /* use original name in case of name-case difference */; |
| 2857 | zFile = 0; |
| 2858 | } |
| 2859 | }else{ |
| 2860 | /* Non-delta: write F-cards which lexically preceed pCI->zFilename */ |
| 2861 | while((zFile = manifest_file_next(pCI->pParent, 0)) |
| 2862 | && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ |
| 2863 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| 2864 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2865 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2866 | } |
| 2867 | /* Figure out file perms and name to save... */ |
| 2868 | if(zFile!=0 && cmp==0){ |
| 2869 | /* Match: override this F-card */ |
| 2870 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2871 | goto err_no_symlink; |
| 2872 | } |
| 2873 | zFilename = zFile->zName |
| 2874 | /* use original name in case of name-case difference */; |
| 2875 | zFile = 0; |
| 2876 | }else{ |
| 2877 | /* This is a new file. */ |
| 2878 | assert(zFile==0); |
| 2879 | zFilename = pCI->zFilename; |
| 2880 | } |
| 2881 | } |
| 2882 | if(PERM_LNK==pCI->filePerm){ |
| 2883 | goto err_no_symlink; |
| 2884 | }else if(PERM_EXE==pCI->filePerm){ |
| 2885 | zPerm = " x"; |
| 2886 | }else{ |
| 2887 | zPerm = ""; |
| 2888 | } |
| 2889 | zUuid = blob_str(&pCI->fileHash); |
| 2890 | assert(zFile ? cmp>0&&asDelta==0 : 1); |
| 2891 | assert(zFilename); |
| 2892 | assert(zUuid); |
| 2893 | assert(zPerm); |
| 2894 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2895 | /* Non-delta: write F-cards which lexically follow pCI->zFilename */ |
| 2896 | while(zFile!=0){ |
| 2897 | #ifndef NDEBUG |
| 2898 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2899 | assert(cmp>0); |
| 2900 | if(cmp<=0){ |
| 2901 | mf_err((pErr,"Internal error: mis-ordering of " |
| 2902 | "F-cards detected.")); |
| 2903 | } |
| 2904 | #endif |
| 2905 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2906 | zFile->zName, zFile->zUuid, |
| 2907 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2908 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2909 | zFile = manifest_file_next(pCI->pParent, 0); |
| 2910 | } |
| 2911 | return 1; |
| 2912 | err_no_symlink: |
| 2913 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 2914 | "via mini-checkin.")); |
| @@ -2951,23 +3033,21 @@ | |
| 2951 | ** sensible way to handle a symlink add/checkin without a |
| 2952 | ** checkout. |
| 2953 | */ |
| 2954 | blob_zero(pOut); |
| 2955 | manifest_file_rewind(pCI->pParent) /* force load of baseline */; |
| 2956 | if(((CIMINI_PREFER_DELTA & pCI->flags) |
| 2957 | && pCI->pParent->zBaseline==0 /* parent is not a delta */ |
| 2958 | /* ^^^ TODO allow creation of a delta from a delta */ |
| 2959 | )&&( |
| 2960 | CIMINI_STRONGLY_PREFER_DELTA & pCI->flags |
| 2961 | || (pCI->pParent->pBaseline |
| 2962 | ? pCI->pParent->pBaseline |
| 2963 | : pCI->pParent)->nFile > 10 |
| 2964 | /* 10 is arbitrary: don't create a delta when there is only a |
| 2965 | ** tiny gain for doing so. */)){ |
| 2966 | asDelta = 1; |
| 2967 | } |
| 2968 | if(asDelta){ |
| 2969 | blob_appendf(pOut, "B %s\n", |
| 2970 | pCI->pParent->zBaseline |
| 2971 | ? pCI->pParent->zBaseline |
| 2972 | : pCI->zParentUuid); |
| 2973 | } |
| @@ -3058,10 +3138,11 @@ | |
| 3058 | zProjCode)){ |
| 3059 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| 3060 | "in non-dry-run mode until it's been well-vetted. " |
| 3061 | "Use a temp/test repo."); |
| 3062 | } |
| 3063 | } |
| 3064 | db_begin_transaction(); |
| 3065 | |
| 3066 | if(pCI->pParent==0){ |
| 3067 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| @@ -3140,11 +3221,14 @@ | |
| 3140 | ** case-sensitivity mismatch between the user/repo/filesystem, or |
| 3141 | ** some such. |
| 3142 | */ |
| 3143 | manifest_file_rewind(pCI->pParent); |
| 3144 | zFilePrev = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 3145 | if(!zFilePrev && !(CIMINI_ALLOW_NEW_FILE & pCI->flags)){ |
| 3146 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3147 | "Adding new files is currently not permitted.", |
| 3148 | pCI->zFilename, pCI->zParentUuid)); |
| 3149 | }else if(zFilePrev |
| 3150 | && manifest_file_mperm(zFilePrev)==PERM_LNK){ |
| @@ -3210,11 +3294,12 @@ | |
| 3210 | assert(blob_size(&pCI->fileHash)>0); |
| 3211 | } |
| 3212 | if(zFilePrev){ |
| 3213 | /* Has this file been changed since its previous commit? */ |
| 3214 | assert(blob_size(&pCI->fileHash)); |
| 3215 | if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))){ |
| 3216 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3217 | } |
| 3218 | } |
| 3219 | /* Create, save, deltify, and crosslink the manifest... */ |
| 3220 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| @@ -3284,11 +3369,13 @@ | |
| 3284 | ** --convert-eol Convert EOL style of the checkin to match |
| 3285 | ** the previous version's content. Does not |
| 3286 | ** modify the input file, only the checked-in |
| 3287 | ** content. |
| 3288 | ** --delta Prefer to generate a delta manifest, if |
| 3289 | ** able. |
| 3290 | ** --allow-new-file Allow addition of a new file this way. |
| 3291 | ** Disabled by default to avoid that case- |
| 3292 | ** sensitivity errors inadvertently lead to |
| 3293 | ** adding a new file where an update is |
| 3294 | ** intended. |
| 3295 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2796,10 +2796,17 @@ | |
| 2796 | ** A "stronger hint" to checkin_mini() to prefer creation of a delta |
| 2797 | ** manifest if it at all can. It will decide not to only if creation |
| 2798 | ** of a delta is not a realistic option. For this to work, it must be |
| 2799 | ** set together with the CIMINI_PREFER_DELTA flag, but the two cannot |
| 2800 | ** be combined in this enum. |
| 2801 | ** |
| 2802 | ** This option is ONLY INTENDED FOR TESTING, used in bypassing |
| 2803 | ** heuristics which may otherwise disable generation of a delta on the |
| 2804 | ** grounds of efficiency (e.g. not generating a delta if the parent |
| 2805 | ** non-delta only has a few F-cards). |
| 2806 | ** |
| 2807 | ** The forbid-delta-manifests repo config option trumps this. |
| 2808 | */ |
| 2809 | CIMINI_STRONGLY_PREFER_DELTA = 1<<7, |
| 2810 | /* |
| 2811 | ** Tells checkin_mini() to permit the addition of a new file. Normally |
| 2812 | ** this is disabled because there are many cases where it could cause |
| @@ -2806,109 +2813,184 @@ | |
| 2813 | ** the inadvertent addition of a new file when an update to an |
| 2814 | ** existing was intended, as a side-effect of name-case differences. |
| 2815 | */ |
| 2816 | CIMINI_ALLOW_NEW_FILE = 1<<8 |
| 2817 | }; |
| 2818 | |
| 2819 | /* |
| 2820 | ** Internal helper which returns an F-card perms string suitable for |
| 2821 | ** writing into a manifest. |
| 2822 | */ |
| 2823 | static const char * mfile_permint_mstring(int perm){ |
| 2824 | switch(perm){ |
| 2825 | case PERM_EXE: return " x"; |
| 2826 | case PERM_LNK: return " l"; |
| 2827 | default: return ""; |
| 2828 | } |
| 2829 | } |
| 2830 | |
| 2831 | static const char * mfile_perm_mstring(const ManifestFile * p){ |
| 2832 | return mfile_permint_mstring(manifest_file_mperm(p)); |
| 2833 | } |
| 2834 | |
| 2835 | /* |
| 2836 | ** Handles the F-card parts for create_manifest_mini(). |
| 2837 | ** |
| 2838 | ** If asDelta is true, F-cards will be handled as for a delta |
| 2839 | ** manifest, and the caller MUST have added a B-card to pOut before |
| 2840 | ** calling this. |
| 2841 | ** |
| 2842 | ** Returns 1 on success, 0 on error, and writes any error message to |
| 2843 | ** pErr (if it's not NULL). |
| 2844 | */ |
| 2845 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2846 | CheckinMiniInfo * pCI, |
| 2847 | int asDelta, |
| 2848 | Blob * pErr){ |
| 2849 | ManifestFile *zFile; /* One file entry from pCI->pParent */ |
| 2850 | const char *zFilename = 0; /* filename for new F-card */ |
| 2851 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2852 | int cmp = 0; /* filename comparison result */ |
| 2853 | int iFCursor = 0; /* Cursor into pCI->pParent->aFile if |
| 2854 | pCI->pParent is a delta. */ |
| 2855 | int postProcess = -1; /* How to traverse post-pCI->zFilename |
| 2856 | F-cards at the end of the function: |
| 2857 | 0=none, 1=normal traversal, |
| 2858 | 2=delta-clone tranversal. */ |
| 2859 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2860 | filenames_are_case_sensitive() |
| 2861 | ? fossil_strcmp |
| 2862 | : fossil_stricmp; |
| 2863 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2864 | |
| 2865 | assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); |
| 2866 | assert(pCI->filePerm==PERM_REG || pCI->filePerm==PERM_EXE); |
| 2867 | if(PERM_LNK==pCI->filePerm){ |
| 2868 | goto err_no_symlink; |
| 2869 | } |
| 2870 | manifest_file_rewind(pCI->pParent); |
| 2871 | if(asDelta){ |
| 2872 | if(pCI->pParent->zBaseline==0 || pCI->pParent->nFile==0 ){ |
| 2873 | /* Parent is a baseline or a delta with no F-cards, so this is |
| 2874 | ** the simplest case: create a delta with a single F-card. |
| 2875 | */ |
| 2876 | zFile = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 2877 | if(zFile==0){/* New file */ |
| 2878 | zFilename = pCI->zFilename; |
| 2879 | }else{/* Replacement file */ |
| 2880 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2881 | goto err_no_symlink; |
| 2882 | } |
| 2883 | zFilename = zFile->zName |
| 2884 | /* use original name in case of name-case difference */; |
| 2885 | } |
| 2886 | postProcess = 0; |
| 2887 | }else{ |
| 2888 | /* Parent is a delta manifest with F-cards. Traversal of delta |
| 2889 | ** manifest file entries is normally done via |
| 2890 | ** manifest_file_next(), which takes into account the |
| 2891 | ** differences between the delta and its parent and returns |
| 2892 | ** F-cards from both. Each successive delta from the same |
| 2893 | ** baseline includes all F-card changes from the previous |
| 2894 | ** deltas, so we instead "clone" the parent's F-cards except for |
| 2895 | ** the one (if any) which matches the new file. |
| 2896 | */ |
| 2897 | Manifest * p = pCI->pParent; |
| 2898 | cmp = -1; |
| 2899 | assert(p->nFile > 0); |
| 2900 | iFCursor = 0; |
| 2901 | zFile = &p->aFile[iFCursor]; |
| 2902 | /* Write F-cards which lexically preceed pCI->zFilename */ |
| 2903 | for( ; iFCursor<p->nFile; ){ |
| 2904 | zFile = &p->aFile[iFCursor]; |
| 2905 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2906 | if(cmp<0){ |
| 2907 | ++iFCursor; |
| 2908 | if(zFile->zUuid){ |
| 2909 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, |
| 2910 | zFile->zUuid, mfile_perm_mstring(zFile)); |
| 2911 | }else{/* File was removed in parent delta */ |
| 2912 | blob_appendf(pOut, "F %F\n", zFile->zName); |
| 2913 | } |
| 2914 | }else{ |
| 2915 | break; |
| 2916 | } |
| 2917 | } |
| 2918 | if(0==cmp){/* Match: override this F-card */ |
| 2919 | assert(zFile); |
| 2920 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2921 | goto err_no_symlink; |
| 2922 | } |
| 2923 | ++iFCursor; |
| 2924 | zFilename = zFile->zName |
| 2925 | /* use original name in case of name-case difference */; |
| 2926 | }else{/* This is a new file */ |
| 2927 | zFilename = pCI->zFilename; |
| 2928 | } |
| 2929 | postProcess = 2; |
| 2930 | } |
| 2931 | }else{/* Non-delta: write F-cards which lexically preceed |
| 2932 | pCI->zFilename */ |
| 2933 | cmp = -1; |
| 2934 | while((zFile = manifest_file_next(pCI->pParent, 0)) |
| 2935 | && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ |
| 2936 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, |
| 2937 | mfile_perm_mstring(zFile)); |
| 2938 | } |
| 2939 | if(cmp==0){/* Match: override this F-card*/ |
| 2940 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2941 | goto err_no_symlink; |
| 2942 | } |
| 2943 | zFilename = zFile->zName |
| 2944 | /* use original name in case of name-case difference */; |
| 2945 | }else{/* This is a new file. */ |
| 2946 | zFilename = pCI->zFilename; |
| 2947 | if(zFile!=0){ |
| 2948 | assert(cmp>0); |
| 2949 | assert(pCI->pParent->iFile>0); |
| 2950 | --pCI->pParent->iFile |
| 2951 | /* So that the post-processing loop picks up this file |
| 2952 | again.*/; |
| 2953 | } |
| 2954 | } |
| 2955 | postProcess = 1; |
| 2956 | } |
| 2957 | /* Finally add the new file's F-card... */ |
| 2958 | zFile = 0; |
| 2959 | zUuid = blob_str(&pCI->fileHash); |
| 2960 | assert(zFilename); |
| 2961 | assert(zUuid); |
| 2962 | assert(postProcess==0 || postProcess==1 || postProcess==2); |
| 2963 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, |
| 2964 | mfile_permint_mstring(pCI->filePerm)); |
| 2965 | while(postProcess>0){ |
| 2966 | /* Write F-cards which lexically follow pCI->zFilename */ |
| 2967 | if(postProcess==1){ /* non-delta parent */ |
| 2968 | zFile = manifest_file_next(pCI->pParent, 0); |
| 2969 | }else{ /* clone directly from delta parent */ |
| 2970 | zFile = iFCursor<pCI->pParent->nFile |
| 2971 | ? &pCI->pParent->aFile[iFCursor++] : 0; |
| 2972 | } |
| 2973 | if(zFile==0){ |
| 2974 | break; |
| 2975 | } |
| 2976 | #ifndef NDEBUG |
| 2977 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2978 | assert(cmp>0); |
| 2979 | if(cmp<=0){ |
| 2980 | mf_err((pErr,"Internal error: mis-ordering of " |
| 2981 | "F-cards detected.")); |
| 2982 | } |
| 2983 | #endif |
| 2984 | if(zFile->zUuid){ |
| 2985 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, |
| 2986 | mfile_perm_mstring(zFile)); |
| 2987 | }else{ |
| 2988 | assert(postProcess==2); |
| 2989 | /* File was removed from parent delta. */ |
| 2990 | blob_appendf(pOut, "F %F\n", zFile->zName); |
| 2991 | } |
| 2992 | } |
| 2993 | return 1; |
| 2994 | err_no_symlink: |
| 2995 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 2996 | "via mini-checkin.")); |
| @@ -2951,23 +3033,21 @@ | |
| 3033 | ** sensible way to handle a symlink add/checkin without a |
| 3034 | ** checkout. |
| 3035 | */ |
| 3036 | blob_zero(pOut); |
| 3037 | manifest_file_rewind(pCI->pParent) /* force load of baseline */; |
| 3038 | |
| 3039 | if((CIMINI_PREFER_DELTA & pCI->flags) |
| 3040 | && ((CIMINI_STRONGLY_PREFER_DELTA & pCI->flags) |
| 3041 | || (pCI->pParent->pBaseline |
| 3042 | ? pCI->pParent->pBaseline |
| 3043 | : pCI->pParent)->nFile > 10 |
| 3044 | /* 10 is arbitrary: don't create a delta when there is only a |
| 3045 | ** tiny gain for doing so. */) |
| 3046 | && !db_get_boolean("forbid-delta-manifests",0) |
| 3047 | ){ |
| 3048 | asDelta = 1; |
| 3049 | blob_appendf(pOut, "B %s\n", |
| 3050 | pCI->pParent->zBaseline |
| 3051 | ? pCI->pParent->zBaseline |
| 3052 | : pCI->zParentUuid); |
| 3053 | } |
| @@ -3058,10 +3138,11 @@ | |
| 3138 | zProjCode)){ |
| 3139 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| 3140 | "in non-dry-run mode until it's been well-vetted. " |
| 3141 | "Use a temp/test repo."); |
| 3142 | } |
| 3143 | fossil_free(zProjCode); |
| 3144 | } |
| 3145 | db_begin_transaction(); |
| 3146 | |
| 3147 | if(pCI->pParent==0){ |
| 3148 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| @@ -3140,11 +3221,14 @@ | |
| 3221 | ** case-sensitivity mismatch between the user/repo/filesystem, or |
| 3222 | ** some such. |
| 3223 | */ |
| 3224 | manifest_file_rewind(pCI->pParent); |
| 3225 | zFilePrev = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 3226 | if(!(CIMINI_ALLOW_NEW_FILE & pCI->flags) |
| 3227 | && (!zFilePrev |
| 3228 | || !zFilePrev->zUuid/*was removed from parent delta manifest*/) |
| 3229 | ){ |
| 3230 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3231 | "Adding new files is currently not permitted.", |
| 3232 | pCI->zFilename, pCI->zParentUuid)); |
| 3233 | }else if(zFilePrev |
| 3234 | && manifest_file_mperm(zFilePrev)==PERM_LNK){ |
| @@ -3210,11 +3294,12 @@ | |
| 3294 | assert(blob_size(&pCI->fileHash)>0); |
| 3295 | } |
| 3296 | if(zFilePrev){ |
| 3297 | /* Has this file been changed since its previous commit? */ |
| 3298 | assert(blob_size(&pCI->fileHash)); |
| 3299 | if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash)) |
| 3300 | && manifest_file_mperm(zFilePrev)==pCI->filePerm){ |
| 3301 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3302 | } |
| 3303 | } |
| 3304 | /* Create, save, deltify, and crosslink the manifest... */ |
| 3305 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| @@ -3284,11 +3369,13 @@ | |
| 3369 | ** --convert-eol Convert EOL style of the checkin to match |
| 3370 | ** the previous version's content. Does not |
| 3371 | ** modify the input file, only the checked-in |
| 3372 | ** content. |
| 3373 | ** --delta Prefer to generate a delta manifest, if |
| 3374 | ** able. The forbid-delta-manifests repo |
| 3375 | ** config option trumps this, as do certain |
| 3376 | ** heuristics. |
| 3377 | ** --allow-new-file Allow addition of a new file this way. |
| 3378 | ** Disabled by default to avoid that case- |
| 3379 | ** sensitivity errors inadvertently lead to |
| 3380 | ** adding a new file where an update is |
| 3381 | ** intended. |
| 3382 |