Fossil SCM
Mini-checkin can now create a delta manifest if its parent is a baseline and a flag indicating a preference for delta manifests is set.
Commit
9699997444fd6d64cf6ad0c0132083acfe96c9d1d8442b2da05b96181fb52a9b
Parent
c06b292365aab13…
1 file changed
+188
-110
+188
-110
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2685,38 +2685,42 @@ | ||
| 2685 | 2685 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2686 | 2686 | } |
| 2687 | 2687 | } |
| 2688 | 2688 | |
| 2689 | 2689 | /* |
| 2690 | -** State for the "web-checkin" infrastructure, which enables the | |
| 2690 | +** State for the "mini-checkin" infrastructure, which enables the | |
| 2691 | 2691 | ** ability to commit changes to a single file via an HTTP request. |
| 2692 | 2692 | ** |
| 2693 | 2693 | ** Memory for all non-const (char *) members is owned by the |
| 2694 | 2694 | ** CheckinMiniInfo instance. |
| 2695 | 2695 | */ |
| 2696 | 2696 | struct CheckinMiniInfo { |
| 2697 | + Manifest * pParent; /* parent checkin */ | |
| 2698 | + char *zParentUuid; /* UUID of pParent */ | |
| 2697 | 2699 | Blob comment; /* Check-in comment text */ |
| 2698 | 2700 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2699 | - Manifest * pParent; /* parent checkin */ | |
| 2700 | - char *zParentUuid; /* UUID of pParent */ | |
| 2701 | 2701 | char *zUser; /* User name */ |
| 2702 | 2702 | char *zDate; /* Optionally force this date string |
| 2703 | 2703 | (anything supported by |
| 2704 | - date_in_standard_format(). | |
| 2704 | + date_in_standard_format()). | |
| 2705 | 2705 | Maybe be NULL. */ |
| 2706 | 2706 | char *zFilename; /* Name of single file to commit. Must be |
| 2707 | 2707 | relative to the top of the repo. */ |
| 2708 | 2708 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2709 | 2709 | Blob fileHash; /* Hash of this->fileContent, using the |
| 2710 | 2710 | repo's preferred hash method. */ |
| 2711 | + int flags; /* Bitmask of fossil_cimini_flags for | |
| 2712 | + communication from checkin_mini() to | |
| 2713 | + create_manifest_mini(). */ | |
| 2711 | 2714 | }; |
| 2712 | 2715 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2713 | 2716 | /* |
| 2714 | 2717 | ** Initializes p to a known-valid default state. |
| 2715 | 2718 | */ |
| 2716 | 2719 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2717 | 2720 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2721 | + p->flags = 0; | |
| 2718 | 2722 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2719 | 2723 | } |
| 2720 | 2724 | |
| 2721 | 2725 | /* |
| 2722 | 2726 | ** Frees all memory owned by p, but does not free p. |
| @@ -2738,10 +2742,11 @@ | ||
| 2738 | 2742 | |
| 2739 | 2743 | /* |
| 2740 | 2744 | ** Flags for checkin_mini() |
| 2741 | 2745 | */ |
| 2742 | 2746 | enum fossil_cimini_flags { |
| 2747 | +CIMINI_NONE = 0, | |
| 2743 | 2748 | /* |
| 2744 | 2749 | ** Tells checkin_mini() to use dry-run mode. |
| 2745 | 2750 | */ |
| 2746 | 2751 | CIMINI_DRY_RUN = 1, |
| 2747 | 2752 | /* |
| @@ -2774,12 +2779,11 @@ | ||
| 2774 | 2779 | ** affected, not the original file (if any). |
| 2775 | 2780 | */ |
| 2776 | 2781 | CIMINI_CONVERT_EOL = 1<<5, |
| 2777 | 2782 | |
| 2778 | 2783 | /* |
| 2779 | -** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer | |
| 2780 | -** creation of a delta manifest. | |
| 2784 | +** A hint to checkin_mini() to prefer creation of a delta manifest. | |
| 2781 | 2785 | */ |
| 2782 | 2786 | CIMINI_PREFER_DELTA = 1<<6, |
| 2783 | 2787 | |
| 2784 | 2788 | /* |
| 2785 | 2789 | ** NOT YET IMPLEMENTED. |
| @@ -2786,31 +2790,137 @@ | ||
| 2786 | 2790 | */ |
| 2787 | 2791 | CIMINI_ALLOW_CLOSED_LEAF = 1<<7 |
| 2788 | 2792 | }; |
| 2789 | 2793 | |
| 2790 | 2794 | /* |
| 2791 | -** Creates a manifest file, written to pOut, from the state in the | |
| 2792 | -** fully-populated pCI argument. pCI is not *semantically* modified | |
| 2793 | -** but cannot be const because blob_str() may need to NUL-terminate | |
| 2794 | -** any given blob. | |
| 2795 | +** Handles the F-card parts for create_manifest_mini(). | |
| 2796 | +** | |
| 2797 | +** If asDelta is true, pCI->pParent MUST be a baseline, else an | |
| 2798 | +** assert() is triggered. Still TODO is creating a delta from | |
| 2799 | +** pCI->pParent when that object is itself a delta. | |
| 2795 | 2800 | ** |
| 2796 | -** Returns true on success. On error, returns 0 and, if pErr is not | |
| 2797 | -** NULL, writes an error message there. | |
| 2801 | +** Returns 1 on success, 0 on error, and writes any error message to | |
| 2802 | +** pErr (if it's not NULL). | |
| 2798 | 2803 | */ |
| 2799 | -static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI, | |
| 2800 | - Blob * pErr){ | |
| 2801 | - Blob zCard = empty_blob; /* Z-card checksum */ | |
| 2804 | +static int create_manifest_mini_fcards( Blob * pOut, | |
| 2805 | + CheckinMiniInfo * pCI, | |
| 2806 | + int asDelta, | |
| 2807 | + Blob * pErr){ | |
| 2802 | 2808 | ManifestFile *zFile; /* One file entry from the pCI->pParent*/ |
| 2803 | - int cmp = -99; /* filename comparison result */ | |
| 2804 | 2809 | int fperm = 0; /* file permissions */ |
| 2805 | 2810 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2806 | - const char *zFilename = 0; /* filename to use for F-card */ | |
| 2807 | - const char *zUuid = 0; /* UUID for F-card */ | |
| 2811 | + const char *zFilename = 0; /* filename for new F-card */ | |
| 2812 | + const char *zUuid = 0; /* UUID for new F-card */ | |
| 2813 | + int cmp = 0; /* filename comparison result */ | |
| 2808 | 2814 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2809 | 2815 | filenames_are_case_sensitive() |
| 2810 | 2816 | ? fossil_strcmp |
| 2811 | 2817 | : fossil_stricmp; |
| 2818 | +#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 | |
| 2819 | + | |
| 2820 | + /* Potential TODOs: | |
| 2821 | + ** | |
| 2822 | + ** - When updating a file and the new one has the +x bit, add that | |
| 2823 | + ** bit if needed. We also need that logic in the upstream "has | |
| 2824 | + ** this file changed?" check. We currently always inherit the old | |
| 2825 | + ** perms. | |
| 2826 | + */ | |
| 2827 | + | |
| 2828 | + manifest_file_rewind(pCI->pParent); | |
| 2829 | + if(asDelta){ | |
| 2830 | + /* Parent is a baseline and we have only 1 file to modify, so this | |
| 2831 | + ** is the simplest case... | |
| 2832 | + */ | |
| 2833 | + assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI."); | |
| 2834 | + zFile = manifest_file_seek(pCI->pParent, pCI->zFilename,0); | |
| 2835 | + if(zFile==0){ | |
| 2836 | + /* New file */ | |
| 2837 | + fperm = file_perm(pCI->zFilename, ExtFILE); | |
| 2838 | + zFilename = pCI->zFilename; | |
| 2839 | + }else{ | |
| 2840 | + /* Replacement file */ | |
| 2841 | + fperm = manifest_file_mperm(zFile); | |
| 2842 | + zFilename = zFile->zName | |
| 2843 | + /* use original name in case of name-case difference */; | |
| 2844 | + zFile = 0; | |
| 2845 | + } | |
| 2846 | + }else{ | |
| 2847 | + /* Non-delta: write F-cards which lexically preceed pCI->zFilename */ | |
| 2848 | + while((zFile = manifest_file_next(pCI->pParent, 0))){ | |
| 2849 | + cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 2850 | + if(cmp<0){ | |
| 2851 | + blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, | |
| 2852 | + (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2853 | + (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2854 | + }else{ | |
| 2855 | + break; | |
| 2856 | + } | |
| 2857 | + } | |
| 2858 | + /* Figure out file perms and name to save... */ | |
| 2859 | + if(cmp==0){ | |
| 2860 | + /* Match: override this F-card */ | |
| 2861 | + fperm = manifest_file_mperm(zFile); | |
| 2862 | + zFilename = zFile->zName | |
| 2863 | + /* use original name in case of name-case difference */; | |
| 2864 | + zFile = 0; | |
| 2865 | + }else{ | |
| 2866 | + /* This is a new file. */ | |
| 2867 | + fperm = file_perm(pCI->zFilename, ExtFILE); | |
| 2868 | + zFilename = pCI->zFilename; | |
| 2869 | + } | |
| 2870 | + } | |
| 2871 | + assert(fperm!=PERM_LNK && "This should have been validated before."); | |
| 2872 | + if(PERM_LNK==fperm){ | |
| 2873 | + mf_err((pErr,"Cannot commit symlinks via mini-checkin.")); | |
| 2874 | + }else if(PERM_EXE==fperm){ | |
| 2875 | + zPerm = " x"; | |
| 2876 | + }else{ | |
| 2877 | + zPerm = ""; | |
| 2878 | + } | |
| 2879 | + zUuid = blob_str(&pCI->fileHash); | |
| 2880 | + assert(zFile ? cmp>0&&asDelta==0 : asDelta!=0||cmp==0); | |
| 2881 | + assert(zFilename); | |
| 2882 | + assert(zUuid); | |
| 2883 | + assert(zPerm); | |
| 2884 | + blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); | |
| 2885 | + /* Non-delta: write F-cards which lexically follow pCI->zFilename */ | |
| 2886 | + while(zFile!=0){ | |
| 2887 | +#ifndef NDEBUG | |
| 2888 | + cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 2889 | + assert(cmp>0); | |
| 2890 | + if(cmp<=0){ | |
| 2891 | + mf_err((pErr,"Internal error: mis-ordering of " | |
| 2892 | + "F-cards detected.")); | |
| 2893 | + } | |
| 2894 | +#endif | |
| 2895 | + blob_appendf(pOut, "F %F %s%s%s\n", | |
| 2896 | + zFile->zName, zFile->zUuid, | |
| 2897 | + (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2898 | + (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2899 | + zFile = manifest_file_next(pCI->pParent, 0); | |
| 2900 | + } | |
| 2901 | +#undef mf_err | |
| 2902 | + return 1; | |
| 2903 | +} | |
| 2904 | + | |
| 2905 | + | |
| 2906 | +/* | |
| 2907 | +** Creates a manifest file, written to pOut, from the state in the | |
| 2908 | +** fully-populated and semantically valid pCI argument. pCI is not | |
| 2909 | +** *semantically* modified but cannot be const because blob_str() may | |
| 2910 | +** need to NUL-terminate any given blob. | |
| 2911 | +** | |
| 2912 | +** Returns true on success. On error, returns 0 and, if pErr is not | |
| 2913 | +** NULL, writes an error message there. | |
| 2914 | +** | |
| 2915 | +** Intended only to be called via checkin_mini() or routines which | |
| 2916 | +** have already completely vetted pCI. | |
| 2917 | +*/ | |
| 2918 | +static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI, | |
| 2919 | + Blob * pErr){ | |
| 2920 | + Blob zCard = empty_blob; /* Z-card checksum */ | |
| 2921 | + int asDelta = 0; | |
| 2812 | 2922 | |
| 2813 | 2923 | assert(blob_str(&pCI->fileHash)); |
| 2814 | 2924 | assert(pCI->pParent); |
| 2815 | 2925 | assert(pCI->zFilename); |
| 2816 | 2926 | assert(pCI->zUser); |
| @@ -2825,65 +2935,26 @@ | ||
| 2825 | 2935 | ** told to handle a symlink because there would seem to be no(?) |
| 2826 | 2936 | ** sensible way to handle a symlink add/checkin without a |
| 2827 | 2937 | ** checkout. |
| 2828 | 2938 | */ |
| 2829 | 2939 | blob_zero(pOut); |
| 2940 | + if((pCI->flags & CIMINI_PREFER_DELTA) | |
| 2941 | + && pCI->pParent->zBaseline==0){ | |
| 2942 | + asDelta = 1; | |
| 2943 | + } | |
| 2944 | + if(asDelta){ | |
| 2945 | + blob_appendf(pOut, "B %s\n", pCI->zParentUuid); | |
| 2946 | + } | |
| 2830 | 2947 | if(blob_size(&pCI->comment)!=0){ |
| 2831 | 2948 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2832 | 2949 | }else{ |
| 2833 | 2950 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2834 | 2951 | } |
| 2835 | 2952 | blob_appendf(pOut, "D %z\n", pCI->zDate); |
| 2836 | - manifest_file_rewind(pCI->pParent); | |
| 2837 | - while((zFile = manifest_file_next(pCI->pParent, 0))){ | |
| 2838 | - cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 2839 | - if(cmp<0){ | |
| 2840 | - blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, | |
| 2841 | - (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2842 | - (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2843 | - }else{ | |
| 2844 | - break; | |
| 2845 | - } | |
| 2846 | - } | |
| 2847 | - if(cmp==0){ /* Match */ | |
| 2848 | - fperm = manifest_file_mperm(zFile); | |
| 2849 | - zFilename = zFile->zName | |
| 2850 | - /* use original name in case of name-case difference */; | |
| 2851 | - zUuid = blob_str(&pCI->fileHash); | |
| 2852 | - }else{ | |
| 2853 | - /* This is a new file. */ | |
| 2854 | - fperm = file_perm(pCI->zFilename, ExtFILE); | |
| 2855 | - zFilename = pCI->zFilename; | |
| 2856 | - zUuid = blob_str(&pCI->fileHash); | |
| 2857 | - if(cmp>0){ | |
| 2858 | - assert(zFile!=0); | |
| 2859 | - assert(pCI->pParent->iFile>0); | |
| 2860 | - --pCI->pParent->iFile | |
| 2861 | - /* so that the next step picks up that entry again */; | |
| 2862 | - } | |
| 2863 | - } | |
| 2864 | - if(PERM_LNK==fperm){ | |
| 2865 | - mf_err((pErr,"Cannot commit symlinks with this approach.")); | |
| 2866 | - }else if(PERM_EXE==fperm){ | |
| 2867 | - zPerm = " x"; | |
| 2868 | - }else{ | |
| 2869 | - zPerm = ""; | |
| 2870 | - } | |
| 2871 | - assert(zFilename); | |
| 2872 | - assert(zUuid); | |
| 2873 | - assert(zPerm); | |
| 2874 | - blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); | |
| 2875 | - while((zFile = manifest_file_next(pCI->pParent, 0))){ | |
| 2876 | - cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 2877 | - assert(cmp>0); | |
| 2878 | - if(cmp<=0){ | |
| 2879 | - mf_err((pErr,"Internal error: mis-ordering of F-cards detected.")); | |
| 2880 | - } | |
| 2881 | - blob_appendf(pOut, "F %F %s%s%s\n", | |
| 2882 | - zFile->zName, zFile->zUuid, | |
| 2883 | - (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2884 | - (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2953 | + | |
| 2954 | + if(!create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)){ | |
| 2955 | + return 0; | |
| 2885 | 2956 | } |
| 2886 | 2957 | |
| 2887 | 2958 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2888 | 2959 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2889 | 2960 | } |
| @@ -2936,16 +3007,15 @@ | ||
| 2936 | 3007 | ** diagnostic message there. |
| 2937 | 3008 | ** |
| 2938 | 3009 | ** Returns true on success. If pRid is not NULL, the RID of the |
| 2939 | 3010 | ** resulting manifest is written to *pRid. |
| 2940 | 3011 | ** |
| 2941 | -** ciminiFlags is a bitmask of optional flags from fossil_cimini_flags | |
| 2942 | -** enum. See that enum for the docs for each flag. Pass 0 for no | |
| 2943 | -** flags. | |
| 3012 | +** The checkin process is largely influenced by pCI->flags, and that | |
| 3013 | +** must be populated before calling this. See the fossil_cimini_flags | |
| 3014 | +** enum for the docs for each flag. | |
| 2944 | 3015 | */ |
| 2945 | -static int checkin_mini( CheckinMiniInfo * pCI, int *pRid, | |
| 2946 | - int ciminiFlags, Blob * pErr ){ | |
| 3016 | +static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){ | |
| 2947 | 3017 | Blob mf = empty_blob; /* output manifest */ |
| 2948 | 3018 | int rid = 0, frid = 0; /* various RIDs */ |
| 2949 | 3019 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2950 | 3020 | ManifestFile * zFilePrev; /* file entry from pCI->pParent */ |
| 2951 | 3021 | int prevFRid = 0; /* RID of file's prev. version */ |
| @@ -2964,20 +3034,24 @@ | ||
| 2964 | 3034 | ** no reason we can't do that via the generated manifest, |
| 2965 | 3035 | ** but the commit command does not offer that option, so |
| 2966 | 3036 | ** we won't, either. |
| 2967 | 3037 | */ |
| 2968 | 3038 | } |
| 2969 | - if(!(CIMINI_ALLOW_FORK & ciminiFlags) | |
| 3039 | + if(!(CIMINI_ALLOW_FORK & pCI->flags) | |
| 2970 | 3040 | && !is_a_leaf(pCI->pParent->rid)){ |
| 2971 | 3041 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 2972 | 3042 | pCI->zParentUuid)); |
| 2973 | 3043 | } |
| 2974 | - if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags) | |
| 3044 | + if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags) | |
| 2975 | 3045 | && contains_merge_marker(&pCI->fileContent)){ |
| 2976 | 3046 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 2977 | 3047 | } |
| 2978 | - | |
| 3048 | + if(!file_is_simple_pathname(pCI->zFilename, 1)){ | |
| 3049 | + ci_err((pErr,"Invalid filename for use in a repository: %s", | |
| 3050 | + pCI->zFilename)); | |
| 3051 | + } | |
| 3052 | + | |
| 2979 | 3053 | { |
| 2980 | 3054 | /* |
| 2981 | 3055 | ** Normalize the timestamp. We don't use date_in_standard_format() |
| 2982 | 3056 | ** because that has side-effects we don't want to trigger here. |
| 2983 | 3057 | */ |
| @@ -2989,11 +3063,11 @@ | ||
| 2989 | 3063 | ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); |
| 2990 | 3064 | } |
| 2991 | 3065 | fossil_free(pCI->zDate); |
| 2992 | 3066 | pCI->zDate = zDVal; |
| 2993 | 3067 | } |
| 2994 | - if(!(CIMINI_ALLOW_OLDER & ciminiFlags) | |
| 3068 | + if(!(CIMINI_ALLOW_OLDER & pCI->flags) | |
| 2995 | 3069 | && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ |
| 2996 | 3070 | ci_err((pErr,"Checkin time (%s) may not be older " |
| 2997 | 3071 | "than its parent (%z).", |
| 2998 | 3072 | pCI->zDate, |
| 2999 | 3073 | db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", |
| @@ -3024,37 +3098,38 @@ | ||
| 3024 | 3098 | */ |
| 3025 | 3099 | manifest_file_rewind(pCI->pParent); |
| 3026 | 3100 | zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 3027 | 3101 | if(!zFilePrev){ |
| 3028 | 3102 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3029 | - "Adding new files is currently not allowed.", | |
| 3103 | + "Adding new files is currently not permitted.", | |
| 3030 | 3104 | pCI->zFilename, pCI->zParentUuid)); |
| 3031 | - }else if(zFilePrev->zPerm && strstr(zFilePrev->zPerm, "l")){ | |
| 3032 | - ci_err((pErr,"Cannot save a symlink this way.")); | |
| 3105 | + }else if(zFilePrev->zPerm | |
| 3106 | + && manifest_file_mperm(zFilePrev)==PERM_LNK){ | |
| 3107 | + ci_err((pErr,"Cannot save a symlink via a mini-checkin.")); | |
| 3033 | 3108 | } |
| 3034 | 3109 | if(zFilePrev){ |
| 3035 | 3110 | prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); |
| 3036 | 3111 | } |
| 3037 | 3112 | |
| 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) | |
| 3113 | + if((CIMINI_CONVERT_EOL & pCI->flags) | |
| 3054 | 3114 | && zFilePrev!=0 |
| 3055 | 3115 | && blob_size(&pCI->fileContent)>0){ |
| 3116 | + /* Confirm that the new content has the same EOL style as its | |
| 3117 | + ** predecessor and convert it, if needed, to the same style. Note | |
| 3118 | + ** that this inherently runs a risk of breaking content, | |
| 3119 | + ** e.g. string literals which contain embedded newlines. Note that | |
| 3120 | + ** HTML5 specifies that form-submitted TEXTAREA content gets | |
| 3121 | + ** normalized to CRLF-style: | |
| 3122 | + ** | |
| 3123 | + ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element | |
| 3124 | + ** | |
| 3125 | + ** More performant/efficient would be to offer a flag which says | |
| 3126 | + ** which newline form to use, converting the new copy (if needed) | |
| 3127 | + ** without having to examine the original. Since the primary use | |
| 3128 | + ** case is a web interface, it would be easy to offer it as a | |
| 3129 | + ** checkbox there. | |
| 3130 | + */ | |
| 3056 | 3131 | const int pseudoBinary = LOOK_LONG | LOOK_NUL; |
| 3057 | 3132 | const int lookFlags = LOOK_CRLF | pseudoBinary; |
| 3058 | 3133 | const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags ); |
| 3059 | 3134 | if(!(pseudoBinary & lookNew)){ |
| 3060 | 3135 | Blob contentPrev = empty_blob; |
| @@ -3101,11 +3176,11 @@ | ||
| 3101 | 3176 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3102 | 3177 | return 0; |
| 3103 | 3178 | } |
| 3104 | 3179 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3105 | 3180 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3106 | - if(ciminiFlags & CIMINI_DUMP_MANIFEST){ | |
| 3181 | + if(pCI->flags & CIMINI_DUMP_MANIFEST){ | |
| 3107 | 3182 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3108 | 3183 | } |
| 3109 | 3184 | manifest_crosslink(rid, &mf, 0); |
| 3110 | 3185 | blob_reset(&mf); |
| 3111 | 3186 | /* Save and deltify the file content... */ |
| @@ -3113,11 +3188,11 @@ | ||
| 3113 | 3188 | 0, 0, isPrivate); |
| 3114 | 3189 | if(zFilePrev!=0){ |
| 3115 | 3190 | assert(prevFRid>0); |
| 3116 | 3191 | content_deltify(frid, &prevFRid, 1, 0); |
| 3117 | 3192 | } |
| 3118 | - db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); | |
| 3193 | + db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0); | |
| 3119 | 3194 | if(pRid!=0){ |
| 3120 | 3195 | *pRid = rid; |
| 3121 | 3196 | } |
| 3122 | 3197 | return 1; |
| 3123 | 3198 | ci_error: |
| @@ -3160,10 +3235,12 @@ | ||
| 3160 | 3235 | ** ancestor. |
| 3161 | 3236 | ** --convert-eol Convert EOL style of the checkin to match |
| 3162 | 3237 | ** the previous version's content. Does not |
| 3163 | 3238 | ** modify the original file, only the |
| 3164 | 3239 | ** checked-in content. |
| 3240 | +** --delta Prefer to generate a delta manifest, if | |
| 3241 | +** able. | |
| 3165 | 3242 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3166 | 3243 | ** --wet-run Disables the default dry-run mode. |
| 3167 | 3244 | ** |
| 3168 | 3245 | ** Example: |
| 3169 | 3246 | ** |
| @@ -3178,47 +3255,49 @@ | ||
| 3178 | 3255 | const char * zCommentFile; /* -M FILE */ |
| 3179 | 3256 | const char * zAsFilename; /* --as filename */ |
| 3180 | 3257 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3181 | 3258 | const char * zUser; /* --user-override */ |
| 3182 | 3259 | const char * zDate; /* --date-override */ |
| 3183 | - int ciminiFlags = 0; /* flags for checkin_mini(). */ | |
| 3184 | 3260 | |
| 3185 | 3261 | CheckinMiniInfo_init(&cinf); |
| 3186 | 3262 | zComment = find_option("comment","m",1); |
| 3187 | 3263 | zCommentFile = find_option("comment-file","M",1); |
| 3188 | 3264 | zAsFilename = find_option("as",0,1); |
| 3189 | 3265 | zRevision = find_option("revision","r",1); |
| 3190 | 3266 | zUser = find_option("user-override",0,1); |
| 3191 | 3267 | zDate = find_option("date-override",0,1); |
| 3192 | 3268 | if(find_option("wet-run",0,0)==0){ |
| 3193 | - ciminiFlags |= CIMINI_DRY_RUN; | |
| 3269 | + cinf.flags |= CIMINI_DRY_RUN; | |
| 3194 | 3270 | } |
| 3195 | 3271 | if(find_option("allow-fork",0,0)!=0){ |
| 3196 | - ciminiFlags |= CIMINI_ALLOW_FORK; | |
| 3272 | + cinf.flags |= CIMINI_ALLOW_FORK; | |
| 3197 | 3273 | } |
| 3198 | 3274 | if(find_option("dump-manifest","d",0)!=0){ |
| 3199 | - ciminiFlags |= CIMINI_DUMP_MANIFEST; | |
| 3275 | + cinf.flags |= CIMINI_DUMP_MANIFEST; | |
| 3200 | 3276 | } |
| 3201 | 3277 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3202 | - ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; | |
| 3278 | + cinf.flags |= CIMINI_ALLOW_MERGE_MARKER; | |
| 3203 | 3279 | } |
| 3204 | 3280 | if(find_option("allow-older",0,0)!=0){ |
| 3205 | - ciminiFlags |= CIMINI_ALLOW_OLDER; | |
| 3281 | + cinf.flags |= CIMINI_ALLOW_OLDER; | |
| 3206 | 3282 | } |
| 3207 | 3283 | if(find_option("convert-eol",0,0)!=0){ |
| 3208 | - ciminiFlags |= CIMINI_CONVERT_EOL; | |
| 3284 | + cinf.flags |= CIMINI_CONVERT_EOL; | |
| 3209 | 3285 | } |
| 3210 | - | |
| 3286 | + if(find_option("delta",0,0)!=0){ | |
| 3287 | + cinf.flags |= CIMINI_PREFER_DELTA; | |
| 3288 | + } | |
| 3211 | 3289 | db_find_and_open_repository(0, 0); |
| 3212 | 3290 | verify_all_options(); |
| 3213 | 3291 | user_select(); |
| 3214 | - | |
| 3215 | 3292 | if(g.argc!=3){ |
| 3216 | 3293 | usage("INFILE"); |
| 3217 | 3294 | } |
| 3218 | 3295 | |
| 3219 | - if(1){ | |
| 3296 | + if(!(cinf.flags & CIMINI_DRY_RUN)){ | |
| 3297 | + /* Until this feature is fully vetted, disallow it in the main | |
| 3298 | + ** fossil repo unless dry-run mode is being used. */ | |
| 3220 | 3299 | char * zProjCode = db_get("project-code",0); |
| 3221 | 3300 | assert(zProjCode); |
| 3222 | 3301 | if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", |
| 3223 | 3302 | zProjCode)){ |
| 3224 | 3303 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| @@ -3261,21 +3340,20 @@ | ||
| 3261 | 3340 | assert(cinf.pParent!=0); |
| 3262 | 3341 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3263 | 3342 | ExtFILE/*may want to reconsider*/); |
| 3264 | 3343 | { |
| 3265 | 3344 | Blob errMsg = empty_blob; |
| 3266 | - const int rc = checkin_mini(&cinf, &newRid, ciminiFlags, | |
| 3267 | - &errMsg); | |
| 3345 | + const int rc = checkin_mini(&cinf, &newRid, &errMsg); | |
| 3268 | 3346 | CheckinMiniInfo_cleanup(&cinf); |
| 3269 | 3347 | if(rc){ |
| 3270 | 3348 | assert(blob_size(&errMsg)==0); |
| 3271 | 3349 | }else{ |
| 3272 | 3350 | assert(blob_size(&errMsg)); |
| 3273 | 3351 | fossil_fatal("%b", &errMsg); |
| 3274 | 3352 | } |
| 3275 | 3353 | } |
| 3276 | - if(!(ciminiFlags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ | |
| 3354 | + if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ | |
| 3277 | 3355 | fossil_warning("The checkout state is now out of sync " |
| 3278 | 3356 | "with regards to this commit. It needs to be " |
| 3279 | 3357 | "'update'd or 'close'd and re-'open'ed."); |
| 3280 | 3358 | } |
| 3281 | 3359 | } |
| 3282 | 3360 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2685,38 +2685,42 @@ | |
| 2685 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2686 | } |
| 2687 | } |
| 2688 | |
| 2689 | /* |
| 2690 | ** State for the "web-checkin" infrastructure, which enables the |
| 2691 | ** ability to commit changes to a single file via an HTTP request. |
| 2692 | ** |
| 2693 | ** Memory for all non-const (char *) members is owned by the |
| 2694 | ** CheckinMiniInfo instance. |
| 2695 | */ |
| 2696 | struct CheckinMiniInfo { |
| 2697 | Blob comment; /* Check-in comment text */ |
| 2698 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2699 | Manifest * pParent; /* parent checkin */ |
| 2700 | char *zParentUuid; /* UUID of pParent */ |
| 2701 | char *zUser; /* User name */ |
| 2702 | char *zDate; /* Optionally force this date string |
| 2703 | (anything supported by |
| 2704 | date_in_standard_format(). |
| 2705 | Maybe be NULL. */ |
| 2706 | char *zFilename; /* Name of single file to commit. Must be |
| 2707 | relative to the top of the repo. */ |
| 2708 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2709 | Blob fileHash; /* Hash of this->fileContent, using the |
| 2710 | repo's preferred hash method. */ |
| 2711 | }; |
| 2712 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2713 | /* |
| 2714 | ** Initializes p to a known-valid default state. |
| 2715 | */ |
| 2716 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2717 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2718 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2719 | } |
| 2720 | |
| 2721 | /* |
| 2722 | ** Frees all memory owned by p, but does not free p. |
| @@ -2738,10 +2742,11 @@ | |
| 2738 | |
| 2739 | /* |
| 2740 | ** Flags for checkin_mini() |
| 2741 | */ |
| 2742 | enum fossil_cimini_flags { |
| 2743 | /* |
| 2744 | ** Tells checkin_mini() to use dry-run mode. |
| 2745 | */ |
| 2746 | CIMINI_DRY_RUN = 1, |
| 2747 | /* |
| @@ -2774,12 +2779,11 @@ | |
| 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,31 +2790,137 @@ | |
| 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 |
| 2793 | ** but cannot be const because blob_str() may need to NUL-terminate |
| 2794 | ** any given blob. |
| 2795 | ** |
| 2796 | ** Returns true on success. On error, returns 0 and, if pErr is not |
| 2797 | ** NULL, writes an error message there. |
| 2798 | */ |
| 2799 | static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI, |
| 2800 | Blob * pErr){ |
| 2801 | Blob zCard = empty_blob; /* Z-card checksum */ |
| 2802 | ManifestFile *zFile; /* One file entry from the pCI->pParent*/ |
| 2803 | int cmp = -99; /* filename comparison result */ |
| 2804 | int fperm = 0; /* file permissions */ |
| 2805 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2806 | const char *zFilename = 0; /* filename to use for F-card */ |
| 2807 | const char *zUuid = 0; /* UUID for F-card */ |
| 2808 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2809 | filenames_are_case_sensitive() |
| 2810 | ? fossil_strcmp |
| 2811 | : fossil_stricmp; |
| 2812 | |
| 2813 | assert(blob_str(&pCI->fileHash)); |
| 2814 | assert(pCI->pParent); |
| 2815 | assert(pCI->zFilename); |
| 2816 | assert(pCI->zUser); |
| @@ -2825,65 +2935,26 @@ | |
| 2825 | ** told to handle a symlink because there would seem to be no(?) |
| 2826 | ** sensible way to handle a symlink add/checkin without a |
| 2827 | ** checkout. |
| 2828 | */ |
| 2829 | blob_zero(pOut); |
| 2830 | if(blob_size(&pCI->comment)!=0){ |
| 2831 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2832 | }else{ |
| 2833 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2834 | } |
| 2835 | blob_appendf(pOut, "D %z\n", pCI->zDate); |
| 2836 | manifest_file_rewind(pCI->pParent); |
| 2837 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2838 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2839 | if(cmp<0){ |
| 2840 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| 2841 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2842 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2843 | }else{ |
| 2844 | break; |
| 2845 | } |
| 2846 | } |
| 2847 | if(cmp==0){ /* Match */ |
| 2848 | fperm = manifest_file_mperm(zFile); |
| 2849 | zFilename = zFile->zName |
| 2850 | /* use original name in case of name-case difference */; |
| 2851 | zUuid = blob_str(&pCI->fileHash); |
| 2852 | }else{ |
| 2853 | /* This is a new file. */ |
| 2854 | fperm = file_perm(pCI->zFilename, ExtFILE); |
| 2855 | zFilename = pCI->zFilename; |
| 2856 | zUuid = blob_str(&pCI->fileHash); |
| 2857 | if(cmp>0){ |
| 2858 | assert(zFile!=0); |
| 2859 | assert(pCI->pParent->iFile>0); |
| 2860 | --pCI->pParent->iFile |
| 2861 | /* so that the next step picks up that entry again */; |
| 2862 | } |
| 2863 | } |
| 2864 | if(PERM_LNK==fperm){ |
| 2865 | mf_err((pErr,"Cannot commit symlinks with this approach.")); |
| 2866 | }else if(PERM_EXE==fperm){ |
| 2867 | zPerm = " x"; |
| 2868 | }else{ |
| 2869 | zPerm = ""; |
| 2870 | } |
| 2871 | assert(zFilename); |
| 2872 | assert(zUuid); |
| 2873 | assert(zPerm); |
| 2874 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2875 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2876 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2877 | assert(cmp>0); |
| 2878 | if(cmp<=0){ |
| 2879 | mf_err((pErr,"Internal error: mis-ordering of F-cards detected.")); |
| 2880 | } |
| 2881 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2882 | zFile->zName, zFile->zUuid, |
| 2883 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2884 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2885 | } |
| 2886 | |
| 2887 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2888 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2889 | } |
| @@ -2936,16 +3007,15 @@ | |
| 2936 | ** diagnostic message there. |
| 2937 | ** |
| 2938 | ** Returns true on success. If pRid is not NULL, the RID of the |
| 2939 | ** resulting manifest is written to *pRid. |
| 2940 | ** |
| 2941 | ** ciminiFlags is a bitmask of optional flags from fossil_cimini_flags |
| 2942 | ** enum. See that enum for the docs for each flag. Pass 0 for no |
| 2943 | ** flags. |
| 2944 | */ |
| 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 */ |
| @@ -2964,20 +3034,24 @@ | |
| 2964 | ** no reason we can't do that via the generated manifest, |
| 2965 | ** but the commit command does not offer that option, so |
| 2966 | ** we won't, either. |
| 2967 | */ |
| 2968 | } |
| 2969 | if(!(CIMINI_ALLOW_FORK & ciminiFlags) |
| 2970 | && !is_a_leaf(pCI->pParent->rid)){ |
| 2971 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 2972 | pCI->zParentUuid)); |
| 2973 | } |
| 2974 | if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags) |
| 2975 | && contains_merge_marker(&pCI->fileContent)){ |
| 2976 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 2977 | } |
| 2978 | |
| 2979 | { |
| 2980 | /* |
| 2981 | ** Normalize the timestamp. We don't use date_in_standard_format() |
| 2982 | ** because that has side-effects we don't want to trigger here. |
| 2983 | */ |
| @@ -2989,11 +3063,11 @@ | |
| 2989 | ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); |
| 2990 | } |
| 2991 | fossil_free(pCI->zDate); |
| 2992 | pCI->zDate = zDVal; |
| 2993 | } |
| 2994 | if(!(CIMINI_ALLOW_OLDER & ciminiFlags) |
| 2995 | && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ |
| 2996 | ci_err((pErr,"Checkin time (%s) may not be older " |
| 2997 | "than its parent (%z).", |
| 2998 | pCI->zDate, |
| 2999 | db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", |
| @@ -3024,37 +3098,38 @@ | |
| 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; |
| @@ -3101,11 +3176,11 @@ | |
| 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... */ |
| @@ -3113,11 +3188,11 @@ | |
| 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; |
| 3123 | ci_error: |
| @@ -3160,10 +3235,12 @@ | |
| 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 | ** |
| @@ -3178,47 +3255,49 @@ | |
| 3178 | const char * zCommentFile; /* -M FILE */ |
| 3179 | const char * zAsFilename; /* --as filename */ |
| 3180 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3181 | const char * zUser; /* --user-override */ |
| 3182 | const char * zDate; /* --date-override */ |
| 3183 | int ciminiFlags = 0; /* flags for checkin_mini(). */ |
| 3184 | |
| 3185 | CheckinMiniInfo_init(&cinf); |
| 3186 | zComment = find_option("comment","m",1); |
| 3187 | zCommentFile = find_option("comment-file","M",1); |
| 3188 | zAsFilename = find_option("as",0,1); |
| 3189 | zRevision = find_option("revision","r",1); |
| 3190 | zUser = find_option("user-override",0,1); |
| 3191 | zDate = find_option("date-override",0,1); |
| 3192 | if(find_option("wet-run",0,0)==0){ |
| 3193 | ciminiFlags |= CIMINI_DRY_RUN; |
| 3194 | } |
| 3195 | if(find_option("allow-fork",0,0)!=0){ |
| 3196 | ciminiFlags |= CIMINI_ALLOW_FORK; |
| 3197 | } |
| 3198 | if(find_option("dump-manifest","d",0)!=0){ |
| 3199 | ciminiFlags |= CIMINI_DUMP_MANIFEST; |
| 3200 | } |
| 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 | |
| 3215 | if(g.argc!=3){ |
| 3216 | usage("INFILE"); |
| 3217 | } |
| 3218 | |
| 3219 | if(1){ |
| 3220 | char * zProjCode = db_get("project-code",0); |
| 3221 | assert(zProjCode); |
| 3222 | if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", |
| 3223 | zProjCode)){ |
| 3224 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| @@ -3261,21 +3340,20 @@ | |
| 3261 | assert(cinf.pParent!=0); |
| 3262 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3263 | ExtFILE/*may want to reconsider*/); |
| 3264 | { |
| 3265 | Blob errMsg = empty_blob; |
| 3266 | const int rc = checkin_mini(&cinf, &newRid, ciminiFlags, |
| 3267 | &errMsg); |
| 3268 | CheckinMiniInfo_cleanup(&cinf); |
| 3269 | if(rc){ |
| 3270 | assert(blob_size(&errMsg)==0); |
| 3271 | }else{ |
| 3272 | assert(blob_size(&errMsg)); |
| 3273 | fossil_fatal("%b", &errMsg); |
| 3274 | } |
| 3275 | } |
| 3276 | if(!(ciminiFlags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3277 | fossil_warning("The checkout state is now out of sync " |
| 3278 | "with regards to this commit. It needs to be " |
| 3279 | "'update'd or 'close'd and re-'open'ed."); |
| 3280 | } |
| 3281 | } |
| 3282 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2685,38 +2685,42 @@ | |
| 2685 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2686 | } |
| 2687 | } |
| 2688 | |
| 2689 | /* |
| 2690 | ** State for the "mini-checkin" infrastructure, which enables the |
| 2691 | ** ability to commit changes to a single file via an HTTP request. |
| 2692 | ** |
| 2693 | ** Memory for all non-const (char *) members is owned by the |
| 2694 | ** CheckinMiniInfo instance. |
| 2695 | */ |
| 2696 | struct CheckinMiniInfo { |
| 2697 | Manifest * pParent; /* parent checkin */ |
| 2698 | char *zParentUuid; /* UUID of pParent */ |
| 2699 | Blob comment; /* Check-in comment text */ |
| 2700 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2701 | char *zUser; /* User name */ |
| 2702 | char *zDate; /* Optionally force this date string |
| 2703 | (anything supported by |
| 2704 | date_in_standard_format()). |
| 2705 | Maybe be NULL. */ |
| 2706 | char *zFilename; /* Name of single file to commit. Must be |
| 2707 | relative to the top of the repo. */ |
| 2708 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2709 | Blob fileHash; /* Hash of this->fileContent, using the |
| 2710 | repo's preferred hash method. */ |
| 2711 | int flags; /* Bitmask of fossil_cimini_flags for |
| 2712 | communication from checkin_mini() to |
| 2713 | create_manifest_mini(). */ |
| 2714 | }; |
| 2715 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2716 | /* |
| 2717 | ** Initializes p to a known-valid default state. |
| 2718 | */ |
| 2719 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2720 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2721 | p->flags = 0; |
| 2722 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2723 | } |
| 2724 | |
| 2725 | /* |
| 2726 | ** Frees all memory owned by p, but does not free p. |
| @@ -2738,10 +2742,11 @@ | |
| 2742 | |
| 2743 | /* |
| 2744 | ** Flags for checkin_mini() |
| 2745 | */ |
| 2746 | enum fossil_cimini_flags { |
| 2747 | CIMINI_NONE = 0, |
| 2748 | /* |
| 2749 | ** Tells checkin_mini() to use dry-run mode. |
| 2750 | */ |
| 2751 | CIMINI_DRY_RUN = 1, |
| 2752 | /* |
| @@ -2774,12 +2779,11 @@ | |
| 2779 | ** affected, not the original file (if any). |
| 2780 | */ |
| 2781 | CIMINI_CONVERT_EOL = 1<<5, |
| 2782 | |
| 2783 | /* |
| 2784 | ** A hint to checkin_mini() to prefer creation of a delta manifest. |
| 2785 | */ |
| 2786 | CIMINI_PREFER_DELTA = 1<<6, |
| 2787 | |
| 2788 | /* |
| 2789 | ** NOT YET IMPLEMENTED. |
| @@ -2786,31 +2790,137 @@ | |
| 2790 | */ |
| 2791 | CIMINI_ALLOW_CLOSED_LEAF = 1<<7 |
| 2792 | }; |
| 2793 | |
| 2794 | /* |
| 2795 | ** Handles the F-card parts for create_manifest_mini(). |
| 2796 | ** |
| 2797 | ** If asDelta is true, pCI->pParent MUST be a baseline, else an |
| 2798 | ** assert() is triggered. Still TODO is creating a delta from |
| 2799 | ** pCI->pParent when that object is itself a delta. |
| 2800 | ** |
| 2801 | ** Returns 1 on success, 0 on error, and writes any error message to |
| 2802 | ** pErr (if it's not NULL). |
| 2803 | */ |
| 2804 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2805 | CheckinMiniInfo * pCI, |
| 2806 | int asDelta, |
| 2807 | Blob * pErr){ |
| 2808 | ManifestFile *zFile; /* One file entry from the pCI->pParent*/ |
| 2809 | int fperm = 0; /* file permissions */ |
| 2810 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2811 | const char *zFilename = 0; /* filename for new F-card */ |
| 2812 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2813 | int cmp = 0; /* filename comparison result */ |
| 2814 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2815 | filenames_are_case_sensitive() |
| 2816 | ? fossil_strcmp |
| 2817 | : fossil_stricmp; |
| 2818 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2819 | |
| 2820 | /* Potential TODOs: |
| 2821 | ** |
| 2822 | ** - When updating a file and the new one has the +x bit, add that |
| 2823 | ** bit if needed. We also need that logic in the upstream "has |
| 2824 | ** this file changed?" check. We currently always inherit the old |
| 2825 | ** perms. |
| 2826 | */ |
| 2827 | |
| 2828 | manifest_file_rewind(pCI->pParent); |
| 2829 | if(asDelta){ |
| 2830 | /* Parent is a baseline and we have only 1 file to modify, so this |
| 2831 | ** is the simplest case... |
| 2832 | */ |
| 2833 | assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI."); |
| 2834 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename,0); |
| 2835 | if(zFile==0){ |
| 2836 | /* New file */ |
| 2837 | fperm = file_perm(pCI->zFilename, ExtFILE); |
| 2838 | zFilename = pCI->zFilename; |
| 2839 | }else{ |
| 2840 | /* Replacement file */ |
| 2841 | fperm = manifest_file_mperm(zFile); |
| 2842 | zFilename = zFile->zName |
| 2843 | /* use original name in case of name-case difference */; |
| 2844 | zFile = 0; |
| 2845 | } |
| 2846 | }else{ |
| 2847 | /* Non-delta: write F-cards which lexically preceed pCI->zFilename */ |
| 2848 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2849 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2850 | if(cmp<0){ |
| 2851 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| 2852 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2853 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2854 | }else{ |
| 2855 | break; |
| 2856 | } |
| 2857 | } |
| 2858 | /* Figure out file perms and name to save... */ |
| 2859 | if(cmp==0){ |
| 2860 | /* Match: override this F-card */ |
| 2861 | fperm = manifest_file_mperm(zFile); |
| 2862 | zFilename = zFile->zName |
| 2863 | /* use original name in case of name-case difference */; |
| 2864 | zFile = 0; |
| 2865 | }else{ |
| 2866 | /* This is a new file. */ |
| 2867 | fperm = file_perm(pCI->zFilename, ExtFILE); |
| 2868 | zFilename = pCI->zFilename; |
| 2869 | } |
| 2870 | } |
| 2871 | assert(fperm!=PERM_LNK && "This should have been validated before."); |
| 2872 | if(PERM_LNK==fperm){ |
| 2873 | mf_err((pErr,"Cannot commit symlinks via mini-checkin.")); |
| 2874 | }else if(PERM_EXE==fperm){ |
| 2875 | zPerm = " x"; |
| 2876 | }else{ |
| 2877 | zPerm = ""; |
| 2878 | } |
| 2879 | zUuid = blob_str(&pCI->fileHash); |
| 2880 | assert(zFile ? cmp>0&&asDelta==0 : asDelta!=0||cmp==0); |
| 2881 | assert(zFilename); |
| 2882 | assert(zUuid); |
| 2883 | assert(zPerm); |
| 2884 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2885 | /* Non-delta: write F-cards which lexically follow pCI->zFilename */ |
| 2886 | while(zFile!=0){ |
| 2887 | #ifndef NDEBUG |
| 2888 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2889 | assert(cmp>0); |
| 2890 | if(cmp<=0){ |
| 2891 | mf_err((pErr,"Internal error: mis-ordering of " |
| 2892 | "F-cards detected.")); |
| 2893 | } |
| 2894 | #endif |
| 2895 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2896 | zFile->zName, zFile->zUuid, |
| 2897 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2898 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2899 | zFile = manifest_file_next(pCI->pParent, 0); |
| 2900 | } |
| 2901 | #undef mf_err |
| 2902 | return 1; |
| 2903 | } |
| 2904 | |
| 2905 | |
| 2906 | /* |
| 2907 | ** Creates a manifest file, written to pOut, from the state in the |
| 2908 | ** fully-populated and semantically valid pCI argument. pCI is not |
| 2909 | ** *semantically* modified but cannot be const because blob_str() may |
| 2910 | ** need to NUL-terminate any given blob. |
| 2911 | ** |
| 2912 | ** Returns true on success. On error, returns 0 and, if pErr is not |
| 2913 | ** NULL, writes an error message there. |
| 2914 | ** |
| 2915 | ** Intended only to be called via checkin_mini() or routines which |
| 2916 | ** have already completely vetted pCI. |
| 2917 | */ |
| 2918 | static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI, |
| 2919 | Blob * pErr){ |
| 2920 | Blob zCard = empty_blob; /* Z-card checksum */ |
| 2921 | int asDelta = 0; |
| 2922 | |
| 2923 | assert(blob_str(&pCI->fileHash)); |
| 2924 | assert(pCI->pParent); |
| 2925 | assert(pCI->zFilename); |
| 2926 | assert(pCI->zUser); |
| @@ -2825,65 +2935,26 @@ | |
| 2935 | ** told to handle a symlink because there would seem to be no(?) |
| 2936 | ** sensible way to handle a symlink add/checkin without a |
| 2937 | ** checkout. |
| 2938 | */ |
| 2939 | blob_zero(pOut); |
| 2940 | if((pCI->flags & CIMINI_PREFER_DELTA) |
| 2941 | && pCI->pParent->zBaseline==0){ |
| 2942 | asDelta = 1; |
| 2943 | } |
| 2944 | if(asDelta){ |
| 2945 | blob_appendf(pOut, "B %s\n", pCI->zParentUuid); |
| 2946 | } |
| 2947 | if(blob_size(&pCI->comment)!=0){ |
| 2948 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2949 | }else{ |
| 2950 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2951 | } |
| 2952 | blob_appendf(pOut, "D %z\n", pCI->zDate); |
| 2953 | |
| 2954 | if(!create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)){ |
| 2955 | return 0; |
| 2956 | } |
| 2957 | |
| 2958 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2959 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2960 | } |
| @@ -2936,16 +3007,15 @@ | |
| 3007 | ** diagnostic message there. |
| 3008 | ** |
| 3009 | ** Returns true on success. If pRid is not NULL, the RID of the |
| 3010 | ** resulting manifest is written to *pRid. |
| 3011 | ** |
| 3012 | ** The checkin process is largely influenced by pCI->flags, and that |
| 3013 | ** must be populated before calling this. See the fossil_cimini_flags |
| 3014 | ** enum for the docs for each flag. |
| 3015 | */ |
| 3016 | static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){ |
| 3017 | Blob mf = empty_blob; /* output manifest */ |
| 3018 | int rid = 0, frid = 0; /* various RIDs */ |
| 3019 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 3020 | ManifestFile * zFilePrev; /* file entry from pCI->pParent */ |
| 3021 | int prevFRid = 0; /* RID of file's prev. version */ |
| @@ -2964,20 +3034,24 @@ | |
| 3034 | ** no reason we can't do that via the generated manifest, |
| 3035 | ** but the commit command does not offer that option, so |
| 3036 | ** we won't, either. |
| 3037 | */ |
| 3038 | } |
| 3039 | if(!(CIMINI_ALLOW_FORK & pCI->flags) |
| 3040 | && !is_a_leaf(pCI->pParent->rid)){ |
| 3041 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 3042 | pCI->zParentUuid)); |
| 3043 | } |
| 3044 | if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags) |
| 3045 | && contains_merge_marker(&pCI->fileContent)){ |
| 3046 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 3047 | } |
| 3048 | if(!file_is_simple_pathname(pCI->zFilename, 1)){ |
| 3049 | ci_err((pErr,"Invalid filename for use in a repository: %s", |
| 3050 | pCI->zFilename)); |
| 3051 | } |
| 3052 | |
| 3053 | { |
| 3054 | /* |
| 3055 | ** Normalize the timestamp. We don't use date_in_standard_format() |
| 3056 | ** because that has side-effects we don't want to trigger here. |
| 3057 | */ |
| @@ -2989,11 +3063,11 @@ | |
| 3063 | ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); |
| 3064 | } |
| 3065 | fossil_free(pCI->zDate); |
| 3066 | pCI->zDate = zDVal; |
| 3067 | } |
| 3068 | if(!(CIMINI_ALLOW_OLDER & pCI->flags) |
| 3069 | && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ |
| 3070 | ci_err((pErr,"Checkin time (%s) may not be older " |
| 3071 | "than its parent (%z).", |
| 3072 | pCI->zDate, |
| 3073 | db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", |
| @@ -3024,37 +3098,38 @@ | |
| 3098 | */ |
| 3099 | manifest_file_rewind(pCI->pParent); |
| 3100 | zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 3101 | if(!zFilePrev){ |
| 3102 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3103 | "Adding new files is currently not permitted.", |
| 3104 | pCI->zFilename, pCI->zParentUuid)); |
| 3105 | }else if(zFilePrev->zPerm |
| 3106 | && manifest_file_mperm(zFilePrev)==PERM_LNK){ |
| 3107 | ci_err((pErr,"Cannot save a symlink via a mini-checkin.")); |
| 3108 | } |
| 3109 | if(zFilePrev){ |
| 3110 | prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); |
| 3111 | } |
| 3112 | |
| 3113 | if((CIMINI_CONVERT_EOL & pCI->flags) |
| 3114 | && zFilePrev!=0 |
| 3115 | && blob_size(&pCI->fileContent)>0){ |
| 3116 | /* Confirm that the new content has the same EOL style as its |
| 3117 | ** predecessor and convert it, if needed, to the same style. Note |
| 3118 | ** that this inherently runs a risk of breaking content, |
| 3119 | ** e.g. string literals which contain embedded newlines. Note that |
| 3120 | ** HTML5 specifies that form-submitted TEXTAREA content gets |
| 3121 | ** normalized to CRLF-style: |
| 3122 | ** |
| 3123 | ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element |
| 3124 | ** |
| 3125 | ** More performant/efficient would be to offer a flag which says |
| 3126 | ** which newline form to use, converting the new copy (if needed) |
| 3127 | ** without having to examine the original. Since the primary use |
| 3128 | ** case is a web interface, it would be easy to offer it as a |
| 3129 | ** checkbox there. |
| 3130 | */ |
| 3131 | const int pseudoBinary = LOOK_LONG | LOOK_NUL; |
| 3132 | const int lookFlags = LOOK_CRLF | pseudoBinary; |
| 3133 | const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags ); |
| 3134 | if(!(pseudoBinary & lookNew)){ |
| 3135 | Blob contentPrev = empty_blob; |
| @@ -3101,11 +3176,11 @@ | |
| 3176 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3177 | return 0; |
| 3178 | } |
| 3179 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3180 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3181 | if(pCI->flags & CIMINI_DUMP_MANIFEST){ |
| 3182 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3183 | } |
| 3184 | manifest_crosslink(rid, &mf, 0); |
| 3185 | blob_reset(&mf); |
| 3186 | /* Save and deltify the file content... */ |
| @@ -3113,11 +3188,11 @@ | |
| 3188 | 0, 0, isPrivate); |
| 3189 | if(zFilePrev!=0){ |
| 3190 | assert(prevFRid>0); |
| 3191 | content_deltify(frid, &prevFRid, 1, 0); |
| 3192 | } |
| 3193 | db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0); |
| 3194 | if(pRid!=0){ |
| 3195 | *pRid = rid; |
| 3196 | } |
| 3197 | return 1; |
| 3198 | ci_error: |
| @@ -3160,10 +3235,12 @@ | |
| 3235 | ** ancestor. |
| 3236 | ** --convert-eol Convert EOL style of the checkin to match |
| 3237 | ** the previous version's content. Does not |
| 3238 | ** modify the original file, only the |
| 3239 | ** checked-in content. |
| 3240 | ** --delta Prefer to generate a delta manifest, if |
| 3241 | ** able. |
| 3242 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3243 | ** --wet-run Disables the default dry-run mode. |
| 3244 | ** |
| 3245 | ** Example: |
| 3246 | ** |
| @@ -3178,47 +3255,49 @@ | |
| 3255 | const char * zCommentFile; /* -M FILE */ |
| 3256 | const char * zAsFilename; /* --as filename */ |
| 3257 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3258 | const char * zUser; /* --user-override */ |
| 3259 | const char * zDate; /* --date-override */ |
| 3260 | |
| 3261 | CheckinMiniInfo_init(&cinf); |
| 3262 | zComment = find_option("comment","m",1); |
| 3263 | zCommentFile = find_option("comment-file","M",1); |
| 3264 | zAsFilename = find_option("as",0,1); |
| 3265 | zRevision = find_option("revision","r",1); |
| 3266 | zUser = find_option("user-override",0,1); |
| 3267 | zDate = find_option("date-override",0,1); |
| 3268 | if(find_option("wet-run",0,0)==0){ |
| 3269 | cinf.flags |= CIMINI_DRY_RUN; |
| 3270 | } |
| 3271 | if(find_option("allow-fork",0,0)!=0){ |
| 3272 | cinf.flags |= CIMINI_ALLOW_FORK; |
| 3273 | } |
| 3274 | if(find_option("dump-manifest","d",0)!=0){ |
| 3275 | cinf.flags |= CIMINI_DUMP_MANIFEST; |
| 3276 | } |
| 3277 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3278 | cinf.flags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3279 | } |
| 3280 | if(find_option("allow-older",0,0)!=0){ |
| 3281 | cinf.flags |= CIMINI_ALLOW_OLDER; |
| 3282 | } |
| 3283 | if(find_option("convert-eol",0,0)!=0){ |
| 3284 | cinf.flags |= CIMINI_CONVERT_EOL; |
| 3285 | } |
| 3286 | if(find_option("delta",0,0)!=0){ |
| 3287 | cinf.flags |= CIMINI_PREFER_DELTA; |
| 3288 | } |
| 3289 | db_find_and_open_repository(0, 0); |
| 3290 | verify_all_options(); |
| 3291 | user_select(); |
| 3292 | if(g.argc!=3){ |
| 3293 | usage("INFILE"); |
| 3294 | } |
| 3295 | |
| 3296 | if(!(cinf.flags & CIMINI_DRY_RUN)){ |
| 3297 | /* Until this feature is fully vetted, disallow it in the main |
| 3298 | ** fossil repo unless dry-run mode is being used. */ |
| 3299 | char * zProjCode = db_get("project-code",0); |
| 3300 | assert(zProjCode); |
| 3301 | if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", |
| 3302 | zProjCode)){ |
| 3303 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| @@ -3261,21 +3340,20 @@ | |
| 3340 | assert(cinf.pParent!=0); |
| 3341 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3342 | ExtFILE/*may want to reconsider*/); |
| 3343 | { |
| 3344 | Blob errMsg = empty_blob; |
| 3345 | const int rc = checkin_mini(&cinf, &newRid, &errMsg); |
| 3346 | CheckinMiniInfo_cleanup(&cinf); |
| 3347 | if(rc){ |
| 3348 | assert(blob_size(&errMsg)==0); |
| 3349 | }else{ |
| 3350 | assert(blob_size(&errMsg)); |
| 3351 | fossil_fatal("%b", &errMsg); |
| 3352 | } |
| 3353 | } |
| 3354 | if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3355 | fossil_warning("The checkout state is now out of sync " |
| 3356 | "with regards to this commit. It needs to be " |
| 3357 | "'update'd or 'close'd and re-'open'ed."); |
| 3358 | } |
| 3359 | } |
| 3360 |