Fossil SCM
Various unrelated improvements in the single-file commit process. Moved leaf-is-closed check into its own function (affects the commit command).
Commit
3a71400423dcfdc595e707779bde9d20fe337cb9583fc10be1d300f41f07790c
Parent
1d0cc12583de397…
2 files changed
+69
-33
+11
+69
-33
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2321,13 +2321,11 @@ | ||
| 2321 | 2321 | ** Do not allow a commit against a closed leaf unless the commit |
| 2322 | 2322 | ** ends up on a different branch. |
| 2323 | 2323 | */ |
| 2324 | 2324 | if( |
| 2325 | 2325 | /* parent check-in has the "closed" tag... */ |
| 2326 | - db_exists("SELECT 1 FROM tagxref" | |
| 2327 | - " WHERE tagid=%d AND rid=%d AND tagtype>0", | |
| 2328 | - TAG_CLOSED, vid) | |
| 2326 | + leaf_is_closed(vid) | |
| 2329 | 2327 | /* ... and the new check-in has no --branch option or the --branch |
| 2330 | 2328 | ** option does not actually change the branch */ |
| 2331 | 2329 | && (sCiInfo.zBranch==0 |
| 2332 | 2330 | || db_exists("SELECT 1 FROM tagxref" |
| 2333 | 2331 | " WHERE tagid=%d AND rid=%d AND tagtype>0" |
| @@ -2673,15 +2671,17 @@ | ||
| 2673 | 2671 | if( count_nonbranch_children(vid)>1 ){ |
| 2674 | 2672 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2675 | 2673 | } |
| 2676 | 2674 | } |
| 2677 | 2675 | |
| 2678 | -#if INTERFACE | |
| 2679 | 2676 | /* |
| 2680 | 2677 | ** State for the "web-checkin" infrastructure, which enables the |
| 2681 | 2678 | ** ability to commit changes to a single file via an HTTP request. |
| 2682 | - */ | |
| 2679 | +** | |
| 2680 | +** Memory for all non-const (char *) members is owned by the | |
| 2681 | +** CheckinOneFileInfo instance. | |
| 2682 | +*/ | |
| 2683 | 2683 | struct CheckinOneFileInfo { |
| 2684 | 2684 | Blob comment; /* Check-in comment text */ |
| 2685 | 2685 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2686 | 2686 | Manifest * pParent; /* parent checkin */ |
| 2687 | 2687 | char *zParentUuid; /* UUID of pParent */ |
| @@ -2688,21 +2688,22 @@ | ||
| 2688 | 2688 | char *zUser; /* User name */ |
| 2689 | 2689 | char *zDate; /* Optionally force this date string |
| 2690 | 2690 | (anything supported by |
| 2691 | 2691 | date_in_standard_format(). |
| 2692 | 2692 | Maybe be NULL. */ |
| 2693 | - char *zFilename; /* Name of single file to commit. */ | |
| 2694 | - Blob fileContent; /* Content of the modified file. */ | |
| 2695 | - Blob fileHash; /* Hash of this->fileContent */ | |
| 2693 | + char *zFilename; /* Name of single file to commit. Must be | |
| 2694 | + relative to the top of the repo. */ | |
| 2695 | + Blob fileContent; /* Content of file referred to by zFilename. */ | |
| 2696 | + Blob fileHash; /* Hash of this->fileContent, using the | |
| 2697 | + repo's preferred hash method. */ | |
| 2696 | 2698 | }; |
| 2697 | -#endif /* INTERFACE */ | |
| 2698 | - | |
| 2699 | +typedef struct CheckinOneFileInfo CheckinOneFileInfo; | |
| 2699 | 2700 | /* |
| 2700 | 2701 | ** Initializes p to a known-valid default state. |
| 2701 | 2702 | */ |
| 2702 | 2703 | static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ |
| 2703 | - memset(p, 0, sizeof(struct CheckinOneFileInfo)); | |
| 2704 | + memset(p, 0, sizeof(CheckinOneFileInfo)); | |
| 2704 | 2705 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2705 | 2706 | } |
| 2706 | 2707 | |
| 2707 | 2708 | /* |
| 2708 | 2709 | ** Frees all memory owned by p, but does not free p. |
| @@ -2735,11 +2736,22 @@ | ||
| 2735 | 2736 | */ |
| 2736 | 2737 | CKIN1_ALLOW_FORK = 1<<1, |
| 2737 | 2738 | /* |
| 2738 | 2739 | ** Tells checkin_one_file() to dump its generated manifest to stdout. |
| 2739 | 2740 | */ |
| 2740 | -CKIN1_DUMP_MANIFEST = 1<<2 | |
| 2741 | +CKIN1_DUMP_MANIFEST = 1<<2, | |
| 2742 | + | |
| 2743 | +/* | |
| 2744 | +** By default, content containing what appears to be a merge conflict | |
| 2745 | +** marker is not permitted. This flag relaxes that requirement. | |
| 2746 | +*/ | |
| 2747 | +CKIN1_ALLOW_MERGE_MARKER = 1<<3, | |
| 2748 | + | |
| 2749 | +/* | |
| 2750 | +** NOT YET IMPLEMENTED. | |
| 2751 | +*/ | |
| 2752 | +CKIN1_ALLOW_CLOSED_LEAF = 1<<4 | |
| 2741 | 2753 | }; |
| 2742 | 2754 | |
| 2743 | 2755 | /* |
| 2744 | 2756 | ** Creates a manifest file, written to pOut, from the state in the |
| 2745 | 2757 | ** fully-populated pCI argument. Returns true on success. On error, |
| @@ -2856,13 +2868,11 @@ | ||
| 2856 | 2868 | |
| 2857 | 2869 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2858 | 2870 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2859 | 2871 | } |
| 2860 | 2872 | |
| 2861 | - blob_appendf(pOut, "P %z\n", | |
| 2862 | - db_text(0,"SELECT uuid FROM blob WHERE rid=%d", | |
| 2863 | - pCI->pParent->rid)); | |
| 2873 | + blob_appendf(pOut, "P %s\n", pCI->zParentUuid); | |
| 2864 | 2874 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2865 | 2875 | md5sum_blob(pOut, &zCard); |
| 2866 | 2876 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2867 | 2877 | blob_reset(&zCard); |
| 2868 | 2878 | return 1; |
| @@ -2879,10 +2889,13 @@ | ||
| 2879 | 2889 | ** |
| 2880 | 2890 | ** This routine uses the state from the given fully-populated pCI |
| 2881 | 2891 | ** argument to add pCI->fileContent to the database, and create and |
| 2882 | 2892 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2883 | 2893 | ** are unchanged. |
| 2894 | +** | |
| 2895 | +** If pCI->fileHash is empty, this routine populates it with the | |
| 2896 | +** repository's preferred hash algorithm. | |
| 2884 | 2897 | ** |
| 2885 | 2898 | ** On error, returns false (0) and, if pErr is not NULL, writes a |
| 2886 | 2899 | ** diagnostic message there. |
| 2887 | 2900 | ** |
| 2888 | 2901 | ** Returns true on success. If pRid is not NULL, the RID of the |
| @@ -2890,12 +2903,12 @@ | ||
| 2890 | 2903 | ** |
| 2891 | 2904 | ** ckin1Flags is a bitmask of optional flags from fossil_ckin1_flags |
| 2892 | 2905 | ** enum. See that enum for the docs for each flag. |
| 2893 | 2906 | ** |
| 2894 | 2907 | */ |
| 2895 | -int checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, int ckin1Flags, | |
| 2896 | - Blob * pErr ){ | |
| 2908 | +static int checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, | |
| 2909 | + int ckin1Flags, Blob * pErr ){ | |
| 2897 | 2910 | Blob mf = empty_blob; /* output manifest */ |
| 2898 | 2911 | int rid = 0, frid = 0; /* various RIDs */ |
| 2899 | 2912 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2900 | 2913 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2901 | 2914 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| @@ -2904,19 +2917,45 @@ | ||
| 2904 | 2917 | db_begin_transaction(); |
| 2905 | 2918 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2906 | 2919 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 2907 | 2920 | } |
| 2908 | 2921 | assert(pCI->pParent->rid>0); |
| 2922 | + if(leaf_is_closed(pCI->pParent->rid)){ | |
| 2923 | + ci_err((pErr,"Cannot commit to a closed leaf.")); | |
| 2924 | + /* Remember that in order to override this we'd also need to | |
| 2925 | + ** cancel TAG_CLOSED on pCI->pParent. There would seem to be | |
| 2926 | + ** no reason we can't do that via the generated manifest, | |
| 2927 | + ** but the commit command does not offer that option, so | |
| 2928 | + ** we won't, either. | |
| 2929 | + */ | |
| 2930 | + } | |
| 2909 | 2931 | if(!(CKIN1_ALLOW_FORK & ckin1Flags) |
| 2910 | 2932 | && !is_a_leaf(pCI->pParent->rid)){ |
| 2911 | 2933 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 2912 | 2934 | pCI->zParentUuid)); |
| 2913 | 2935 | } |
| 2936 | + if(!(CKIN1_ALLOW_MERGE_MARKER & ckin1Flags) | |
| 2937 | + && contains_merge_marker(&pCI->fileContent)){ | |
| 2938 | + ci_err((pErr,"Content appears to contain a merge conflict marker.")); | |
| 2939 | + } | |
| 2940 | + if(blob_size(&pCI->fileHash)==0){ | |
| 2941 | + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); | |
| 2942 | + assert(blob_size(&pCI->fileHash)>0); | |
| 2943 | + } | |
| 2944 | + /* Potential TODOs include: | |
| 2945 | + ** | |
| 2946 | + ** - Commit allows an empty checkin only with a flag, but we | |
| 2947 | + ** currently disallow it entirely. Conform with commit? | |
| 2948 | + */ | |
| 2949 | + | |
| 2914 | 2950 | /* |
| 2915 | - ** Confirm that pCI->zFilename can be found in pCI->pParent. | |
| 2916 | - ** If not, fail. This is admittedly an artificial limitation, | |
| 2917 | - ** not strictly necessary. | |
| 2951 | + ** Confirm that pCI->zFilename can be found in pCI->pParent. If | |
| 2952 | + ** not, fail. This is admittedly an artificial limitation, not | |
| 2953 | + ** strictly necessary. We do it to hopefully reduce the chance of an | |
| 2954 | + ** "oops" where file X/Y/z gets committed as X/Y/Z due to a typo or | |
| 2955 | + ** case-sensitivity mismatch between the user and repo, or some | |
| 2956 | + ** such. | |
| 2918 | 2957 | */ |
| 2919 | 2958 | manifest_file_rewind(pCI->pParent); |
| 2920 | 2959 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2921 | 2960 | if(!zFile){ |
| 2922 | 2961 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| @@ -2942,26 +2981,22 @@ | ||
| 2942 | 2981 | int prevFRid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", |
| 2943 | 2982 | zFilePrevUuid); |
| 2944 | 2983 | assert(prevFRid>0); |
| 2945 | 2984 | content_deltify(frid, &prevFRid, 1, 0); |
| 2946 | 2985 | } |
| 2947 | - /* Save and crosslink the manifest... */ | |
| 2986 | + /* Save, deltify, and crosslink the manifest... */ | |
| 2948 | 2987 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2988 | + content_deltify(rid, &pCI->pParent->rid, 1, 0); | |
| 2949 | 2989 | if(pRid!=0){ |
| 2950 | 2990 | *pRid = rid; |
| 2951 | 2991 | } |
| 2952 | 2992 | if( ckin1Flags & CKIN1_DUMP_MANIFEST ){ |
| 2953 | 2993 | fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); |
| 2954 | 2994 | } |
| 2955 | 2995 | manifest_crosslink(rid, &mf, 0); |
| 2956 | - if(CKIN1_DRY_RUN & ckin1Flags){ | |
| 2957 | - fossil_print("Rolling back transaction.\n"); | |
| 2958 | - db_end_transaction(1); | |
| 2959 | - }else{ | |
| 2960 | - db_end_transaction(0); | |
| 2961 | - } | |
| 2962 | 2996 | blob_reset(&mf); |
| 2997 | + db_end_transaction((CKIN1_DRY_RUN & ckin1Flags) ? 1 : 0); | |
| 2963 | 2998 | return 1; |
| 2964 | 2999 | ci_error: |
| 2965 | 3000 | assert(db_transaction_nesting_depth()>0); |
| 2966 | 3001 | db_end_transaction(1); |
| 2967 | 3002 | return 0; |
| @@ -2992,10 +3027,12 @@ | ||
| 2992 | 3027 | ** trunk (if used without a checkout). |
| 2993 | 3028 | ** --wet-run Disables the default dry-run mode. |
| 2994 | 3029 | ** --allow-fork Allows the commit to be made against a |
| 2995 | 3030 | ** non-leaf parent. Note that no autosync |
| 2996 | 3031 | ** is performed beforehand. |
| 3032 | +** --allow-merge-conflict Allows checkin of a file even if it appears | |
| 3033 | +** to contain a fossil merge conflict marker. | |
| 2997 | 3034 | ** --user-override USER USER to use instead of the current default. |
| 2998 | 3035 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 2999 | 3036 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3000 | 3037 | ** |
| 3001 | 3038 | ** Example: |
| @@ -3031,20 +3068,22 @@ | ||
| 3031 | 3068 | ckin1Flags |= CKIN1_ALLOW_FORK; |
| 3032 | 3069 | } |
| 3033 | 3070 | if(find_option("dump-manifest","d",0)!=0){ |
| 3034 | 3071 | ckin1Flags |= CKIN1_DUMP_MANIFEST; |
| 3035 | 3072 | } |
| 3073 | + if(find_option("allow-merge-conflict",0,0)!=0){ | |
| 3074 | + ckin1Flags |= CKIN1_ALLOW_MERGE_MARKER; | |
| 3075 | + } | |
| 3036 | 3076 | |
| 3037 | 3077 | CheckinOneFileInfo_init(&cinf); |
| 3038 | 3078 | |
| 3039 | 3079 | if(zComment && zCommentFile){ |
| 3040 | 3080 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3041 | 3081 | }else{ |
| 3042 | - if(zCommentFile){ | |
| 3082 | + if(zCommentFile && *zCommentFile){ | |
| 3043 | 3083 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| 3044 | - }else{ | |
| 3045 | - assert(zComment); | |
| 3084 | + }else if(zComment && *zComment){ | |
| 3046 | 3085 | blob_append(&cinf.comment, zComment, -1); |
| 3047 | 3086 | } |
| 3048 | 3087 | if(!blob_size(&cinf.comment)){ |
| 3049 | 3088 | fossil_fatal("Non-empty checkin comment is required."); |
| 3050 | 3089 | } |
| @@ -3086,13 +3125,10 @@ | ||
| 3086 | 3125 | cinf.pParent = manifest_get_by_name(cinf.zParentUuid, &parentVid); |
| 3087 | 3126 | assert(parentVid>0); |
| 3088 | 3127 | assert(cinf.pParent!=0); |
| 3089 | 3128 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3090 | 3129 | ExtFILE/*may want to reconsider*/); |
| 3091 | - sha3sum_init(256); | |
| 3092 | - sha3sum_step_blob(&cinf.fileContent); | |
| 3093 | - sha3sum_finish(&cinf.fileHash); | |
| 3094 | 3130 | { |
| 3095 | 3131 | Blob errMsg = empty_blob; |
| 3096 | 3132 | const int rc = checkin_one_file(&cinf, &newRid, ckin1Flags, &errMsg); |
| 3097 | 3133 | CheckinOneFileInfo_cleanup(&cinf); |
| 3098 | 3134 | if(rc){ |
| 3099 | 3135 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2321,13 +2321,11 @@ | |
| 2321 | ** Do not allow a commit against a closed leaf unless the commit |
| 2322 | ** ends up on a different branch. |
| 2323 | */ |
| 2324 | if( |
| 2325 | /* parent check-in has the "closed" tag... */ |
| 2326 | db_exists("SELECT 1 FROM tagxref" |
| 2327 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| 2328 | TAG_CLOSED, vid) |
| 2329 | /* ... and the new check-in has no --branch option or the --branch |
| 2330 | ** option does not actually change the branch */ |
| 2331 | && (sCiInfo.zBranch==0 |
| 2332 | || db_exists("SELECT 1 FROM tagxref" |
| 2333 | " WHERE tagid=%d AND rid=%d AND tagtype>0" |
| @@ -2673,15 +2671,17 @@ | |
| 2673 | if( count_nonbranch_children(vid)>1 ){ |
| 2674 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2675 | } |
| 2676 | } |
| 2677 | |
| 2678 | #if INTERFACE |
| 2679 | /* |
| 2680 | ** State for the "web-checkin" infrastructure, which enables the |
| 2681 | ** ability to commit changes to a single file via an HTTP request. |
| 2682 | */ |
| 2683 | struct CheckinOneFileInfo { |
| 2684 | Blob comment; /* Check-in comment text */ |
| 2685 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2686 | Manifest * pParent; /* parent checkin */ |
| 2687 | char *zParentUuid; /* UUID of pParent */ |
| @@ -2688,21 +2688,22 @@ | |
| 2688 | char *zUser; /* User name */ |
| 2689 | char *zDate; /* Optionally force this date string |
| 2690 | (anything supported by |
| 2691 | date_in_standard_format(). |
| 2692 | Maybe be NULL. */ |
| 2693 | char *zFilename; /* Name of single file to commit. */ |
| 2694 | Blob fileContent; /* Content of the modified file. */ |
| 2695 | Blob fileHash; /* Hash of this->fileContent */ |
| 2696 | }; |
| 2697 | #endif /* INTERFACE */ |
| 2698 | |
| 2699 | /* |
| 2700 | ** Initializes p to a known-valid default state. |
| 2701 | */ |
| 2702 | static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ |
| 2703 | memset(p, 0, sizeof(struct CheckinOneFileInfo)); |
| 2704 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2705 | } |
| 2706 | |
| 2707 | /* |
| 2708 | ** Frees all memory owned by p, but does not free p. |
| @@ -2735,11 +2736,22 @@ | |
| 2735 | */ |
| 2736 | CKIN1_ALLOW_FORK = 1<<1, |
| 2737 | /* |
| 2738 | ** Tells checkin_one_file() to dump its generated manifest to stdout. |
| 2739 | */ |
| 2740 | CKIN1_DUMP_MANIFEST = 1<<2 |
| 2741 | }; |
| 2742 | |
| 2743 | /* |
| 2744 | ** Creates a manifest file, written to pOut, from the state in the |
| 2745 | ** fully-populated pCI argument. Returns true on success. On error, |
| @@ -2856,13 +2868,11 @@ | |
| 2856 | |
| 2857 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2858 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2859 | } |
| 2860 | |
| 2861 | blob_appendf(pOut, "P %z\n", |
| 2862 | db_text(0,"SELECT uuid FROM blob WHERE rid=%d", |
| 2863 | pCI->pParent->rid)); |
| 2864 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2865 | md5sum_blob(pOut, &zCard); |
| 2866 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2867 | blob_reset(&zCard); |
| 2868 | return 1; |
| @@ -2879,10 +2889,13 @@ | |
| 2879 | ** |
| 2880 | ** This routine uses the state from the given fully-populated pCI |
| 2881 | ** argument to add pCI->fileContent to the database, and create and |
| 2882 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2883 | ** are unchanged. |
| 2884 | ** |
| 2885 | ** On error, returns false (0) and, if pErr is not NULL, writes a |
| 2886 | ** diagnostic message there. |
| 2887 | ** |
| 2888 | ** Returns true on success. If pRid is not NULL, the RID of the |
| @@ -2890,12 +2903,12 @@ | |
| 2890 | ** |
| 2891 | ** ckin1Flags is a bitmask of optional flags from fossil_ckin1_flags |
| 2892 | ** enum. See that enum for the docs for each flag. |
| 2893 | ** |
| 2894 | */ |
| 2895 | int checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, int ckin1Flags, |
| 2896 | Blob * pErr ){ |
| 2897 | Blob mf = empty_blob; /* output manifest */ |
| 2898 | int rid = 0, frid = 0; /* various RIDs */ |
| 2899 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2900 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2901 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| @@ -2904,19 +2917,45 @@ | |
| 2904 | db_begin_transaction(); |
| 2905 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2906 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 2907 | } |
| 2908 | assert(pCI->pParent->rid>0); |
| 2909 | if(!(CKIN1_ALLOW_FORK & ckin1Flags) |
| 2910 | && !is_a_leaf(pCI->pParent->rid)){ |
| 2911 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 2912 | pCI->zParentUuid)); |
| 2913 | } |
| 2914 | /* |
| 2915 | ** Confirm that pCI->zFilename can be found in pCI->pParent. |
| 2916 | ** If not, fail. This is admittedly an artificial limitation, |
| 2917 | ** not strictly necessary. |
| 2918 | */ |
| 2919 | manifest_file_rewind(pCI->pParent); |
| 2920 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2921 | if(!zFile){ |
| 2922 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| @@ -2942,26 +2981,22 @@ | |
| 2942 | int prevFRid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", |
| 2943 | zFilePrevUuid); |
| 2944 | assert(prevFRid>0); |
| 2945 | content_deltify(frid, &prevFRid, 1, 0); |
| 2946 | } |
| 2947 | /* Save and crosslink the manifest... */ |
| 2948 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2949 | if(pRid!=0){ |
| 2950 | *pRid = rid; |
| 2951 | } |
| 2952 | if( ckin1Flags & CKIN1_DUMP_MANIFEST ){ |
| 2953 | fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); |
| 2954 | } |
| 2955 | manifest_crosslink(rid, &mf, 0); |
| 2956 | if(CKIN1_DRY_RUN & ckin1Flags){ |
| 2957 | fossil_print("Rolling back transaction.\n"); |
| 2958 | db_end_transaction(1); |
| 2959 | }else{ |
| 2960 | db_end_transaction(0); |
| 2961 | } |
| 2962 | blob_reset(&mf); |
| 2963 | return 1; |
| 2964 | ci_error: |
| 2965 | assert(db_transaction_nesting_depth()>0); |
| 2966 | db_end_transaction(1); |
| 2967 | return 0; |
| @@ -2992,10 +3027,12 @@ | |
| 2992 | ** trunk (if used without a checkout). |
| 2993 | ** --wet-run Disables the default dry-run mode. |
| 2994 | ** --allow-fork Allows the commit to be made against a |
| 2995 | ** non-leaf parent. Note that no autosync |
| 2996 | ** is performed beforehand. |
| 2997 | ** --user-override USER USER to use instead of the current default. |
| 2998 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 2999 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3000 | ** |
| 3001 | ** Example: |
| @@ -3031,20 +3068,22 @@ | |
| 3031 | ckin1Flags |= CKIN1_ALLOW_FORK; |
| 3032 | } |
| 3033 | if(find_option("dump-manifest","d",0)!=0){ |
| 3034 | ckin1Flags |= CKIN1_DUMP_MANIFEST; |
| 3035 | } |
| 3036 | |
| 3037 | CheckinOneFileInfo_init(&cinf); |
| 3038 | |
| 3039 | if(zComment && zCommentFile){ |
| 3040 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3041 | }else{ |
| 3042 | if(zCommentFile){ |
| 3043 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| 3044 | }else{ |
| 3045 | assert(zComment); |
| 3046 | blob_append(&cinf.comment, zComment, -1); |
| 3047 | } |
| 3048 | if(!blob_size(&cinf.comment)){ |
| 3049 | fossil_fatal("Non-empty checkin comment is required."); |
| 3050 | } |
| @@ -3086,13 +3125,10 @@ | |
| 3086 | cinf.pParent = manifest_get_by_name(cinf.zParentUuid, &parentVid); |
| 3087 | assert(parentVid>0); |
| 3088 | assert(cinf.pParent!=0); |
| 3089 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3090 | ExtFILE/*may want to reconsider*/); |
| 3091 | sha3sum_init(256); |
| 3092 | sha3sum_step_blob(&cinf.fileContent); |
| 3093 | sha3sum_finish(&cinf.fileHash); |
| 3094 | { |
| 3095 | Blob errMsg = empty_blob; |
| 3096 | const int rc = checkin_one_file(&cinf, &newRid, ckin1Flags, &errMsg); |
| 3097 | CheckinOneFileInfo_cleanup(&cinf); |
| 3098 | if(rc){ |
| 3099 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2321,13 +2321,11 @@ | |
| 2321 | ** Do not allow a commit against a closed leaf unless the commit |
| 2322 | ** ends up on a different branch. |
| 2323 | */ |
| 2324 | if( |
| 2325 | /* parent check-in has the "closed" tag... */ |
| 2326 | leaf_is_closed(vid) |
| 2327 | /* ... and the new check-in has no --branch option or the --branch |
| 2328 | ** option does not actually change the branch */ |
| 2329 | && (sCiInfo.zBranch==0 |
| 2330 | || db_exists("SELECT 1 FROM tagxref" |
| 2331 | " WHERE tagid=%d AND rid=%d AND tagtype>0" |
| @@ -2673,15 +2671,17 @@ | |
| 2671 | if( count_nonbranch_children(vid)>1 ){ |
| 2672 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2673 | } |
| 2674 | } |
| 2675 | |
| 2676 | /* |
| 2677 | ** State for the "web-checkin" infrastructure, which enables the |
| 2678 | ** ability to commit changes to a single file via an HTTP request. |
| 2679 | ** |
| 2680 | ** Memory for all non-const (char *) members is owned by the |
| 2681 | ** CheckinOneFileInfo instance. |
| 2682 | */ |
| 2683 | struct CheckinOneFileInfo { |
| 2684 | Blob comment; /* Check-in comment text */ |
| 2685 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2686 | Manifest * pParent; /* parent checkin */ |
| 2687 | char *zParentUuid; /* UUID of pParent */ |
| @@ -2688,21 +2688,22 @@ | |
| 2688 | char *zUser; /* User name */ |
| 2689 | char *zDate; /* Optionally force this date string |
| 2690 | (anything supported by |
| 2691 | date_in_standard_format(). |
| 2692 | Maybe be NULL. */ |
| 2693 | char *zFilename; /* Name of single file to commit. Must be |
| 2694 | relative to the top of the repo. */ |
| 2695 | Blob fileContent; /* Content of file referred to by zFilename. */ |
| 2696 | Blob fileHash; /* Hash of this->fileContent, using the |
| 2697 | repo's preferred hash method. */ |
| 2698 | }; |
| 2699 | typedef struct CheckinOneFileInfo CheckinOneFileInfo; |
| 2700 | /* |
| 2701 | ** Initializes p to a known-valid default state. |
| 2702 | */ |
| 2703 | static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ |
| 2704 | memset(p, 0, sizeof(CheckinOneFileInfo)); |
| 2705 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2706 | } |
| 2707 | |
| 2708 | /* |
| 2709 | ** Frees all memory owned by p, but does not free p. |
| @@ -2735,11 +2736,22 @@ | |
| 2736 | */ |
| 2737 | CKIN1_ALLOW_FORK = 1<<1, |
| 2738 | /* |
| 2739 | ** Tells checkin_one_file() to dump its generated manifest to stdout. |
| 2740 | */ |
| 2741 | CKIN1_DUMP_MANIFEST = 1<<2, |
| 2742 | |
| 2743 | /* |
| 2744 | ** By default, content containing what appears to be a merge conflict |
| 2745 | ** marker is not permitted. This flag relaxes that requirement. |
| 2746 | */ |
| 2747 | CKIN1_ALLOW_MERGE_MARKER = 1<<3, |
| 2748 | |
| 2749 | /* |
| 2750 | ** NOT YET IMPLEMENTED. |
| 2751 | */ |
| 2752 | CKIN1_ALLOW_CLOSED_LEAF = 1<<4 |
| 2753 | }; |
| 2754 | |
| 2755 | /* |
| 2756 | ** Creates a manifest file, written to pOut, from the state in the |
| 2757 | ** fully-populated pCI argument. Returns true on success. On error, |
| @@ -2856,13 +2868,11 @@ | |
| 2868 | |
| 2869 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2870 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2871 | } |
| 2872 | |
| 2873 | blob_appendf(pOut, "P %s\n", pCI->zParentUuid); |
| 2874 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2875 | md5sum_blob(pOut, &zCard); |
| 2876 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2877 | blob_reset(&zCard); |
| 2878 | return 1; |
| @@ -2879,10 +2889,13 @@ | |
| 2889 | ** |
| 2890 | ** This routine uses the state from the given fully-populated pCI |
| 2891 | ** argument to add pCI->fileContent to the database, and create and |
| 2892 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2893 | ** are unchanged. |
| 2894 | ** |
| 2895 | ** If pCI->fileHash is empty, this routine populates it with the |
| 2896 | ** repository's preferred hash algorithm. |
| 2897 | ** |
| 2898 | ** On error, returns false (0) and, if pErr is not NULL, writes a |
| 2899 | ** diagnostic message there. |
| 2900 | ** |
| 2901 | ** Returns true on success. If pRid is not NULL, the RID of the |
| @@ -2890,12 +2903,12 @@ | |
| 2903 | ** |
| 2904 | ** ckin1Flags is a bitmask of optional flags from fossil_ckin1_flags |
| 2905 | ** enum. See that enum for the docs for each flag. |
| 2906 | ** |
| 2907 | */ |
| 2908 | static int checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, |
| 2909 | int ckin1Flags, Blob * pErr ){ |
| 2910 | Blob mf = empty_blob; /* output manifest */ |
| 2911 | int rid = 0, frid = 0; /* various RIDs */ |
| 2912 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2913 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2914 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| @@ -2904,19 +2917,45 @@ | |
| 2917 | db_begin_transaction(); |
| 2918 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2919 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 2920 | } |
| 2921 | assert(pCI->pParent->rid>0); |
| 2922 | if(leaf_is_closed(pCI->pParent->rid)){ |
| 2923 | ci_err((pErr,"Cannot commit to a closed leaf.")); |
| 2924 | /* Remember that in order to override this we'd also need to |
| 2925 | ** cancel TAG_CLOSED on pCI->pParent. There would seem to be |
| 2926 | ** no reason we can't do that via the generated manifest, |
| 2927 | ** but the commit command does not offer that option, so |
| 2928 | ** we won't, either. |
| 2929 | */ |
| 2930 | } |
| 2931 | if(!(CKIN1_ALLOW_FORK & ckin1Flags) |
| 2932 | && !is_a_leaf(pCI->pParent->rid)){ |
| 2933 | ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", |
| 2934 | pCI->zParentUuid)); |
| 2935 | } |
| 2936 | if(!(CKIN1_ALLOW_MERGE_MARKER & ckin1Flags) |
| 2937 | && contains_merge_marker(&pCI->fileContent)){ |
| 2938 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 2939 | } |
| 2940 | if(blob_size(&pCI->fileHash)==0){ |
| 2941 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 2942 | assert(blob_size(&pCI->fileHash)>0); |
| 2943 | } |
| 2944 | /* Potential TODOs include: |
| 2945 | ** |
| 2946 | ** - Commit allows an empty checkin only with a flag, but we |
| 2947 | ** currently disallow it entirely. Conform with commit? |
| 2948 | */ |
| 2949 | |
| 2950 | /* |
| 2951 | ** Confirm that pCI->zFilename can be found in pCI->pParent. If |
| 2952 | ** not, fail. This is admittedly an artificial limitation, not |
| 2953 | ** strictly necessary. We do it to hopefully reduce the chance of an |
| 2954 | ** "oops" where file X/Y/z gets committed as X/Y/Z due to a typo or |
| 2955 | ** case-sensitivity mismatch between the user and repo, or some |
| 2956 | ** such. |
| 2957 | */ |
| 2958 | manifest_file_rewind(pCI->pParent); |
| 2959 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2960 | if(!zFile){ |
| 2961 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| @@ -2942,26 +2981,22 @@ | |
| 2981 | int prevFRid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", |
| 2982 | zFilePrevUuid); |
| 2983 | assert(prevFRid>0); |
| 2984 | content_deltify(frid, &prevFRid, 1, 0); |
| 2985 | } |
| 2986 | /* Save, deltify, and crosslink the manifest... */ |
| 2987 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2988 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 2989 | if(pRid!=0){ |
| 2990 | *pRid = rid; |
| 2991 | } |
| 2992 | if( ckin1Flags & CKIN1_DUMP_MANIFEST ){ |
| 2993 | fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); |
| 2994 | } |
| 2995 | manifest_crosslink(rid, &mf, 0); |
| 2996 | blob_reset(&mf); |
| 2997 | db_end_transaction((CKIN1_DRY_RUN & ckin1Flags) ? 1 : 0); |
| 2998 | return 1; |
| 2999 | ci_error: |
| 3000 | assert(db_transaction_nesting_depth()>0); |
| 3001 | db_end_transaction(1); |
| 3002 | return 0; |
| @@ -2992,10 +3027,12 @@ | |
| 3027 | ** trunk (if used without a checkout). |
| 3028 | ** --wet-run Disables the default dry-run mode. |
| 3029 | ** --allow-fork Allows the commit to be made against a |
| 3030 | ** non-leaf parent. Note that no autosync |
| 3031 | ** is performed beforehand. |
| 3032 | ** --allow-merge-conflict Allows checkin of a file even if it appears |
| 3033 | ** to contain a fossil merge conflict marker. |
| 3034 | ** --user-override USER USER to use instead of the current default. |
| 3035 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3036 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3037 | ** |
| 3038 | ** Example: |
| @@ -3031,20 +3068,22 @@ | |
| 3068 | ckin1Flags |= CKIN1_ALLOW_FORK; |
| 3069 | } |
| 3070 | if(find_option("dump-manifest","d",0)!=0){ |
| 3071 | ckin1Flags |= CKIN1_DUMP_MANIFEST; |
| 3072 | } |
| 3073 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3074 | ckin1Flags |= CKIN1_ALLOW_MERGE_MARKER; |
| 3075 | } |
| 3076 | |
| 3077 | CheckinOneFileInfo_init(&cinf); |
| 3078 | |
| 3079 | if(zComment && zCommentFile){ |
| 3080 | fossil_fatal("Only one of -m or -M, not both, may be used."); |
| 3081 | }else{ |
| 3082 | if(zCommentFile && *zCommentFile){ |
| 3083 | blob_read_from_file(&cinf.comment, zCommentFile, ExtFILE); |
| 3084 | }else if(zComment && *zComment){ |
| 3085 | blob_append(&cinf.comment, zComment, -1); |
| 3086 | } |
| 3087 | if(!blob_size(&cinf.comment)){ |
| 3088 | fossil_fatal("Non-empty checkin comment is required."); |
| 3089 | } |
| @@ -3086,13 +3125,10 @@ | |
| 3125 | cinf.pParent = manifest_get_by_name(cinf.zParentUuid, &parentVid); |
| 3126 | assert(parentVid>0); |
| 3127 | assert(cinf.pParent!=0); |
| 3128 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3129 | ExtFILE/*may want to reconsider*/); |
| 3130 | { |
| 3131 | Blob errMsg = empty_blob; |
| 3132 | const int rc = checkin_one_file(&cinf, &newRid, ckin1Flags, &errMsg); |
| 3133 | CheckinOneFileInfo_cleanup(&cinf); |
| 3134 | if(rc){ |
| 3135 |
+11
| --- src/leaf.c | ||
| +++ src/leaf.c | ||
| @@ -152,10 +152,21 @@ | ||
| 152 | 152 | " AND tx.tagid=%d" |
| 153 | 153 | " AND tx.tagtype>0)", |
| 154 | 154 | zVar, TAG_CLOSED |
| 155 | 155 | ); |
| 156 | 156 | } |
| 157 | + | |
| 158 | +/* | |
| 159 | +** Returns true if vid refers to a closed leaf, else false. vid is | |
| 160 | +** assumed to refer to a manifest, but this function does not verify | |
| 161 | +** that. | |
| 162 | +*/ | |
| 163 | +int leaf_is_closed(int vid){ | |
| 164 | + return db_exists("SELECT 1 FROM tagxref" | |
| 165 | + " WHERE tagid=%d AND rid=%d AND tagtype>0", | |
| 166 | + TAG_CLOSED, vid); | |
| 167 | +} | |
| 157 | 168 | |
| 158 | 169 | /* |
| 159 | 170 | ** Schedule a leaf check for "rid" and its parents. |
| 160 | 171 | */ |
| 161 | 172 | void leaf_eventually_check(int rid){ |
| 162 | 173 |
| --- src/leaf.c | |
| +++ src/leaf.c | |
| @@ -152,10 +152,21 @@ | |
| 152 | " AND tx.tagid=%d" |
| 153 | " AND tx.tagtype>0)", |
| 154 | zVar, TAG_CLOSED |
| 155 | ); |
| 156 | } |
| 157 | |
| 158 | /* |
| 159 | ** Schedule a leaf check for "rid" and its parents. |
| 160 | */ |
| 161 | void leaf_eventually_check(int rid){ |
| 162 |
| --- src/leaf.c | |
| +++ src/leaf.c | |
| @@ -152,10 +152,21 @@ | |
| 152 | " AND tx.tagid=%d" |
| 153 | " AND tx.tagtype>0)", |
| 154 | zVar, TAG_CLOSED |
| 155 | ); |
| 156 | } |
| 157 | |
| 158 | /* |
| 159 | ** Returns true if vid refers to a closed leaf, else false. vid is |
| 160 | ** assumed to refer to a manifest, but this function does not verify |
| 161 | ** that. |
| 162 | */ |
| 163 | int leaf_is_closed(int vid){ |
| 164 | return db_exists("SELECT 1 FROM tagxref" |
| 165 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| 166 | TAG_CLOSED, vid); |
| 167 | } |
| 168 | |
| 169 | /* |
| 170 | ** Schedule a leaf check for "rid" and its parents. |
| 171 | */ |
| 172 | void leaf_eventually_check(int rid){ |
| 173 |