Fossil SCM
File perms are now always taken from the local file. Added flag to allow addition of a new file (default is not to, for reasons explained in the comments).
Commit
c281a179c0da28fc6cc6d64bd09d7bcf09d29ebfcfd91608de69e18492161ce4
Parent
9699997444fd6d6…
1 file changed
+124
-98
+124
-98
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2692,35 +2692,43 @@ | ||
| 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 */ | |
| 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(). */ | |
| 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 (anything | |
| 2703 | + supported by date_in_standard_format()). | |
| 2704 | + Maybe be NULL. */ | |
| 2705 | + char *zFilename; /* Name of single file to commit. Must be | |
| 2706 | + relative to the top of the repo. */ | |
| 2707 | + Blob fileContent; /* Content of file referred to by zFilename. */ | |
| 2708 | + Blob fileHash; /* Hash of this->fileContent, using the repo's | |
| 2709 | + preferred hash method. */ | |
| 2710 | + int filePerm; /* Permissions (via file_perm()) of file. We | |
| 2711 | + need to store this before calling | |
| 2712 | + checkin_mini() because the real input file | |
| 2713 | + name may differ from this->zFilename and | |
| 2714 | + checkin_mini() requires the permissions of | |
| 2715 | + the original file. For web commits, set this | |
| 2716 | + to PERM_REG before calling | |
| 2717 | + checkin_mini(). */ | |
| 2718 | + int flags; /* Bitmask of fossil_cimini_flags for | |
| 2719 | + communication from checkin_mini() to | |
| 2720 | + create_manifest_mini(). */ | |
| 2714 | 2721 | }; |
| 2715 | 2722 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2716 | 2723 | /* |
| 2717 | 2724 | ** Initializes p to a known-valid default state. |
| 2718 | 2725 | */ |
| 2719 | 2726 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2720 | 2727 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2721 | 2728 | p->flags = 0; |
| 2729 | + p->filePerm = -1; | |
| 2722 | 2730 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2723 | 2731 | } |
| 2724 | 2732 | |
| 2725 | 2733 | /* |
| 2726 | 2734 | ** Frees all memory owned by p, but does not free p. |
| @@ -2784,13 +2792,16 @@ | ||
| 2784 | 2792 | ** A hint to checkin_mini() to prefer creation of a delta manifest. |
| 2785 | 2793 | */ |
| 2786 | 2794 | CIMINI_PREFER_DELTA = 1<<6, |
| 2787 | 2795 | |
| 2788 | 2796 | /* |
| 2789 | -** NOT YET IMPLEMENTED. | |
| 2797 | +** Tells checkin_mini() to permit the addition of a new file. Normally | |
| 2798 | +** this is disabled because there are many cases where it could cause | |
| 2799 | +** the inadvertent addition of a new file when an update to an | |
| 2800 | +** existing was intended, as a side-effect of name-case differences. | |
| 2790 | 2801 | */ |
| 2791 | -CIMINI_ALLOW_CLOSED_LEAF = 1<<7 | |
| 2802 | +CIMINI_ALLOW_NEW_FILE = 1<<7 | |
| 2792 | 2803 | }; |
| 2793 | 2804 | |
| 2794 | 2805 | /* |
| 2795 | 2806 | ** Handles the F-card parts for create_manifest_mini(). |
| 2796 | 2807 | ** |
| @@ -2804,11 +2815,10 @@ | ||
| 2804 | 2815 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2805 | 2816 | CheckinMiniInfo * pCI, |
| 2806 | 2817 | int asDelta, |
| 2807 | 2818 | Blob * pErr){ |
| 2808 | 2819 | ManifestFile *zFile; /* One file entry from the pCI->pParent*/ |
| 2809 | - int fperm = 0; /* file permissions */ | |
| 2810 | 2820 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2811 | 2821 | const char *zFilename = 0; /* filename for new F-card */ |
| 2812 | 2822 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2813 | 2823 | int cmp = 0; /* filename comparison result */ |
| 2814 | 2824 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| @@ -2815,17 +2825,12 @@ | ||
| 2815 | 2825 | filenames_are_case_sensitive() |
| 2816 | 2826 | ? fossil_strcmp |
| 2817 | 2827 | : fossil_stricmp; |
| 2818 | 2828 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2819 | 2829 | |
| 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 | - */ | |
| 2830 | + assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); | |
| 2831 | + assert(pCI->filePerm>=0 && "Must have been set by the caller."); | |
| 2827 | 2832 | |
| 2828 | 2833 | manifest_file_rewind(pCI->pParent); |
| 2829 | 2834 | if(asDelta){ |
| 2830 | 2835 | /* Parent is a baseline and we have only 1 file to modify, so this |
| 2831 | 2836 | ** is the simplest case... |
| @@ -2832,54 +2837,52 @@ | ||
| 2832 | 2837 | */ |
| 2833 | 2838 | assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI."); |
| 2834 | 2839 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename,0); |
| 2835 | 2840 | if(zFile==0){ |
| 2836 | 2841 | /* New file */ |
| 2837 | - fperm = file_perm(pCI->zFilename, ExtFILE); | |
| 2838 | 2842 | zFilename = pCI->zFilename; |
| 2839 | 2843 | }else{ |
| 2840 | 2844 | /* Replacement file */ |
| 2841 | - fperm = manifest_file_mperm(zFile); | |
| 2845 | + if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2846 | + goto err_no_symlink; | |
| 2847 | + } | |
| 2842 | 2848 | zFilename = zFile->zName |
| 2843 | 2849 | /* use original name in case of name-case difference */; |
| 2844 | 2850 | zFile = 0; |
| 2845 | 2851 | } |
| 2846 | 2852 | }else{ |
| 2847 | 2853 | /* 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 | - } | |
| 2854 | + while((zFile = manifest_file_next(pCI->pParent, 0)) | |
| 2855 | + && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ | |
| 2856 | + blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, | |
| 2857 | + (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2858 | + (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2857 | 2859 | } |
| 2858 | 2860 | /* Figure out file perms and name to save... */ |
| 2859 | - if(cmp==0){ | |
| 2861 | + if(zFile!=0 && cmp==0){ | |
| 2860 | 2862 | /* Match: override this F-card */ |
| 2861 | - fperm = manifest_file_mperm(zFile); | |
| 2863 | + if(manifest_file_mperm(zFile)==PERM_LNK){ | |
| 2864 | + goto err_no_symlink; | |
| 2865 | + } | |
| 2862 | 2866 | zFilename = zFile->zName |
| 2863 | 2867 | /* use original name in case of name-case difference */; |
| 2864 | 2868 | zFile = 0; |
| 2865 | 2869 | }else{ |
| 2866 | 2870 | /* This is a new file. */ |
| 2867 | - fperm = file_perm(pCI->zFilename, ExtFILE); | |
| 2871 | + assert(zFile==0); | |
| 2868 | 2872 | zFilename = pCI->zFilename; |
| 2869 | 2873 | } |
| 2870 | 2874 | } |
| 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 | + if(PERM_LNK==pCI->filePerm){ | |
| 2876 | + goto err_no_symlink; | |
| 2877 | + }else if(PERM_EXE==pCI->filePerm){ | |
| 2875 | 2878 | zPerm = " x"; |
| 2876 | 2879 | }else{ |
| 2877 | 2880 | zPerm = ""; |
| 2878 | 2881 | } |
| 2879 | 2882 | zUuid = blob_str(&pCI->fileHash); |
| 2880 | - assert(zFile ? cmp>0&&asDelta==0 : asDelta!=0||cmp==0); | |
| 2883 | + assert(zFile ? cmp>0&&asDelta==0 : 1); | |
| 2881 | 2884 | assert(zFilename); |
| 2882 | 2885 | assert(zUuid); |
| 2883 | 2886 | assert(zPerm); |
| 2884 | 2887 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2885 | 2888 | /* Non-delta: write F-cards which lexically follow pCI->zFilename */ |
| @@ -2896,12 +2899,16 @@ | ||
| 2896 | 2899 | zFile->zName, zFile->zUuid, |
| 2897 | 2900 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2898 | 2901 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2899 | 2902 | zFile = manifest_file_next(pCI->pParent, 0); |
| 2900 | 2903 | } |
| 2901 | -#undef mf_err | |
| 2902 | 2904 | return 1; |
| 2905 | +err_no_symlink: | |
| 2906 | + mf_err((pErr,"Cannot commit or overwrite symlinks " | |
| 2907 | + "via mini-checkin.")); | |
| 2908 | + return 0; | |
| 2909 | +#undef mf_err | |
| 2903 | 2910 | } |
| 2904 | 2911 | |
| 2905 | 2912 | |
| 2906 | 2913 | /* |
| 2907 | 2914 | ** Creates a manifest file, written to pOut, from the state in the |
| @@ -2982,11 +2989,14 @@ | ||
| 2982 | 2989 | ** This routine uses the state from the given fully-populated pCI |
| 2983 | 2990 | ** argument to add pCI->fileContent to the database, and create and |
| 2984 | 2991 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2985 | 2992 | ** are unchanged. |
| 2986 | 2993 | ** |
| 2987 | -** pCI may be modified as follows: | |
| 2994 | +** This function may may modify pCI as follows: | |
| 2995 | +** | |
| 2996 | +** - If Manifest pCI->pParent is NULL then it will be loaded | |
| 2997 | +** using pCI->zParentUuid. pCI->zParentUuid may not be NULL. | |
| 2988 | 2998 | ** |
| 2989 | 2999 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 2990 | 3000 | ** string. If its original value cannot be validated then |
| 2991 | 3001 | ** this function fails. If pCI->zDate is NULL, the current time |
| 2992 | 3002 | ** is used. |
| @@ -3014,29 +3024,49 @@ | ||
| 3014 | 3024 | ** enum for the docs for each flag. |
| 3015 | 3025 | */ |
| 3016 | 3026 | static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){ |
| 3017 | 3027 | Blob mf = empty_blob; /* output manifest */ |
| 3018 | 3028 | int rid = 0, frid = 0; /* various RIDs */ |
| 3019 | - const int isPrivate = content_is_private(pCI->pParent->rid); | |
| 3029 | + int isPrivate; /* whether this is private content | |
| 3030 | + or not */ | |
| 3020 | 3031 | ManifestFile * zFilePrev; /* file entry from pCI->pParent */ |
| 3021 | 3032 | int prevFRid = 0; /* RID of file's prev. version */ |
| 3022 | - | |
| 3023 | 3033 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 3024 | 3034 | |
| 3035 | + if(!(pCI->flags & CIMINI_DRY_RUN)){ | |
| 3036 | + /* Until this feature is fully vetted, disallow it in the main | |
| 3037 | + ** fossil repo unless dry-run mode is being used. */ | |
| 3038 | + char * zProjCode = db_get("project-code",0); | |
| 3039 | + assert(zProjCode); | |
| 3040 | + if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", | |
| 3041 | + zProjCode)){ | |
| 3042 | + fossil_fatal("Never, ever run this in/on the core fossil repo " | |
| 3043 | + "in non-dry-run mode until it's been well-vetted. " | |
| 3044 | + "Use a temp/test repo."); | |
| 3045 | + } | |
| 3046 | + } | |
| 3025 | 3047 | db_begin_transaction(); |
| 3026 | - if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ | |
| 3027 | - ci_err((pErr,"No such user: %s", pCI->zUser)); | |
| 3048 | + | |
| 3049 | + if(pCI->pParent==0){ | |
| 3050 | + pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); | |
| 3051 | + if(pCI->pParent==0){ | |
| 3052 | + ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); | |
| 3053 | + } | |
| 3028 | 3054 | } |
| 3055 | + | |
| 3029 | 3056 | assert(pCI->pParent->rid>0); |
| 3030 | 3057 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 3031 | 3058 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| 3032 | 3059 | /* Remember that in order to override this we'd also need to |
| 3033 | - ** cancel TAG_CLOSED on pCI->pParent. There would seem to be | |
| 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. | |
| 3060 | + ** cancel TAG_CLOSED on pCI->pParent. There would seem to be no | |
| 3061 | + ** reason we can't do that via the generated manifest, but the | |
| 3062 | + ** commit command does not offer that option, so mini-checkin | |
| 3063 | + ** probably shouldn't, either. | |
| 3037 | 3064 | */ |
| 3065 | + } | |
| 3066 | + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ | |
| 3067 | + ci_err((pErr,"No such user: %s", pCI->zUser)); | |
| 3038 | 3068 | } |
| 3039 | 3069 | if(!(CIMINI_ALLOW_FORK & pCI->flags) |
| 3040 | 3070 | && !is_a_leaf(pCI->pParent->rid)){ |
| 3041 | 3071 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 3042 | 3072 | pCI->zParentUuid)); |
| @@ -3047,36 +3077,34 @@ | ||
| 3047 | 3077 | } |
| 3048 | 3078 | if(!file_is_simple_pathname(pCI->zFilename, 1)){ |
| 3049 | 3079 | ci_err((pErr,"Invalid filename for use in a repository: %s", |
| 3050 | 3080 | pCI->zFilename)); |
| 3051 | 3081 | } |
| 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 | - */ | |
| 3058 | - char * zDVal = db_text( | |
| 3059 | - 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", | |
| 3060 | - pCI->zDate ? pCI->zDate : "now"); | |
| 3061 | - if(zDVal[0]==0){ | |
| 3062 | - fossil_free(zDVal); | |
| 3063 | - ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); | |
| 3064 | - } | |
| 3065 | - fossil_free(pCI->zDate); | |
| 3066 | - pCI->zDate = zDVal; | |
| 3067 | - } | |
| 3068 | 3082 | if(!(CIMINI_ALLOW_OLDER & pCI->flags) |
| 3069 | 3083 | && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ |
| 3070 | 3084 | ci_err((pErr,"Checkin time (%s) may not be older " |
| 3071 | 3085 | "than its parent (%z).", |
| 3072 | 3086 | pCI->zDate, |
| 3073 | 3087 | db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", |
| 3074 | 3088 | pCI->pParent->rDate) |
| 3075 | 3089 | )); |
| 3076 | 3090 | } |
| 3077 | - | |
| 3091 | + { | |
| 3092 | + /* | |
| 3093 | + ** Normalize the timestamp. We don't use date_in_standard_format() | |
| 3094 | + ** because that has side-effects we don't want to trigger here. | |
| 3095 | + */ | |
| 3096 | + char * zDVal = db_text( | |
| 3097 | + 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", | |
| 3098 | + pCI->zDate ? pCI->zDate : "now"); | |
| 3099 | + if(zDVal==0 || zDVal[0]==0){ | |
| 3100 | + fossil_free(zDVal); | |
| 3101 | + ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); | |
| 3102 | + } | |
| 3103 | + fossil_free(pCI->zDate); | |
| 3104 | + pCI->zDate = zDVal; | |
| 3105 | + } | |
| 3078 | 3106 | /* Potential TODOs include: |
| 3079 | 3107 | ** |
| 3080 | 3108 | ** - Commit allows an empty checkin only with a flag, but we |
| 3081 | 3109 | ** currently disallow it entirely. Conform with commit? |
| 3082 | 3110 | ** |
| @@ -3086,25 +3114,24 @@ | ||
| 3086 | 3114 | ** code cannot do if it's going to be run via a web page. |
| 3087 | 3115 | */ |
| 3088 | 3116 | |
| 3089 | 3117 | /* |
| 3090 | 3118 | ** Confirm that pCI->zFilename can be found in pCI->pParent. If |
| 3091 | - ** not, fail. This is admittedly an artificial limitation, not | |
| 3092 | - ** strictly necessary. We do it to hopefully reduce the chance of an | |
| 3093 | - ** "oops" where file X/Y/z gets committed as X/Y/Z due to a typo or | |
| 3119 | + ** not, fail unless the CIMINI_ALLOW_NEW_FILE flag is set. This is | |
| 3120 | + ** admittedly an artificial limitation, not strictly necessary. We | |
| 3121 | + ** do it to hopefully reduce the chance of an "oops" where file | |
| 3122 | + ** X/Y/z gets committed as X/Y/Z or X/y/z due to a typo or | |
| 3094 | 3123 | ** case-sensitivity mismatch between the user/repo/filesystem, or |
| 3095 | - ** some such. That said, the remainder of this function is written | |
| 3096 | - ** as if this check did not exist, so enabling it "should" just be a | |
| 3097 | - ** matter of removing this check or guarding it with a flag. | |
| 3124 | + ** some such. | |
| 3098 | 3125 | */ |
| 3099 | 3126 | manifest_file_rewind(pCI->pParent); |
| 3100 | 3127 | zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 3101 | - if(!zFilePrev){ | |
| 3128 | + if(!zFilePrev && !(CIMINI_ALLOW_NEW_FILE & pCI->flags)){ | |
| 3102 | 3129 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3103 | 3130 | "Adding new files is currently not permitted.", |
| 3104 | 3131 | pCI->zFilename, pCI->zParentUuid)); |
| 3105 | - }else if(zFilePrev->zPerm | |
| 3132 | + }else if(zFilePrev | |
| 3106 | 3133 | && manifest_file_mperm(zFilePrev)==PERM_LNK){ |
| 3107 | 3134 | ci_err((pErr,"Cannot save a symlink via a mini-checkin.")); |
| 3108 | 3135 | } |
| 3109 | 3136 | if(zFilePrev){ |
| 3110 | 3137 | prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); |
| @@ -3174,10 +3201,11 @@ | ||
| 3174 | 3201 | } |
| 3175 | 3202 | /* Create, save, deltify, and crosslink the manifest... */ |
| 3176 | 3203 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3177 | 3204 | return 0; |
| 3178 | 3205 | } |
| 3206 | + isPrivate = content_is_private(pCI->pParent->rid); | |
| 3179 | 3207 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3180 | 3208 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3181 | 3209 | if(pCI->flags & CIMINI_DUMP_MANIFEST){ |
| 3182 | 3210 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3183 | 3211 | } |
| @@ -3237,10 +3265,15 @@ | ||
| 3237 | 3265 | ** the previous version's content. Does not |
| 3238 | 3266 | ** modify the original file, only the |
| 3239 | 3267 | ** checked-in content. |
| 3240 | 3268 | ** --delta Prefer to generate a delta manifest, if |
| 3241 | 3269 | ** able. |
| 3270 | +** --allow-new-file Allow addition of a new file this way. | |
| 3271 | +** Disabled by default to avoid that case- | |
| 3272 | +** sensitivity errors inadvertently lead to | |
| 3273 | +** adding a new file where an update is | |
| 3274 | +** intended. | |
| 3242 | 3275 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3243 | 3276 | ** --wet-run Disables the default dry-run mode. |
| 3244 | 3277 | ** |
| 3245 | 3278 | ** Example: |
| 3246 | 3279 | ** |
| @@ -3256,10 +3289,16 @@ | ||
| 3256 | 3289 | const char * zAsFilename; /* --as filename */ |
| 3257 | 3290 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3258 | 3291 | const char * zUser; /* --user-override */ |
| 3259 | 3292 | const char * zDate; /* --date-override */ |
| 3260 | 3293 | |
| 3294 | + /* This function should perform only the minimal "business logic" it | |
| 3295 | + ** needs in order to fully/properly populate the CheckinMiniInfo and | |
| 3296 | + ** then pass it on to checkin_mini() to do most of the validation | |
| 3297 | + ** and work. The point of this is to avoid duplicate code when a web | |
| 3298 | + ** front-end is added for checkin_mini(). | |
| 3299 | + */ | |
| 3261 | 3300 | CheckinMiniInfo_init(&cinf); |
| 3262 | 3301 | zComment = find_option("comment","m",1); |
| 3263 | 3302 | zCommentFile = find_option("comment-file","M",1); |
| 3264 | 3303 | zAsFilename = find_option("as",0,1); |
| 3265 | 3304 | zRevision = find_option("revision","r",1); |
| @@ -3284,30 +3323,19 @@ | ||
| 3284 | 3323 | cinf.flags |= CIMINI_CONVERT_EOL; |
| 3285 | 3324 | } |
| 3286 | 3325 | if(find_option("delta",0,0)!=0){ |
| 3287 | 3326 | cinf.flags |= CIMINI_PREFER_DELTA; |
| 3288 | 3327 | } |
| 3328 | + if(find_option("allow-new-file",0,0)!=0){ | |
| 3329 | + cinf.flags |= CIMINI_ALLOW_NEW_FILE; | |
| 3330 | + } | |
| 3289 | 3331 | db_find_and_open_repository(0, 0); |
| 3290 | 3332 | verify_all_options(); |
| 3291 | 3333 | user_select(); |
| 3292 | 3334 | if(g.argc!=3){ |
| 3293 | 3335 | usage("INFILE"); |
| 3294 | 3336 | } |
| 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 " | |
| 3304 | - "until it's been well-vetted. Use a temp/test " | |
| 3305 | - "repo."); | |
| 3306 | - } | |
| 3307 | - } | |
| 3308 | - | |
| 3309 | 3337 | if(zComment && zCommentFile){ |
| 3310 | 3338 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3311 | 3339 | }else{ |
| 3312 | 3340 | if(zCommentFile && *zCommentFile){ |
| 3313 | 3341 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| @@ -3316,13 +3344,13 @@ | ||
| 3316 | 3344 | } |
| 3317 | 3345 | if(!blob_size(&cinf.comment)){ |
| 3318 | 3346 | fossil_fatal("Non-empty checkin comment is required."); |
| 3319 | 3347 | } |
| 3320 | 3348 | } |
| 3321 | - | |
| 3322 | 3349 | zFilename = g.argv[2]; |
| 3323 | 3350 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3351 | + cinf.filePerm = file_perm(zFilename, ExtFILE); | |
| 3324 | 3352 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3325 | 3353 | if(zDate){ |
| 3326 | 3354 | cinf.zDate = mprintf("%s", zDate); |
| 3327 | 3355 | } |
| 3328 | 3356 | if(zRevision==0 || zRevision[0]==0){ |
| @@ -3334,12 +3362,10 @@ | ||
| 3334 | 3362 | } |
| 3335 | 3363 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3336 | 3364 | if(cinf.zParentUuid==0){ |
| 3337 | 3365 | fossil_fatal("Cannot determine version to commit to."); |
| 3338 | 3366 | } |
| 3339 | - cinf.pParent = manifest_get_by_name(cinf.zParentUuid, 0); | |
| 3340 | - assert(cinf.pParent!=0); | |
| 3341 | 3367 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3342 | 3368 | ExtFILE/*may want to reconsider*/); |
| 3343 | 3369 | { |
| 3344 | 3370 | Blob errMsg = empty_blob; |
| 3345 | 3371 | const int rc = checkin_mini(&cinf, &newRid, &errMsg); |
| 3346 | 3372 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2692,35 +2692,43 @@ | |
| 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. |
| @@ -2784,13 +2792,16 @@ | |
| 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. |
| 2790 | */ |
| 2791 | CIMINI_ALLOW_CLOSED_LEAF = 1<<7 |
| 2792 | }; |
| 2793 | |
| 2794 | /* |
| 2795 | ** Handles the F-card parts for create_manifest_mini(). |
| 2796 | ** |
| @@ -2804,11 +2815,10 @@ | |
| 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,17 +2825,12 @@ | |
| 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,54 +2837,52 @@ | |
| 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 */ |
| @@ -2896,12 +2899,16 @@ | |
| 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 |
| @@ -2982,11 +2989,14 @@ | |
| 2982 | ** This routine uses the state from the given fully-populated pCI |
| 2983 | ** argument to add pCI->fileContent to the database, and create and |
| 2984 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2985 | ** are unchanged. |
| 2986 | ** |
| 2987 | ** pCI may be modified as follows: |
| 2988 | ** |
| 2989 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 2990 | ** string. If its original value cannot be validated then |
| 2991 | ** this function fails. If pCI->zDate is NULL, the current time |
| 2992 | ** is used. |
| @@ -3014,29 +3024,49 @@ | |
| 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 */ |
| 3022 | |
| 3023 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 3024 | |
| 3025 | db_begin_transaction(); |
| 3026 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 3027 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 3028 | } |
| 3029 | assert(pCI->pParent->rid>0); |
| 3030 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 3031 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| 3032 | /* Remember that in order to override this we'd also need to |
| 3033 | ** cancel TAG_CLOSED on pCI->pParent. There would seem to be |
| 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)); |
| @@ -3047,36 +3077,34 @@ | |
| 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 | */ |
| 3058 | char * zDVal = db_text( |
| 3059 | 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", |
| 3060 | pCI->zDate ? pCI->zDate : "now"); |
| 3061 | if(zDVal[0]==0){ |
| 3062 | fossil_free(zDVal); |
| 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)", |
| 3074 | pCI->pParent->rDate) |
| 3075 | )); |
| 3076 | } |
| 3077 | |
| 3078 | /* Potential TODOs include: |
| 3079 | ** |
| 3080 | ** - Commit allows an empty checkin only with a flag, but we |
| 3081 | ** currently disallow it entirely. Conform with commit? |
| 3082 | ** |
| @@ -3086,25 +3114,24 @@ | |
| 3086 | ** code cannot do if it's going to be run via a web page. |
| 3087 | */ |
| 3088 | |
| 3089 | /* |
| 3090 | ** Confirm that pCI->zFilename can be found in pCI->pParent. If |
| 3091 | ** not, fail. This is admittedly an artificial limitation, not |
| 3092 | ** strictly necessary. We do it to hopefully reduce the chance of an |
| 3093 | ** "oops" where file X/Y/z gets committed as X/Y/Z due to a typo or |
| 3094 | ** case-sensitivity mismatch between the user/repo/filesystem, or |
| 3095 | ** some such. That said, the remainder of this function is written |
| 3096 | ** as if this check did not exist, so enabling it "should" just be a |
| 3097 | ** matter of removing this check or guarding it with a flag. |
| 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); |
| @@ -3174,10 +3201,11 @@ | |
| 3174 | } |
| 3175 | /* Create, save, deltify, and crosslink the manifest... */ |
| 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 | } |
| @@ -3237,10 +3265,15 @@ | |
| 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 | ** |
| @@ -3256,10 +3289,16 @@ | |
| 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); |
| @@ -3284,30 +3323,19 @@ | |
| 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 " |
| 3304 | "until it's been well-vetted. Use a temp/test " |
| 3305 | "repo."); |
| 3306 | } |
| 3307 | } |
| 3308 | |
| 3309 | if(zComment && zCommentFile){ |
| 3310 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3311 | }else{ |
| 3312 | if(zCommentFile && *zCommentFile){ |
| 3313 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| @@ -3316,13 +3344,13 @@ | |
| 3316 | } |
| 3317 | if(!blob_size(&cinf.comment)){ |
| 3318 | fossil_fatal("Non-empty checkin comment is required."); |
| 3319 | } |
| 3320 | } |
| 3321 | |
| 3322 | zFilename = g.argv[2]; |
| 3323 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3324 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3325 | if(zDate){ |
| 3326 | cinf.zDate = mprintf("%s", zDate); |
| 3327 | } |
| 3328 | if(zRevision==0 || zRevision[0]==0){ |
| @@ -3334,12 +3362,10 @@ | |
| 3334 | } |
| 3335 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3336 | if(cinf.zParentUuid==0){ |
| 3337 | fossil_fatal("Cannot determine version to commit to."); |
| 3338 | } |
| 3339 | cinf.pParent = manifest_get_by_name(cinf.zParentUuid, 0); |
| 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 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2692,35 +2692,43 @@ | |
| 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 (anything |
| 2703 | supported by date_in_standard_format()). |
| 2704 | Maybe be NULL. */ |
| 2705 | char *zFilename; /* Name of single file to commit. Must be |
| 2706 | relative to the top of the repo. */ |
| 2707 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2708 | Blob fileHash; /* Hash of this->fileContent, using the repo's |
| 2709 | preferred hash method. */ |
| 2710 | int filePerm; /* Permissions (via file_perm()) of file. We |
| 2711 | need to store this before calling |
| 2712 | checkin_mini() because the real input file |
| 2713 | name may differ from this->zFilename and |
| 2714 | checkin_mini() requires the permissions of |
| 2715 | the original file. For web commits, set this |
| 2716 | to PERM_REG before calling |
| 2717 | checkin_mini(). */ |
| 2718 | int flags; /* Bitmask of fossil_cimini_flags for |
| 2719 | communication from checkin_mini() to |
| 2720 | create_manifest_mini(). */ |
| 2721 | }; |
| 2722 | typedef struct CheckinMiniInfo CheckinMiniInfo; |
| 2723 | /* |
| 2724 | ** Initializes p to a known-valid default state. |
| 2725 | */ |
| 2726 | static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ |
| 2727 | memset(p, 0, sizeof(CheckinMiniInfo)); |
| 2728 | p->flags = 0; |
| 2729 | p->filePerm = -1; |
| 2730 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2731 | } |
| 2732 | |
| 2733 | /* |
| 2734 | ** Frees all memory owned by p, but does not free p. |
| @@ -2784,13 +2792,16 @@ | |
| 2792 | ** A hint to checkin_mini() to prefer creation of a delta manifest. |
| 2793 | */ |
| 2794 | CIMINI_PREFER_DELTA = 1<<6, |
| 2795 | |
| 2796 | /* |
| 2797 | ** Tells checkin_mini() to permit the addition of a new file. Normally |
| 2798 | ** this is disabled because there are many cases where it could cause |
| 2799 | ** the inadvertent addition of a new file when an update to an |
| 2800 | ** existing was intended, as a side-effect of name-case differences. |
| 2801 | */ |
| 2802 | CIMINI_ALLOW_NEW_FILE = 1<<7 |
| 2803 | }; |
| 2804 | |
| 2805 | /* |
| 2806 | ** Handles the F-card parts for create_manifest_mini(). |
| 2807 | ** |
| @@ -2804,11 +2815,10 @@ | |
| 2815 | static int create_manifest_mini_fcards( Blob * pOut, |
| 2816 | CheckinMiniInfo * pCI, |
| 2817 | int asDelta, |
| 2818 | Blob * pErr){ |
| 2819 | ManifestFile *zFile; /* One file entry from the pCI->pParent*/ |
| 2820 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2821 | const char *zFilename = 0; /* filename for new F-card */ |
| 2822 | const char *zUuid = 0; /* UUID for new F-card */ |
| 2823 | int cmp = 0; /* filename comparison result */ |
| 2824 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| @@ -2815,17 +2825,12 @@ | |
| 2825 | filenames_are_case_sensitive() |
| 2826 | ? fossil_strcmp |
| 2827 | : fossil_stricmp; |
| 2828 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2829 | |
| 2830 | assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); |
| 2831 | assert(pCI->filePerm>=0 && "Must have been set by the caller."); |
| 2832 | |
| 2833 | manifest_file_rewind(pCI->pParent); |
| 2834 | if(asDelta){ |
| 2835 | /* Parent is a baseline and we have only 1 file to modify, so this |
| 2836 | ** is the simplest case... |
| @@ -2832,54 +2837,52 @@ | |
| 2837 | */ |
| 2838 | assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI."); |
| 2839 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename,0); |
| 2840 | if(zFile==0){ |
| 2841 | /* New file */ |
| 2842 | zFilename = pCI->zFilename; |
| 2843 | }else{ |
| 2844 | /* Replacement file */ |
| 2845 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2846 | goto err_no_symlink; |
| 2847 | } |
| 2848 | zFilename = zFile->zName |
| 2849 | /* use original name in case of name-case difference */; |
| 2850 | zFile = 0; |
| 2851 | } |
| 2852 | }else{ |
| 2853 | /* Non-delta: write F-cards which lexically preceed pCI->zFilename */ |
| 2854 | while((zFile = manifest_file_next(pCI->pParent, 0)) |
| 2855 | && (cmp = fncmp(zFile->zName, pCI->zFilename))<0){ |
| 2856 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| 2857 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2858 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2859 | } |
| 2860 | /* Figure out file perms and name to save... */ |
| 2861 | if(zFile!=0 && cmp==0){ |
| 2862 | /* Match: override this F-card */ |
| 2863 | if(manifest_file_mperm(zFile)==PERM_LNK){ |
| 2864 | goto err_no_symlink; |
| 2865 | } |
| 2866 | zFilename = zFile->zName |
| 2867 | /* use original name in case of name-case difference */; |
| 2868 | zFile = 0; |
| 2869 | }else{ |
| 2870 | /* This is a new file. */ |
| 2871 | assert(zFile==0); |
| 2872 | zFilename = pCI->zFilename; |
| 2873 | } |
| 2874 | } |
| 2875 | if(PERM_LNK==pCI->filePerm){ |
| 2876 | goto err_no_symlink; |
| 2877 | }else if(PERM_EXE==pCI->filePerm){ |
| 2878 | zPerm = " x"; |
| 2879 | }else{ |
| 2880 | zPerm = ""; |
| 2881 | } |
| 2882 | zUuid = blob_str(&pCI->fileHash); |
| 2883 | assert(zFile ? cmp>0&&asDelta==0 : 1); |
| 2884 | assert(zFilename); |
| 2885 | assert(zUuid); |
| 2886 | assert(zPerm); |
| 2887 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2888 | /* Non-delta: write F-cards which lexically follow pCI->zFilename */ |
| @@ -2896,12 +2899,16 @@ | |
| 2899 | zFile->zName, zFile->zUuid, |
| 2900 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2901 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2902 | zFile = manifest_file_next(pCI->pParent, 0); |
| 2903 | } |
| 2904 | return 1; |
| 2905 | err_no_symlink: |
| 2906 | mf_err((pErr,"Cannot commit or overwrite symlinks " |
| 2907 | "via mini-checkin.")); |
| 2908 | return 0; |
| 2909 | #undef mf_err |
| 2910 | } |
| 2911 | |
| 2912 | |
| 2913 | /* |
| 2914 | ** Creates a manifest file, written to pOut, from the state in the |
| @@ -2982,11 +2989,14 @@ | |
| 2989 | ** This routine uses the state from the given fully-populated pCI |
| 2990 | ** argument to add pCI->fileContent to the database, and create and |
| 2991 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2992 | ** are unchanged. |
| 2993 | ** |
| 2994 | ** This function may may modify pCI as follows: |
| 2995 | ** |
| 2996 | ** - If Manifest pCI->pParent is NULL then it will be loaded |
| 2997 | ** using pCI->zParentUuid. pCI->zParentUuid may not be NULL. |
| 2998 | ** |
| 2999 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 3000 | ** string. If its original value cannot be validated then |
| 3001 | ** this function fails. If pCI->zDate is NULL, the current time |
| 3002 | ** is used. |
| @@ -3014,29 +3024,49 @@ | |
| 3024 | ** enum for the docs for each flag. |
| 3025 | */ |
| 3026 | static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){ |
| 3027 | Blob mf = empty_blob; /* output manifest */ |
| 3028 | int rid = 0, frid = 0; /* various RIDs */ |
| 3029 | int isPrivate; /* whether this is private content |
| 3030 | or not */ |
| 3031 | ManifestFile * zFilePrev; /* file entry from pCI->pParent */ |
| 3032 | int prevFRid = 0; /* RID of file's prev. version */ |
| 3033 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 3034 | |
| 3035 | if(!(pCI->flags & CIMINI_DRY_RUN)){ |
| 3036 | /* Until this feature is fully vetted, disallow it in the main |
| 3037 | ** fossil repo unless dry-run mode is being used. */ |
| 3038 | char * zProjCode = db_get("project-code",0); |
| 3039 | assert(zProjCode); |
| 3040 | if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", |
| 3041 | zProjCode)){ |
| 3042 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| 3043 | "in non-dry-run mode until it's been well-vetted. " |
| 3044 | "Use a temp/test repo."); |
| 3045 | } |
| 3046 | } |
| 3047 | db_begin_transaction(); |
| 3048 | |
| 3049 | if(pCI->pParent==0){ |
| 3050 | pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); |
| 3051 | if(pCI->pParent==0){ |
| 3052 | ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); |
| 3053 | } |
| 3054 | } |
| 3055 | |
| 3056 | assert(pCI->pParent->rid>0); |
| 3057 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 3058 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| 3059 | /* Remember that in order to override this we'd also need to |
| 3060 | ** cancel TAG_CLOSED on pCI->pParent. There would seem to be no |
| 3061 | ** reason we can't do that via the generated manifest, but the |
| 3062 | ** commit command does not offer that option, so mini-checkin |
| 3063 | ** probably shouldn't, either. |
| 3064 | */ |
| 3065 | } |
| 3066 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 3067 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 3068 | } |
| 3069 | if(!(CIMINI_ALLOW_FORK & pCI->flags) |
| 3070 | && !is_a_leaf(pCI->pParent->rid)){ |
| 3071 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 3072 | pCI->zParentUuid)); |
| @@ -3047,36 +3077,34 @@ | |
| 3077 | } |
| 3078 | if(!file_is_simple_pathname(pCI->zFilename, 1)){ |
| 3079 | ci_err((pErr,"Invalid filename for use in a repository: %s", |
| 3080 | pCI->zFilename)); |
| 3081 | } |
| 3082 | if(!(CIMINI_ALLOW_OLDER & pCI->flags) |
| 3083 | && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ |
| 3084 | ci_err((pErr,"Checkin time (%s) may not be older " |
| 3085 | "than its parent (%z).", |
| 3086 | pCI->zDate, |
| 3087 | db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", |
| 3088 | pCI->pParent->rDate) |
| 3089 | )); |
| 3090 | } |
| 3091 | { |
| 3092 | /* |
| 3093 | ** Normalize the timestamp. We don't use date_in_standard_format() |
| 3094 | ** because that has side-effects we don't want to trigger here. |
| 3095 | */ |
| 3096 | char * zDVal = db_text( |
| 3097 | 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", |
| 3098 | pCI->zDate ? pCI->zDate : "now"); |
| 3099 | if(zDVal==0 || zDVal[0]==0){ |
| 3100 | fossil_free(zDVal); |
| 3101 | ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); |
| 3102 | } |
| 3103 | fossil_free(pCI->zDate); |
| 3104 | pCI->zDate = zDVal; |
| 3105 | } |
| 3106 | /* Potential TODOs include: |
| 3107 | ** |
| 3108 | ** - Commit allows an empty checkin only with a flag, but we |
| 3109 | ** currently disallow it entirely. Conform with commit? |
| 3110 | ** |
| @@ -3086,25 +3114,24 @@ | |
| 3114 | ** code cannot do if it's going to be run via a web page. |
| 3115 | */ |
| 3116 | |
| 3117 | /* |
| 3118 | ** Confirm that pCI->zFilename can be found in pCI->pParent. If |
| 3119 | ** not, fail unless the CIMINI_ALLOW_NEW_FILE flag is set. This is |
| 3120 | ** admittedly an artificial limitation, not strictly necessary. We |
| 3121 | ** do it to hopefully reduce the chance of an "oops" where file |
| 3122 | ** X/Y/z gets committed as X/Y/Z or X/y/z due to a typo or |
| 3123 | ** case-sensitivity mismatch between the user/repo/filesystem, or |
| 3124 | ** some such. |
| 3125 | */ |
| 3126 | manifest_file_rewind(pCI->pParent); |
| 3127 | zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 3128 | if(!zFilePrev && !(CIMINI_ALLOW_NEW_FILE & pCI->flags)){ |
| 3129 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3130 | "Adding new files is currently not permitted.", |
| 3131 | pCI->zFilename, pCI->zParentUuid)); |
| 3132 | }else if(zFilePrev |
| 3133 | && manifest_file_mperm(zFilePrev)==PERM_LNK){ |
| 3134 | ci_err((pErr,"Cannot save a symlink via a mini-checkin.")); |
| 3135 | } |
| 3136 | if(zFilePrev){ |
| 3137 | prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); |
| @@ -3174,10 +3201,11 @@ | |
| 3201 | } |
| 3202 | /* Create, save, deltify, and crosslink the manifest... */ |
| 3203 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3204 | return 0; |
| 3205 | } |
| 3206 | isPrivate = content_is_private(pCI->pParent->rid); |
| 3207 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3208 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3209 | if(pCI->flags & CIMINI_DUMP_MANIFEST){ |
| 3210 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3211 | } |
| @@ -3237,10 +3265,15 @@ | |
| 3265 | ** the previous version's content. Does not |
| 3266 | ** modify the original file, only the |
| 3267 | ** checked-in content. |
| 3268 | ** --delta Prefer to generate a delta manifest, if |
| 3269 | ** able. |
| 3270 | ** --allow-new-file Allow addition of a new file this way. |
| 3271 | ** Disabled by default to avoid that case- |
| 3272 | ** sensitivity errors inadvertently lead to |
| 3273 | ** adding a new file where an update is |
| 3274 | ** intended. |
| 3275 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3276 | ** --wet-run Disables the default dry-run mode. |
| 3277 | ** |
| 3278 | ** Example: |
| 3279 | ** |
| @@ -3256,10 +3289,16 @@ | |
| 3289 | const char * zAsFilename; /* --as filename */ |
| 3290 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3291 | const char * zUser; /* --user-override */ |
| 3292 | const char * zDate; /* --date-override */ |
| 3293 | |
| 3294 | /* This function should perform only the minimal "business logic" it |
| 3295 | ** needs in order to fully/properly populate the CheckinMiniInfo and |
| 3296 | ** then pass it on to checkin_mini() to do most of the validation |
| 3297 | ** and work. The point of this is to avoid duplicate code when a web |
| 3298 | ** front-end is added for checkin_mini(). |
| 3299 | */ |
| 3300 | CheckinMiniInfo_init(&cinf); |
| 3301 | zComment = find_option("comment","m",1); |
| 3302 | zCommentFile = find_option("comment-file","M",1); |
| 3303 | zAsFilename = find_option("as",0,1); |
| 3304 | zRevision = find_option("revision","r",1); |
| @@ -3284,30 +3323,19 @@ | |
| 3323 | cinf.flags |= CIMINI_CONVERT_EOL; |
| 3324 | } |
| 3325 | if(find_option("delta",0,0)!=0){ |
| 3326 | cinf.flags |= CIMINI_PREFER_DELTA; |
| 3327 | } |
| 3328 | if(find_option("allow-new-file",0,0)!=0){ |
| 3329 | cinf.flags |= CIMINI_ALLOW_NEW_FILE; |
| 3330 | } |
| 3331 | db_find_and_open_repository(0, 0); |
| 3332 | verify_all_options(); |
| 3333 | user_select(); |
| 3334 | if(g.argc!=3){ |
| 3335 | usage("INFILE"); |
| 3336 | } |
| 3337 | if(zComment && zCommentFile){ |
| 3338 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3339 | }else{ |
| 3340 | if(zCommentFile && *zCommentFile){ |
| 3341 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| @@ -3316,13 +3344,13 @@ | |
| 3344 | } |
| 3345 | if(!blob_size(&cinf.comment)){ |
| 3346 | fossil_fatal("Non-empty checkin comment is required."); |
| 3347 | } |
| 3348 | } |
| 3349 | zFilename = g.argv[2]; |
| 3350 | cinf.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); |
| 3351 | cinf.filePerm = file_perm(zFilename, ExtFILE); |
| 3352 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3353 | if(zDate){ |
| 3354 | cinf.zDate = mprintf("%s", zDate); |
| 3355 | } |
| 3356 | if(zRevision==0 || zRevision[0]==0){ |
| @@ -3334,12 +3362,10 @@ | |
| 3362 | } |
| 3363 | name_to_uuid2(zRevision, "ci", &cinf.zParentUuid); |
| 3364 | if(cinf.zParentUuid==0){ |
| 3365 | fossil_fatal("Cannot determine version to commit to."); |
| 3366 | } |
| 3367 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3368 | ExtFILE/*may want to reconsider*/); |
| 3369 | { |
| 3370 | Blob errMsg = empty_blob; |
| 3371 | const int rc = checkin_mini(&cinf, &newRid, &errMsg); |
| 3372 |