Fossil SCM
Corrected propagation of deleted-via-delta F-cards. Added option to save generated manifest (on success) to a file. General cleanups.
Commit
fcdd76f6fbcf2eab6d0716be197e37b3d9fab9e37962d57705c9aef5b13051bc
Parent
03cce1c97786f84…
1 file changed
+138
-93
+138
-93
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2697,72 +2697,49 @@ | ||
| 2697 | 2697 | } |
| 2698 | 2698 | } |
| 2699 | 2699 | |
| 2700 | 2700 | /* |
| 2701 | 2701 | ** State for the "mini-checkin" infrastructure, which enables the |
| 2702 | -** ability to commit changes to a single file via an HTTP request. | |
| 2702 | +** ability to commit changes to a single file without a checkout | |
| 2703 | +** db, e.g. for use via an HTTP request. | |
| 2703 | 2704 | ** |
| 2704 | 2705 | ** Memory for all non-const (char *) members is owned by the |
| 2705 | -** CheckinMiniInfo instance. | |
| 2706 | +** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). | |
| 2706 | 2707 | */ |
| 2707 | 2708 | struct CheckinMiniInfo { |
| 2708 | - Manifest * pParent; /* parent checkin */ | |
| 2709 | + Manifest * pParent; /* parent checkin. Memory is owned by this | |
| 2710 | + object. */ | |
| 2709 | 2711 | char *zParentUuid; /* UUID of pParent */ |
| 2710 | - Blob comment; /* Check-in comment text */ | |
| 2711 | - char *zMimetype; /* Mimetype of check-in command. May be NULL */ | |
| 2712 | - char *zUser; /* User name */ | |
| 2713 | - char *zDate; /* Optionally force this date string (anything | |
| 2714 | - supported by date_in_standard_format()). | |
| 2715 | - Maybe be NULL. */ | |
| 2716 | 2712 | char *zFilename; /* Name of single file to commit. Must be |
| 2717 | 2713 | relative to the top of the repo. */ |
| 2718 | 2714 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2719 | 2715 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2720 | 2716 | preferred hash method. */ |
| 2721 | - int filePerm; /* Permissions (via file_perm()) of file. We | |
| 2722 | - need to store this before calling | |
| 2717 | + Blob comment; /* Check-in comment text */ | |
| 2718 | + char *zMimetype; /* Mimetype of comment. May be NULL */ | |
| 2719 | + char *zUser; /* User name */ | |
| 2720 | + char *zDate; /* Optionally force this date string (anything | |
| 2721 | + supported by date_in_standard_format()). | |
| 2722 | + Maybe be NULL. */ | |
| 2723 | + Blob *pMfOut; /* If not NULL, checkin_mini() will write a | |
| 2724 | + copy of the generated manifest here. This | |
| 2725 | + memory is NOT owned by CheckinMiniInfo. */ | |
| 2726 | + int filePerm; /* Permissions (via file_perm()) of the input | |
| 2727 | + file. We need to store this before calling | |
| 2723 | 2728 | checkin_mini() because the real input file |
| 2724 | - name may differ from this->zFilename and | |
| 2725 | - checkin_mini() requires the permissions of | |
| 2726 | - the original file. For web commits, set this | |
| 2727 | - to PERM_REG before calling | |
| 2728 | - checkin_mini(). */ | |
| 2729 | - int flags; /* Bitmask of fossil_cimini_flags for | |
| 2730 | - communication from checkin_mini() to | |
| 2731 | - create_manifest_mini(). */ | |
| 2729 | + name may differ from the repo-centric | |
| 2730 | + this->zFilename, and checkin_mini() requires | |
| 2731 | + the permissions of the original file. For | |
| 2732 | + web commits, set this to PERM_REG or (when | |
| 2733 | + editing executable scripts) PERM_EXE before | |
| 2734 | + calling checkin_mini(). */ | |
| 2735 | + int flags; /* Bitmask of fossil_cimini_flags. */ | |
| 2732 | 2736 | }; |
| 2733 | 2737 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2734 | -/* | |
| 2735 | -** Initializes p to a known-valid default state. | |
| 2736 | -*/ | |
| 2737 | -static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ | |
| 2738 | - memset(p, 0, sizeof(CheckinMiniInfo)); | |
| 2739 | - p->flags = 0; | |
| 2740 | - p->filePerm = -1; | |
| 2741 | - p->comment = p->fileContent = p->fileHash = empty_blob; | |
| 2742 | -} | |
| 2743 | - | |
| 2744 | -/* | |
| 2745 | -** Frees all memory owned by p, but does not free p. | |
| 2746 | - */ | |
| 2747 | -static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){ | |
| 2748 | - blob_reset(&p->comment); | |
| 2749 | - blob_reset(&p->fileContent); | |
| 2750 | - blob_reset(&p->fileHash); | |
| 2751 | - if(p->pParent){ | |
| 2752 | - manifest_destroy(p->pParent); | |
| 2753 | - } | |
| 2754 | - fossil_free(p->zFilename); | |
| 2755 | - fossil_free(p->zMimetype); | |
| 2756 | - fossil_free(p->zParentUuid); | |
| 2757 | - fossil_free(p->zUser); | |
| 2758 | - fossil_free(p->zDate); | |
| 2759 | - CheckinMiniInfo_init(p); | |
| 2760 | -} | |
| 2761 | - | |
| 2762 | -/* | |
| 2763 | -** Flags for checkin_mini() | |
| 2738 | + | |
| 2739 | +/* | |
| 2740 | +** CheckinMiniInfo::flags values. | |
| 2764 | 2741 | */ |
| 2765 | 2742 | enum fossil_cimini_flags { |
| 2766 | 2743 | CIMINI_NONE = 0, |
| 2767 | 2744 | /* |
| 2768 | 2745 | ** Tells checkin_mini() to use dry-run mode. |
| @@ -2824,10 +2801,38 @@ | ||
| 2824 | 2801 | ** the inadvertent addition of a new file when an update to an |
| 2825 | 2802 | ** existing was intended, as a side-effect of name-case differences. |
| 2826 | 2803 | */ |
| 2827 | 2804 | CIMINI_ALLOW_NEW_FILE = 1<<8 |
| 2828 | 2805 | }; |
| 2806 | + | |
| 2807 | +/* | |
| 2808 | +** Initializes p to a known-valid default state. | |
| 2809 | +*/ | |
| 2810 | +static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ | |
| 2811 | + memset(p, 0, sizeof(CheckinMiniInfo)); | |
| 2812 | + p->flags = CIMINI_NONE; | |
| 2813 | + p->filePerm = -1; | |
| 2814 | + p->comment = p->fileContent = p->fileHash = empty_blob; | |
| 2815 | +} | |
| 2816 | + | |
| 2817 | +/* | |
| 2818 | +** Frees all memory owned by p, but does not free p. | |
| 2819 | + */ | |
| 2820 | +static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){ | |
| 2821 | + blob_reset(&p->comment); | |
| 2822 | + blob_reset(&p->fileContent); | |
| 2823 | + blob_reset(&p->fileHash); | |
| 2824 | + if(p->pParent){ | |
| 2825 | + manifest_destroy(p->pParent); | |
| 2826 | + } | |
| 2827 | + fossil_free(p->zFilename); | |
| 2828 | + fossil_free(p->zMimetype); | |
| 2829 | + fossil_free(p->zParentUuid); | |
| 2830 | + fossil_free(p->zUser); | |
| 2831 | + fossil_free(p->zDate); | |
| 2832 | + CheckinMiniInfo_init(p); | |
| 2833 | +} | |
| 2829 | 2834 | |
| 2830 | 2835 | /* |
| 2831 | 2836 | ** Internal helper which returns an F-card perms string suitable for |
| 2832 | 2837 | ** writing into a manifest. |
| 2833 | 2838 | */ |
| @@ -2840,10 +2845,31 @@ | ||
| 2840 | 2845 | } |
| 2841 | 2846 | |
| 2842 | 2847 | static const char * mfile_perm_mstring(const ManifestFile * p){ |
| 2843 | 2848 | return mfile_permint_mstring(manifest_file_mperm(p)); |
| 2844 | 2849 | } |
| 2850 | + | |
| 2851 | +/* | |
| 2852 | +** Internal helper for checkin_mini() and friends. Appends an F-card | |
| 2853 | +** for p to pOut. | |
| 2854 | +*/ | |
| 2855 | +static void checkin_mini_append_fcard(Blob *pOut, const ManifestFile *p){ | |
| 2856 | + if(p->zUuid){ | |
| 2857 | + assert(*p->zUuid); | |
| 2858 | + blob_appendf(pOut, "F %F %s%s", p->zName, | |
| 2859 | + p->zUuid, mfile_perm_mstring(p)); | |
| 2860 | + if(p->zPrior){ | |
| 2861 | + assert(*p->zPrior); | |
| 2862 | + blob_appendf(pOut, " %F\n", p->zPrior); | |
| 2863 | + }else{ | |
| 2864 | + blob_append(pOut, "\n", 1); | |
| 2865 | + } | |
| 2866 | + }else{ | |
| 2867 | + /* File was removed from parent delta. */ | |
| 2868 | + blob_appendf(pOut, "F %F\n", p->zName); | |
| 2869 | + } | |
| 2870 | +} | |
| 2845 | 2871 | |
| 2846 | 2872 | /* |
| 2847 | 2873 | ** Handles the F-card parts for create_manifest_mini(). |
| 2848 | 2874 | ** |
| 2849 | 2875 | ** If asDelta is true, F-cards will be handled as for a delta |
| @@ -2855,11 +2881,11 @@ | ||
| 2855 | 2881 | */ |
| 2856 | 2882 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2857 | 2883 | CheckinMiniInfo * pCI, |
| 2858 | 2884 | int asDelta, |
| 2859 | 2885 | Blob * pErr){ |
| 2860 | - ManifestFile *zFile; /* One file entry from pCI->pParent */ | |
| 2886 | + ManifestFile *pFile; /* One file entry from pCI->pParent */ | |
| 2861 | 2887 | const char *zFilename = 0; /* filename for new F-card */ |
| 2862 | 2888 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2863 | 2889 | int cmp = 0; /* filename comparison result */ |
| 2864 | 2890 | int iFCursor = 0; /* Cursor into pCI->pParent->aFile if |
| 2865 | 2891 | pCI->pParent is a delta. */ |
| @@ -2882,18 +2908,18 @@ | ||
| 2882 | 2908 | if(asDelta){ |
| 2883 | 2909 | if(pCI->pParent->zBaseline==0 || pCI->pParent->nFile==0 ){ |
| 2884 | 2910 | /* Parent is a baseline or a delta with no F-cards, so this is |
| 2885 | 2911 | ** the simplest case: create a delta with a single F-card. |
| 2886 | 2912 | */ |
| 2887 | - zFile = manifest_file_find(pCI->pParent, pCI->zFilename); | |
| 2888 | - if(zFile==0){/* New file */ | |
| 2913 | + pFile = manifest_file_find(pCI->pParent, pCI->zFilename); | |
| 2914 | + if(pFile==0){/* New file */ | |
| 2889 | 2915 | zFilename = pCI->zFilename; |
| 2890 | 2916 | }else{/* Replacement file */ |
| 2891 | - if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2917 | + if(manifest_file_mperm(pFile)==PERM_LNK){ | |
| 2892 | 2918 | goto err_no_symlink; |
| 2893 | 2919 | } |
| 2894 | - zFilename = zFile->zName | |
| 2920 | + zFilename = pFile->zName | |
| 2895 | 2921 | /* use original name in case of name-case difference */; |
| 2896 | 2922 | } |
| 2897 | 2923 | postProcess = 0; |
| 2898 | 2924 | }else{ |
| 2899 | 2925 | /* Parent is a delta manifest with F-cards. Traversal of delta |
| @@ -2907,57 +2933,51 @@ | ||
| 2907 | 2933 | */ |
| 2908 | 2934 | Manifest * p = pCI->pParent; |
| 2909 | 2935 | cmp = -1; |
| 2910 | 2936 | assert(p->nFile > 0); |
| 2911 | 2937 | iFCursor = 0; |
| 2912 | - zFile = &p->aFile[iFCursor]; | |
| 2938 | + pFile = &p->aFile[iFCursor]; | |
| 2913 | 2939 | /* Write F-cards which lexically preceed pCI->zFilename */ |
| 2914 | 2940 | for( ; iFCursor<p->nFile; ){ |
| 2915 | - zFile = &p->aFile[iFCursor]; | |
| 2916 | - cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 2941 | + pFile = &p->aFile[iFCursor]; | |
| 2942 | + cmp = fncmp(pFile->zName, pCI->zFilename); | |
| 2917 | 2943 | if(cmp<0){ |
| 2918 | 2944 | ++iFCursor; |
| 2919 | - if(zFile->zUuid){ | |
| 2920 | - blob_appendf(pOut, "F %F %s%s\n", zFile->zName, | |
| 2921 | - zFile->zUuid, mfile_perm_mstring(zFile)); | |
| 2922 | - }else{/* File was removed in parent delta */ | |
| 2923 | - blob_appendf(pOut, "F %F\n", zFile->zName); | |
| 2924 | - } | |
| 2945 | + checkin_mini_append_fcard(pOut,pFile); | |
| 2925 | 2946 | }else{ |
| 2926 | 2947 | break; |
| 2927 | 2948 | } |
| 2928 | 2949 | } |
| 2929 | 2950 | if(0==cmp){/* Match: override this F-card */ |
| 2930 | - assert(zFile); | |
| 2931 | - if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2951 | + assert(pFile); | |
| 2952 | + if(manifest_file_mperm(pFile)==PERM_LNK){ | |
| 2932 | 2953 | goto err_no_symlink; |
| 2933 | 2954 | } |
| 2934 | 2955 | ++iFCursor; |
| 2935 | - zFilename = zFile->zName | |
| 2956 | + zFilename = pFile->zName | |
| 2936 | 2957 | /* use original name in case of name-case difference */; |
| 2937 | 2958 | }else{/* This is a new file */ |
| 2938 | 2959 | zFilename = pCI->zFilename; |
| 2939 | 2960 | } |
| 2940 | 2961 | postProcess = 2; |
| 2941 | 2962 | } |
| 2942 | 2963 | }else{/* Non-delta: write F-cards which lexically preceed |
| 2943 | 2964 | pCI->zFilename */ |
| 2944 | 2965 | cmp = -1; |
| 2945 | - while((zFile = manifest_file_next(pCI->pParent, 0)) | |
| 2946 | - && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ | |
| 2947 | - blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, | |
| 2948 | - mfile_perm_mstring(zFile)); | |
| 2966 | + while((pFile = manifest_file_next(pCI->pParent, 0)) | |
| 2967 | + && (cmp = fncmp(pFile->zName, pCI->zFilename))<0){ | |
| 2968 | + checkin_mini_append_fcard(pOut,pFile); | |
| 2949 | 2969 | } |
| 2950 | 2970 | if(cmp==0){/* Match: override this F-card*/ |
| 2951 | - if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2971 | + if(manifest_file_mperm(pFile)==PERM_LNK){ | |
| 2952 | 2972 | goto err_no_symlink; |
| 2953 | 2973 | } |
| 2954 | - zFilename = zFile->zName | |
| 2974 | + zFilename = pFile->zName | |
| 2955 | 2975 | /* use original name in case of name-case difference */; |
| 2956 | 2976 | }else{/* This is a new file. */ |
| 2957 | 2977 | zFilename = pCI->zFilename; |
| 2958 | - if(zFile!=0){ | |
| 2978 | + if(pFile!=0){ | |
| 2959 | 2979 | assert(cmp>0); |
| 2960 | 2980 | assert(pCI->pParent->iFile>0); |
| 2961 | 2981 | --pCI->pParent->iFile |
| 2962 | 2982 | /* So that the post-processing loop picks up this file |
| 2963 | 2983 | again.*/; |
| @@ -2964,44 +2984,38 @@ | ||
| 2964 | 2984 | } |
| 2965 | 2985 | } |
| 2966 | 2986 | postProcess = 1; |
| 2967 | 2987 | } |
| 2968 | 2988 | /* Finally add the new file's F-card... */ |
| 2969 | - zFile = 0; | |
| 2989 | + pFile = 0; | |
| 2970 | 2990 | zUuid = blob_str(&pCI->fileHash); |
| 2971 | 2991 | assert(zFilename); |
| 2972 | 2992 | assert(zUuid); |
| 2973 | 2993 | assert(postProcess==0 || postProcess==1 || postProcess==2); |
| 2974 | 2994 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, |
| 2975 | 2995 | mfile_permint_mstring(pCI->filePerm)); |
| 2976 | 2996 | while(postProcess>0){ |
| 2977 | 2997 | /* Write F-cards which lexically follow pCI->zFilename */ |
| 2978 | 2998 | if(postProcess==1){ /* non-delta parent */ |
| 2979 | - zFile = manifest_file_next(pCI->pParent, 0); | |
| 2999 | + pFile = manifest_file_next(pCI->pParent, 0); | |
| 2980 | 3000 | }else{ /* clone directly from delta parent */ |
| 2981 | - zFile = iFCursor<pCI->pParent->nFile | |
| 3001 | + pFile = iFCursor<pCI->pParent->nFile | |
| 2982 | 3002 | ? &pCI->pParent->aFile[iFCursor++] : 0; |
| 2983 | 3003 | } |
| 2984 | - if(zFile==0){ | |
| 3004 | + if(pFile==0){ | |
| 2985 | 3005 | break; |
| 2986 | 3006 | } |
| 2987 | 3007 | #ifndef NDEBUG |
| 2988 | - cmp = fncmp(zFile->zName, pCI->zFilename); | |
| 3008 | + cmp = fncmp(pFile->zName, pCI->zFilename); | |
| 2989 | 3009 | assert(cmp>0); |
| 2990 | 3010 | if(cmp<=0){ |
| 2991 | 3011 | mf_err((pErr,"Internal error: mis-ordering of " |
| 2992 | 3012 | "F-cards detected.")); |
| 2993 | 3013 | } |
| 2994 | 3014 | #endif |
| 2995 | - if(zFile->zUuid){ | |
| 2996 | - blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, | |
| 2997 | - mfile_perm_mstring(zFile)); | |
| 2998 | - }else{ | |
| 2999 | - assert(postProcess==2); | |
| 3000 | - /* File was removed from parent delta. */ | |
| 3001 | - blob_appendf(pOut, "F %F\n", zFile->zName); | |
| 3002 | - } | |
| 3015 | + assert(pFile->zUuid || 2==postProcess); | |
| 3016 | + checkin_mini_append_fcard(pOut,pFile); | |
| 3003 | 3017 | } |
| 3004 | 3018 | return 1; |
| 3005 | 3019 | err_no_symlink: |
| 3006 | 3020 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 3007 | 3021 | "via mini-checkin.")); |
| @@ -3316,14 +3330,20 @@ | ||
| 3316 | 3330 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3317 | 3331 | return 0; |
| 3318 | 3332 | } |
| 3319 | 3333 | isPrivate = content_is_private(pCI->pParent->rid); |
| 3320 | 3334 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3321 | - content_deltify(rid, &pCI->pParent->rid, 1, 0); | |
| 3322 | 3335 | if(pCI->flags & CIMINI_DUMP_MANIFEST){ |
| 3323 | - fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); | |
| 3336 | + fossil_print("%b", &mf); | |
| 3337 | + } | |
| 3338 | + if(pCI->pMfOut!=0){ | |
| 3339 | + /* Cross-linking clears mf, so we have to copy it, | |
| 3340 | + ** instead of taking over its memory. */ | |
| 3341 | + blob_reset(pCI->pMfOut); | |
| 3342 | + blob_append(pCI->pMfOut, blob_buffer(&mf), blob_size(&mf)); | |
| 3324 | 3343 | } |
| 3344 | + content_deltify(rid, &pCI->pParent->rid, 1, 0); | |
| 3325 | 3345 | manifest_crosslink(rid, &mf, 0); |
| 3326 | 3346 | blob_reset(&mf); |
| 3327 | 3347 | /* Save and deltify the file content... */ |
| 3328 | 3348 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 3329 | 3349 | 0, 0, isPrivate); |
| @@ -3388,11 +3408,14 @@ | ||
| 3388 | 3408 | ** --allow-new-file Allow addition of a new file this way. |
| 3389 | 3409 | ** Disabled by default to avoid that case- |
| 3390 | 3410 | ** sensitivity errors inadvertently lead to |
| 3391 | 3411 | ** adding a new file where an update is |
| 3392 | 3412 | ** intended. |
| 3393 | -** --dump-manifest|-d Dumps the generated manifest to stdout. | |
| 3413 | +** --dump-manifest|-d Dumps the generated manifest to stdout | |
| 3414 | +** immediately after it's generated. | |
| 3415 | +** --save-manifest FILE Saves the generated manifest to a file | |
| 3416 | +** after successfully processing it. | |
| 3394 | 3417 | ** --wet-run Disables the default dry-run mode. |
| 3395 | 3418 | ** |
| 3396 | 3419 | ** Example: |
| 3397 | 3420 | ** |
| 3398 | 3421 | ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| @@ -3406,10 +3429,11 @@ | ||
| 3406 | 3429 | const char * zCommentFile; /* -M FILE */ |
| 3407 | 3430 | const char * zAsFilename; /* --as filename */ |
| 3408 | 3431 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3409 | 3432 | const char * zUser; /* --user-override */ |
| 3410 | 3433 | const char * zDate; /* --date-override */ |
| 3434 | + char const * zManifestFile = 0;/* --save-manifest FILE */ | |
| 3411 | 3435 | |
| 3412 | 3436 | /* This function should perform only the minimal "business logic" it |
| 3413 | 3437 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3414 | 3438 | ** then pass it on to checkin_mini() to do most of the validation |
| 3415 | 3439 | ** and work. The point of this is to avoid duplicate code when a web |
| @@ -3420,10 +3444,11 @@ | ||
| 3420 | 3444 | zCommentFile = find_option("comment-file","M",1); |
| 3421 | 3445 | zAsFilename = find_option("as",0,1); |
| 3422 | 3446 | zRevision = find_option("revision","r",1); |
| 3423 | 3447 | zUser = find_option("user-override",0,1); |
| 3424 | 3448 | zDate = find_option("date-override",0,1); |
| 3449 | + zManifestFile = find_option("save-manifest",0,1); | |
| 3425 | 3450 | if(find_option("wet-run",0,0)==0){ |
| 3426 | 3451 | cinf.flags |= CIMINI_DRY_RUN; |
| 3427 | 3452 | } |
| 3428 | 3453 | if(find_option("allow-fork",0,0)!=0){ |
| 3429 | 3454 | cinf.flags |= CIMINI_ALLOW_FORK; |
| @@ -3466,10 +3491,11 @@ | ||
| 3466 | 3491 | } |
| 3467 | 3492 | if(!blob_size(&cinf.comment)){ |
| 3468 | 3493 | fossil_fatal("Non-empty checkin comment is required."); |
| 3469 | 3494 | } |
| 3470 | 3495 | } |
| 3496 | + db_begin_transaction(); | |
| 3471 | 3497 | zFilename = g.argv[2]; |
| 3472 | 3498 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3473 | 3499 | cinf.filePerm = file_perm(zFilename, ExtFILE); |
| 3474 | 3500 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3475 | 3501 | if(zDate){ |
| @@ -3484,24 +3510,43 @@ | ||
| 3484 | 3510 | } |
| 3485 | 3511 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3486 | 3512 | if(cinf.zParentUuid==0){ |
| 3487 | 3513 | fossil_fatal("Cannot determine version to commit to."); |
| 3488 | 3514 | } |
| 3489 | - blob_read_from_file(&cinf.fileContent, zFilename, | |
| 3490 | - ExtFILE/*may want to reconsider*/); | |
| 3515 | + blob_read_from_file(&cinf.fileContent, zFilename, ExtFILE); | |
| 3491 | 3516 | { |
| 3517 | + Blob theManifest = empty_blob; /* --save-manifest target */ | |
| 3492 | 3518 | Blob errMsg = empty_blob; |
| 3493 | - const int rc = checkin_mini(&cinf, &newRid, &errMsg); | |
| 3494 | - CheckinMiniInfo_cleanup(&cinf); | |
| 3519 | + int rc; | |
| 3520 | + if(zManifestFile){ | |
| 3521 | + cinf.pMfOut = &theManifest; | |
| 3522 | + } | |
| 3523 | + rc = checkin_mini(&cinf, &newRid, &errMsg); | |
| 3495 | 3524 | if(rc){ |
| 3496 | 3525 | assert(blob_size(&errMsg)==0); |
| 3497 | 3526 | }else{ |
| 3498 | 3527 | assert(blob_size(&errMsg)); |
| 3499 | 3528 | fossil_fatal("%b", &errMsg); |
| 3500 | 3529 | } |
| 3530 | + if(zManifestFile){ | |
| 3531 | + fossil_print("Writing manifest to: %s\n", zManifestFile); | |
| 3532 | + assert(blob_size(&theManifest)>0); | |
| 3533 | + blob_write_to_file(&theManifest, zManifestFile); | |
| 3534 | + blob_reset(&theManifest); | |
| 3535 | + } | |
| 3536 | + } | |
| 3537 | + if(newRid!=0){ | |
| 3538 | + fossil_print("New version%s: %z\n", | |
| 3539 | + (cinf.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", | |
| 3540 | + rid_to_uuid(newRid)); | |
| 3501 | 3541 | } |
| 3542 | + db_end_transaction(0/*checkin_mini() will have triggered it to roll | |
| 3543 | + ** back in dry-run mode, but we need access to | |
| 3544 | + ** the transaction-written db state in this | |
| 3545 | + ** routine.*/); | |
| 3502 | 3546 | if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3503 | 3547 | fossil_warning("The checkout state is now out of sync " |
| 3504 | 3548 | "with regards to this commit. It needs to be " |
| 3505 | 3549 | "'update'd or 'close'd and re-'open'ed."); |
| 3506 | 3550 | } |
| 3551 | + CheckinMiniInfo_cleanup(&cinf); | |
| 3507 | 3552 | } |
| 3508 | 3553 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2697,72 +2697,49 @@ | |
| 2697 | } |
| 2698 | } |
| 2699 | |
| 2700 | /* |
| 2701 | ** State for the "mini-checkin" infrastructure, which enables the |
| 2702 | ** ability to commit changes to a single file via an HTTP request. |
| 2703 | ** |
| 2704 | ** Memory for all non-const (char *) members is owned by the |
| 2705 | ** CheckinMiniInfo instance. |
| 2706 | */ |
| 2707 | struct CheckinMiniInfo { |
| 2708 | Manifest * pParent; /* parent checkin */ |
| 2709 | char *zParentUuid; /* UUID of pParent */ |
| 2710 | Blob comment; /* Check-in comment text */ |
| 2711 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2712 | char *zUser; /* User name */ |
| 2713 | char *zDate; /* Optionally force this date string (anything |
| 2714 | supported by date_in_standard_format()). |
| 2715 | Maybe be NULL. */ |
| 2716 | char *zFilename; /* Name of single file to commit. Must be |
| 2717 | relative to the top of the repo. */ |
| 2718 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2719 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2720 | preferred hash method. */ |
| 2721 | int filePerm; /* Permissions (via file_perm()) of file. We |
| 2722 | need to store this before calling |
| 2723 | checkin_mini() because the real input file |
| 2724 | name may differ from this->zFilename and |
| 2725 | checkin_mini() requires the permissions of |
| 2726 | the original file. For web commits, set this |
| 2727 | to PERM_REG before calling |
| 2728 | checkin_mini(). */ |
| 2729 | int flags; /* Bitmask of fossil_cimini_flags for |
| 2730 | communication from checkin_mini() to |
| 2731 | create_manifest_mini(). */ |
| 2732 | }; |
| 2733 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2734 | /* |
| 2735 | ** Initializes p to a known-valid default state. |
| 2736 | */ |
| 2737 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2738 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2739 | p->flags = 0; |
| 2740 | p->filePerm = -1; |
| 2741 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2742 | } |
| 2743 | |
| 2744 | /* |
| 2745 | ** Frees all memory owned by p, but does not free p. |
| 2746 | */ |
| 2747 | static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){ |
| 2748 | blob_reset(&p->comment); |
| 2749 | blob_reset(&p->fileContent); |
| 2750 | blob_reset(&p->fileHash); |
| 2751 | if(p->pParent){ |
| 2752 | manifest_destroy(p->pParent); |
| 2753 | } |
| 2754 | fossil_free(p->zFilename); |
| 2755 | fossil_free(p->zMimetype); |
| 2756 | fossil_free(p->zParentUuid); |
| 2757 | fossil_free(p->zUser); |
| 2758 | fossil_free(p->zDate); |
| 2759 | CheckinMiniInfo_init(p); |
| 2760 | } |
| 2761 | |
| 2762 | /* |
| 2763 | ** Flags for checkin_mini() |
| 2764 | */ |
| 2765 | enum fossil_cimini_flags { |
| 2766 | CIMINI_NONE = 0, |
| 2767 | /* |
| 2768 | ** Tells checkin_mini() to use dry-run mode. |
| @@ -2824,10 +2801,38 @@ | |
| 2824 | ** the inadvertent addition of a new file when an update to an |
| 2825 | ** existing was intended, as a side-effect of name-case differences. |
| 2826 | */ |
| 2827 | CIMINI_ALLOW_NEW_FILE = 1<<8 |
| 2828 | }; |
| 2829 | |
| 2830 | /* |
| 2831 | ** Internal helper which returns an F-card perms string suitable for |
| 2832 | ** writing into a manifest. |
| 2833 | */ |
| @@ -2840,10 +2845,31 @@ | |
| 2840 | } |
| 2841 | |
| 2842 | static const char * mfile_perm_mstring(const ManifestFile * p){ |
| 2843 | return mfile_permint_mstring(manifest_file_mperm(p)); |
| 2844 | } |
| 2845 | |
| 2846 | /* |
| 2847 | ** Handles the F-card parts for create_manifest_mini(). |
| 2848 | ** |
| 2849 | ** If asDelta is true, F-cards will be handled as for a delta |
| @@ -2855,11 +2881,11 @@ | |
| 2855 | */ |
| 2856 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2857 | CheckinMiniInfo * pCI, |
| 2858 | int asDelta, |
| 2859 | Blob * pErr){ |
| 2860 | ManifestFile *zFile; /* One file entry from pCI->pParent */ |
| 2861 | const char *zFilename = 0; /* filename for new F-card */ |
| 2862 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2863 | int cmp = 0; /* filename comparison result */ |
| 2864 | int iFCursor = 0; /* Cursor into pCI->pParent->aFile if |
| 2865 | pCI->pParent is a delta. */ |
| @@ -2882,18 +2908,18 @@ | |
| 2882 | if(asDelta){ |
| 2883 | if(pCI->pParent->zBaseline==0 || pCI->pParent->nFile==0 ){ |
| 2884 | /* Parent is a baseline or a delta with no F-cards, so this is |
| 2885 | ** the simplest case: create a delta with a single F-card. |
| 2886 | */ |
| 2887 | zFile = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 2888 | if(zFile==0){/* New file */ |
| 2889 | zFilename = pCI->zFilename; |
| 2890 | }else{/* Replacement file */ |
| 2891 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2892 | goto err_no_symlink; |
| 2893 | } |
| 2894 | zFilename = zFile->zName |
| 2895 | /* use original name in case of name-case difference */; |
| 2896 | } |
| 2897 | postProcess = 0; |
| 2898 | }else{ |
| 2899 | /* Parent is a delta manifest with F-cards. Traversal of delta |
| @@ -2907,57 +2933,51 @@ | |
| 2907 | */ |
| 2908 | Manifest * p = pCI->pParent; |
| 2909 | cmp = -1; |
| 2910 | assert(p->nFile > 0); |
| 2911 | iFCursor = 0; |
| 2912 | zFile = &p->aFile[iFCursor]; |
| 2913 | /* Write F-cards which lexically preceed pCI->zFilename */ |
| 2914 | for( ; iFCursor<p->nFile; ){ |
| 2915 | zFile = &p->aFile[iFCursor]; |
| 2916 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2917 | if(cmp<0){ |
| 2918 | ++iFCursor; |
| 2919 | if(zFile->zUuid){ |
| 2920 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, |
| 2921 | zFile->zUuid, mfile_perm_mstring(zFile)); |
| 2922 | }else{/* File was removed in parent delta */ |
| 2923 | blob_appendf(pOut, "F %F\n", zFile->zName); |
| 2924 | } |
| 2925 | }else{ |
| 2926 | break; |
| 2927 | } |
| 2928 | } |
| 2929 | if(0==cmp){/* Match: override this F-card */ |
| 2930 | assert(zFile); |
| 2931 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2932 | goto err_no_symlink; |
| 2933 | } |
| 2934 | ++iFCursor; |
| 2935 | zFilename = zFile->zName |
| 2936 | /* use original name in case of name-case difference */; |
| 2937 | }else{/* This is a new file */ |
| 2938 | zFilename = pCI->zFilename; |
| 2939 | } |
| 2940 | postProcess = 2; |
| 2941 | } |
| 2942 | }else{/* Non-delta: write F-cards which lexically preceed |
| 2943 | pCI->zFilename */ |
| 2944 | cmp = -1; |
| 2945 | while((zFile = manifest_file_next(pCI->pParent, 0)) |
| 2946 | && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ |
| 2947 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, |
| 2948 | mfile_perm_mstring(zFile)); |
| 2949 | } |
| 2950 | if(cmp==0){/* Match: override this F-card*/ |
| 2951 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2952 | goto err_no_symlink; |
| 2953 | } |
| 2954 | zFilename = zFile->zName |
| 2955 | /* use original name in case of name-case difference */; |
| 2956 | }else{/* This is a new file. */ |
| 2957 | zFilename = pCI->zFilename; |
| 2958 | if(zFile!=0){ |
| 2959 | assert(cmp>0); |
| 2960 | assert(pCI->pParent->iFile>0); |
| 2961 | --pCI->pParent->iFile |
| 2962 | /* So that the post-processing loop picks up this file |
| 2963 | again.*/; |
| @@ -2964,44 +2984,38 @@ | |
| 2964 | } |
| 2965 | } |
| 2966 | postProcess = 1; |
| 2967 | } |
| 2968 | /* Finally add the new file's F-card... */ |
| 2969 | zFile = 0; |
| 2970 | zUuid = blob_str(&pCI->fileHash); |
| 2971 | assert(zFilename); |
| 2972 | assert(zUuid); |
| 2973 | assert(postProcess==0 || postProcess==1 || postProcess==2); |
| 2974 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, |
| 2975 | mfile_permint_mstring(pCI->filePerm)); |
| 2976 | while(postProcess>0){ |
| 2977 | /* Write F-cards which lexically follow pCI->zFilename */ |
| 2978 | if(postProcess==1){ /* non-delta parent */ |
| 2979 | zFile = manifest_file_next(pCI->pParent, 0); |
| 2980 | }else{ /* clone directly from delta parent */ |
| 2981 | zFile = iFCursor<pCI->pParent->nFile |
| 2982 | ? &pCI->pParent->aFile[iFCursor++] : 0; |
| 2983 | } |
| 2984 | if(zFile==0){ |
| 2985 | break; |
| 2986 | } |
| 2987 | #ifndef NDEBUG |
| 2988 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2989 | assert(cmp>0); |
| 2990 | if(cmp<=0){ |
| 2991 | mf_err((pErr,"Internal error: mis-ordering of " |
| 2992 | "F-cards detected.")); |
| 2993 | } |
| 2994 | #endif |
| 2995 | if(zFile->zUuid){ |
| 2996 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, |
| 2997 | mfile_perm_mstring(zFile)); |
| 2998 | }else{ |
| 2999 | assert(postProcess==2); |
| 3000 | /* File was removed from parent delta. */ |
| 3001 | blob_appendf(pOut, "F %F\n", zFile->zName); |
| 3002 | } |
| 3003 | } |
| 3004 | return 1; |
| 3005 | err_no_symlink: |
| 3006 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 3007 | "via mini-checkin.")); |
| @@ -3316,14 +3330,20 @@ | |
| 3316 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3317 | return 0; |
| 3318 | } |
| 3319 | isPrivate = content_is_private(pCI->pParent->rid); |
| 3320 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3321 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3322 | if(pCI->flags & CIMINI_DUMP_MANIFEST){ |
| 3323 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3324 | } |
| 3325 | manifest_crosslink(rid, &mf, 0); |
| 3326 | blob_reset(&mf); |
| 3327 | /* Save and deltify the file content... */ |
| 3328 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 3329 | 0, 0, isPrivate); |
| @@ -3388,11 +3408,14 @@ | |
| 3388 | ** --allow-new-file Allow addition of a new file this way. |
| 3389 | ** Disabled by default to avoid that case- |
| 3390 | ** sensitivity errors inadvertently lead to |
| 3391 | ** adding a new file where an update is |
| 3392 | ** intended. |
| 3393 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3394 | ** --wet-run Disables the default dry-run mode. |
| 3395 | ** |
| 3396 | ** Example: |
| 3397 | ** |
| 3398 | ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| @@ -3406,10 +3429,11 @@ | |
| 3406 | const char * zCommentFile; /* -M FILE */ |
| 3407 | const char * zAsFilename; /* --as filename */ |
| 3408 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3409 | const char * zUser; /* --user-override */ |
| 3410 | const char * zDate; /* --date-override */ |
| 3411 | |
| 3412 | /* This function should perform only the minimal "business logic" it |
| 3413 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3414 | ** then pass it on to checkin_mini() to do most of the validation |
| 3415 | ** and work. The point of this is to avoid duplicate code when a web |
| @@ -3420,10 +3444,11 @@ | |
| 3420 | zCommentFile = find_option("comment-file","M",1); |
| 3421 | zAsFilename = find_option("as",0,1); |
| 3422 | zRevision = find_option("revision","r",1); |
| 3423 | zUser = find_option("user-override",0,1); |
| 3424 | zDate = find_option("date-override",0,1); |
| 3425 | if(find_option("wet-run",0,0)==0){ |
| 3426 | cinf.flags |= CIMINI_DRY_RUN; |
| 3427 | } |
| 3428 | if(find_option("allow-fork",0,0)!=0){ |
| 3429 | cinf.flags |= CIMINI_ALLOW_FORK; |
| @@ -3466,10 +3491,11 @@ | |
| 3466 | } |
| 3467 | if(!blob_size(&cinf.comment)){ |
| 3468 | fossil_fatal("Non-empty checkin comment is required."); |
| 3469 | } |
| 3470 | } |
| 3471 | zFilename = g.argv[2]; |
| 3472 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3473 | cinf.filePerm = file_perm(zFilename, ExtFILE); |
| 3474 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3475 | if(zDate){ |
| @@ -3484,24 +3510,43 @@ | |
| 3484 | } |
| 3485 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3486 | if(cinf.zParentUuid==0){ |
| 3487 | fossil_fatal("Cannot determine version to commit to."); |
| 3488 | } |
| 3489 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3490 | ExtFILE/*may want to reconsider*/); |
| 3491 | { |
| 3492 | Blob errMsg = empty_blob; |
| 3493 | const int rc = checkin_mini(&cinf, &newRid, &errMsg); |
| 3494 | CheckinMiniInfo_cleanup(&cinf); |
| 3495 | if(rc){ |
| 3496 | assert(blob_size(&errMsg)==0); |
| 3497 | }else{ |
| 3498 | assert(blob_size(&errMsg)); |
| 3499 | fossil_fatal("%b", &errMsg); |
| 3500 | } |
| 3501 | } |
| 3502 | if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3503 | fossil_warning("The checkout state is now out of sync " |
| 3504 | "with regards to this commit. It needs to be " |
| 3505 | "'update'd or 'close'd and re-'open'ed."); |
| 3506 | } |
| 3507 | } |
| 3508 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2697,72 +2697,49 @@ | |
| 2697 | } |
| 2698 | } |
| 2699 | |
| 2700 | /* |
| 2701 | ** State for the "mini-checkin" infrastructure, which enables the |
| 2702 | ** ability to commit changes to a single file without a checkout |
| 2703 | ** db, e.g. for use via an HTTP request. |
| 2704 | ** |
| 2705 | ** Memory for all non-const (char *) members is owned by the |
| 2706 | ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). |
| 2707 | */ |
| 2708 | struct CheckinMiniInfo { |
| 2709 | Manifest * pParent; /* parent checkin. Memory is owned by this |
| 2710 | object. */ |
| 2711 | char *zParentUuid; /* UUID of pParent */ |
| 2712 | char *zFilename; /* Name of single file to commit. Must be |
| 2713 | relative to the top of the repo. */ |
| 2714 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2715 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2716 | preferred hash method. */ |
| 2717 | Blob comment; /* Check-in comment text */ |
| 2718 | char *zMimetype; /* Mimetype of comment. May be NULL */ |
| 2719 | char *zUser; /* User name */ |
| 2720 | char *zDate; /* Optionally force this date string (anything |
| 2721 | supported by date_in_standard_format()). |
| 2722 | Maybe be NULL. */ |
| 2723 | Blob *pMfOut; /* If not NULL, checkin_mini() will write a |
| 2724 | copy of the generated manifest here. This |
| 2725 | memory is NOT owned by CheckinMiniInfo. */ |
| 2726 | int filePerm; /* Permissions (via file_perm()) of the input |
| 2727 | file. We need to store this before calling |
| 2728 | checkin_mini() because the real input file |
| 2729 | name may differ from the repo-centric |
| 2730 | this->zFilename, and checkin_mini() requires |
| 2731 | the permissions of the original file. For |
| 2732 | web commits, set this to PERM_REG or (when |
| 2733 | editing executable scripts) PERM_EXE before |
| 2734 | calling checkin_mini(). */ |
| 2735 | int flags; /* Bitmask of fossil_cimini_flags. */ |
| 2736 | }; |
| 2737 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2738 | |
| 2739 | /* |
| 2740 | ** CheckinMiniInfo::flags values. |
| 2741 | */ |
| 2742 | enum fossil_cimini_flags { |
| 2743 | CIMINI_NONE = 0, |
| 2744 | /* |
| 2745 | ** Tells checkin_mini() to use dry-run mode. |
| @@ -2824,10 +2801,38 @@ | |
| 2801 | ** the inadvertent addition of a new file when an update to an |
| 2802 | ** existing was intended, as a side-effect of name-case differences. |
| 2803 | */ |
| 2804 | CIMINI_ALLOW_NEW_FILE = 1<<8 |
| 2805 | }; |
| 2806 | |
| 2807 | /* |
| 2808 | ** Initializes p to a known-valid default state. |
| 2809 | */ |
| 2810 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2811 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2812 | p->flags = CIMINI_NONE; |
| 2813 | p->filePerm = -1; |
| 2814 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2815 | } |
| 2816 | |
| 2817 | /* |
| 2818 | ** Frees all memory owned by p, but does not free p. |
| 2819 | */ |
| 2820 | static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){ |
| 2821 | blob_reset(&p->comment); |
| 2822 | blob_reset(&p->fileContent); |
| 2823 | blob_reset(&p->fileHash); |
| 2824 | if(p->pParent){ |
| 2825 | manifest_destroy(p->pParent); |
| 2826 | } |
| 2827 | fossil_free(p->zFilename); |
| 2828 | fossil_free(p->zMimetype); |
| 2829 | fossil_free(p->zParentUuid); |
| 2830 | fossil_free(p->zUser); |
| 2831 | fossil_free(p->zDate); |
| 2832 | CheckinMiniInfo_init(p); |
| 2833 | } |
| 2834 | |
| 2835 | /* |
| 2836 | ** Internal helper which returns an F-card perms string suitable for |
| 2837 | ** writing into a manifest. |
| 2838 | */ |
| @@ -2840,10 +2845,31 @@ | |
| 2845 | } |
| 2846 | |
| 2847 | static const char * mfile_perm_mstring(const ManifestFile * p){ |
| 2848 | return mfile_permint_mstring(manifest_file_mperm(p)); |
| 2849 | } |
| 2850 | |
| 2851 | /* |
| 2852 | ** Internal helper for checkin_mini() and friends. Appends an F-card |
| 2853 | ** for p to pOut. |
| 2854 | */ |
| 2855 | static void checkin_mini_append_fcard(Blob *pOut, const ManifestFile *p){ |
| 2856 | if(p->zUuid){ |
| 2857 | assert(*p->zUuid); |
| 2858 | blob_appendf(pOut, "F %F %s%s", p->zName, |
| 2859 | p->zUuid, mfile_perm_mstring(p)); |
| 2860 | if(p->zPrior){ |
| 2861 | assert(*p->zPrior); |
| 2862 | blob_appendf(pOut, " %F\n", p->zPrior); |
| 2863 | }else{ |
| 2864 | blob_append(pOut, "\n", 1); |
| 2865 | } |
| 2866 | }else{ |
| 2867 | /* File was removed from parent delta. */ |
| 2868 | blob_appendf(pOut, "F %F\n", p->zName); |
| 2869 | } |
| 2870 | } |
| 2871 | |
| 2872 | /* |
| 2873 | ** Handles the F-card parts for create_manifest_mini(). |
| 2874 | ** |
| 2875 | ** If asDelta is true, F-cards will be handled as for a delta |
| @@ -2855,11 +2881,11 @@ | |
| 2881 | */ |
| 2882 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2883 | CheckinMiniInfo * pCI, |
| 2884 | int asDelta, |
| 2885 | Blob * pErr){ |
| 2886 | ManifestFile *pFile; /* One file entry from pCI->pParent */ |
| 2887 | const char *zFilename = 0; /* filename for new F-card */ |
| 2888 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2889 | int cmp = 0; /* filename comparison result */ |
| 2890 | int iFCursor = 0; /* Cursor into pCI->pParent->aFile if |
| 2891 | pCI->pParent is a delta. */ |
| @@ -2882,18 +2908,18 @@ | |
| 2908 | if(asDelta){ |
| 2909 | if(pCI->pParent->zBaseline==0 || pCI->pParent->nFile==0 ){ |
| 2910 | /* Parent is a baseline or a delta with no F-cards, so this is |
| 2911 | ** the simplest case: create a delta with a single F-card. |
| 2912 | */ |
| 2913 | pFile = manifest_file_find(pCI->pParent, pCI->zFilename); |
| 2914 | if(pFile==0){/* New file */ |
| 2915 | zFilename = pCI->zFilename; |
| 2916 | }else{/* Replacement file */ |
| 2917 | if(manifest_file_mperm(pFile)==PERM_LNK){ |
| 2918 | goto err_no_symlink; |
| 2919 | } |
| 2920 | zFilename = pFile->zName |
| 2921 | /* use original name in case of name-case difference */; |
| 2922 | } |
| 2923 | postProcess = 0; |
| 2924 | }else{ |
| 2925 | /* Parent is a delta manifest with F-cards. Traversal of delta |
| @@ -2907,57 +2933,51 @@ | |
| 2933 | */ |
| 2934 | Manifest * p = pCI->pParent; |
| 2935 | cmp = -1; |
| 2936 | assert(p->nFile > 0); |
| 2937 | iFCursor = 0; |
| 2938 | pFile = &p->aFile[iFCursor]; |
| 2939 | /* Write F-cards which lexically preceed pCI->zFilename */ |
| 2940 | for( ; iFCursor<p->nFile; ){ |
| 2941 | pFile = &p->aFile[iFCursor]; |
| 2942 | cmp = fncmp(pFile->zName, pCI->zFilename); |
| 2943 | if(cmp<0){ |
| 2944 | ++iFCursor; |
| 2945 | checkin_mini_append_fcard(pOut,pFile); |
| 2946 | }else{ |
| 2947 | break; |
| 2948 | } |
| 2949 | } |
| 2950 | if(0==cmp){/* Match: override this F-card */ |
| 2951 | assert(pFile); |
| 2952 | if(manifest_file_mperm(pFile)==PERM_LNK){ |
| 2953 | goto err_no_symlink; |
| 2954 | } |
| 2955 | ++iFCursor; |
| 2956 | zFilename = pFile->zName |
| 2957 | /* use original name in case of name-case difference */; |
| 2958 | }else{/* This is a new file */ |
| 2959 | zFilename = pCI->zFilename; |
| 2960 | } |
| 2961 | postProcess = 2; |
| 2962 | } |
| 2963 | }else{/* Non-delta: write F-cards which lexically preceed |
| 2964 | pCI->zFilename */ |
| 2965 | cmp = -1; |
| 2966 | while((pFile = manifest_file_next(pCI->pParent, 0)) |
| 2967 | && (cmp = fncmp(pFile->zName, pCI->zFilename))<0){ |
| 2968 | checkin_mini_append_fcard(pOut,pFile); |
| 2969 | } |
| 2970 | if(cmp==0){/* Match: override this F-card*/ |
| 2971 | if(manifest_file_mperm(pFile)==PERM_LNK){ |
| 2972 | goto err_no_symlink; |
| 2973 | } |
| 2974 | zFilename = pFile->zName |
| 2975 | /* use original name in case of name-case difference */; |
| 2976 | }else{/* This is a new file. */ |
| 2977 | zFilename = pCI->zFilename; |
| 2978 | if(pFile!=0){ |
| 2979 | assert(cmp>0); |
| 2980 | assert(pCI->pParent->iFile>0); |
| 2981 | --pCI->pParent->iFile |
| 2982 | /* So that the post-processing loop picks up this file |
| 2983 | again.*/; |
| @@ -2964,44 +2984,38 @@ | |
| 2984 | } |
| 2985 | } |
| 2986 | postProcess = 1; |
| 2987 | } |
| 2988 | /* Finally add the new file's F-card... */ |
| 2989 | pFile = 0; |
| 2990 | zUuid = blob_str(&pCI->fileHash); |
| 2991 | assert(zFilename); |
| 2992 | assert(zUuid); |
| 2993 | assert(postProcess==0 || postProcess==1 || postProcess==2); |
| 2994 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, |
| 2995 | mfile_permint_mstring(pCI->filePerm)); |
| 2996 | while(postProcess>0){ |
| 2997 | /* Write F-cards which lexically follow pCI->zFilename */ |
| 2998 | if(postProcess==1){ /* non-delta parent */ |
| 2999 | pFile = manifest_file_next(pCI->pParent, 0); |
| 3000 | }else{ /* clone directly from delta parent */ |
| 3001 | pFile = iFCursor<pCI->pParent->nFile |
| 3002 | ? &pCI->pParent->aFile[iFCursor++] : 0; |
| 3003 | } |
| 3004 | if(pFile==0){ |
| 3005 | break; |
| 3006 | } |
| 3007 | #ifndef NDEBUG |
| 3008 | cmp = fncmp(pFile->zName, pCI->zFilename); |
| 3009 | assert(cmp>0); |
| 3010 | if(cmp<=0){ |
| 3011 | mf_err((pErr,"Internal error: mis-ordering of " |
| 3012 | "F-cards detected.")); |
| 3013 | } |
| 3014 | #endif |
| 3015 | assert(pFile->zUuid || 2==postProcess); |
| 3016 | checkin_mini_append_fcard(pOut,pFile); |
| 3017 | } |
| 3018 | return 1; |
| 3019 | err_no_symlink: |
| 3020 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 3021 | "via mini-checkin.")); |
| @@ -3316,14 +3330,20 @@ | |
| 3330 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3331 | return 0; |
| 3332 | } |
| 3333 | isPrivate = content_is_private(pCI->pParent->rid); |
| 3334 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3335 | if(pCI->flags & CIMINI_DUMP_MANIFEST){ |
| 3336 | fossil_print("%b", &mf); |
| 3337 | } |
| 3338 | if(pCI->pMfOut!=0){ |
| 3339 | /* Cross-linking clears mf, so we have to copy it, |
| 3340 | ** instead of taking over its memory. */ |
| 3341 | blob_reset(pCI->pMfOut); |
| 3342 | blob_append(pCI->pMfOut, blob_buffer(&mf), blob_size(&mf)); |
| 3343 | } |
| 3344 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3345 | manifest_crosslink(rid, &mf, 0); |
| 3346 | blob_reset(&mf); |
| 3347 | /* Save and deltify the file content... */ |
| 3348 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 3349 | 0, 0, isPrivate); |
| @@ -3388,11 +3408,14 @@ | |
| 3408 | ** --allow-new-file Allow addition of a new file this way. |
| 3409 | ** Disabled by default to avoid that case- |
| 3410 | ** sensitivity errors inadvertently lead to |
| 3411 | ** adding a new file where an update is |
| 3412 | ** intended. |
| 3413 | ** --dump-manifest|-d Dumps the generated manifest to stdout |
| 3414 | ** immediately after it's generated. |
| 3415 | ** --save-manifest FILE Saves the generated manifest to a file |
| 3416 | ** after successfully processing it. |
| 3417 | ** --wet-run Disables the default dry-run mode. |
| 3418 | ** |
| 3419 | ** Example: |
| 3420 | ** |
| 3421 | ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| @@ -3406,10 +3429,11 @@ | |
| 3429 | const char * zCommentFile; /* -M FILE */ |
| 3430 | const char * zAsFilename; /* --as filename */ |
| 3431 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3432 | const char * zUser; /* --user-override */ |
| 3433 | const char * zDate; /* --date-override */ |
| 3434 | char const * zManifestFile = 0;/* --save-manifest FILE */ |
| 3435 | |
| 3436 | /* This function should perform only the minimal "business logic" it |
| 3437 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3438 | ** then pass it on to checkin_mini() to do most of the validation |
| 3439 | ** and work. The point of this is to avoid duplicate code when a web |
| @@ -3420,10 +3444,11 @@ | |
| 3444 | zCommentFile = find_option("comment-file","M",1); |
| 3445 | zAsFilename = find_option("as",0,1); |
| 3446 | zRevision = find_option("revision","r",1); |
| 3447 | zUser = find_option("user-override",0,1); |
| 3448 | zDate = find_option("date-override",0,1); |
| 3449 | zManifestFile = find_option("save-manifest",0,1); |
| 3450 | if(find_option("wet-run",0,0)==0){ |
| 3451 | cinf.flags |= CIMINI_DRY_RUN; |
| 3452 | } |
| 3453 | if(find_option("allow-fork",0,0)!=0){ |
| 3454 | cinf.flags |= CIMINI_ALLOW_FORK; |
| @@ -3466,10 +3491,11 @@ | |
| 3491 | } |
| 3492 | if(!blob_size(&cinf.comment)){ |
| 3493 | fossil_fatal("Non-empty checkin comment is required."); |
| 3494 | } |
| 3495 | } |
| 3496 | db_begin_transaction(); |
| 3497 | zFilename = g.argv[2]; |
| 3498 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3499 | cinf.filePerm = file_perm(zFilename, ExtFILE); |
| 3500 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3501 | if(zDate){ |
| @@ -3484,24 +3510,43 @@ | |
| 3510 | } |
| 3511 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3512 | if(cinf.zParentUuid==0){ |
| 3513 | fossil_fatal("Cannot determine version to commit to."); |
| 3514 | } |
| 3515 | blob_read_from_file(&cinf.fileContent, zFilename, ExtFILE); |
| 3516 | { |
| 3517 | Blob theManifest = empty_blob; /* --save-manifest target */ |
| 3518 | Blob errMsg = empty_blob; |
| 3519 | int rc; |
| 3520 | if(zManifestFile){ |
| 3521 | cinf.pMfOut = &theManifest; |
| 3522 | } |
| 3523 | rc = checkin_mini(&cinf, &newRid, &errMsg); |
| 3524 | if(rc){ |
| 3525 | assert(blob_size(&errMsg)==0); |
| 3526 | }else{ |
| 3527 | assert(blob_size(&errMsg)); |
| 3528 | fossil_fatal("%b", &errMsg); |
| 3529 | } |
| 3530 | if(zManifestFile){ |
| 3531 | fossil_print("Writing manifest to: %s\n", zManifestFile); |
| 3532 | assert(blob_size(&theManifest)>0); |
| 3533 | blob_write_to_file(&theManifest, zManifestFile); |
| 3534 | blob_reset(&theManifest); |
| 3535 | } |
| 3536 | } |
| 3537 | if(newRid!=0){ |
| 3538 | fossil_print("New version%s: %z\n", |
| 3539 | (cinf.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", |
| 3540 | rid_to_uuid(newRid)); |
| 3541 | } |
| 3542 | db_end_transaction(0/*checkin_mini() will have triggered it to roll |
| 3543 | ** back in dry-run mode, but we need access to |
| 3544 | ** the transaction-written db state in this |
| 3545 | ** routine.*/); |
| 3546 | if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3547 | fossil_warning("The checkout state is now out of sync " |
| 3548 | "with regards to this commit. It needs to be " |
| 3549 | "'update'd or 'close'd and re-'open'ed."); |
| 3550 | } |
| 3551 | CheckinMiniInfo_cleanup(&cinf); |
| 3552 | } |
| 3553 |