Fossil SCM
Now disallow mini-checkin if the datestamp is older than the parent unless --allow-older is used.
Commit
0a0d96dd42db6dde58d1f65ea94467f51b2f24eb7ac39d468a5ee4e5a51c952d
Parent
d84c5149164fc08…
1 file changed
+96
-40
+96
-40
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -1399,10 +1399,28 @@ | ||
| 1399 | 1399 | g.aCommitFile[jj] = 0; |
| 1400 | 1400 | bag_clear(&toCommit); |
| 1401 | 1401 | } |
| 1402 | 1402 | return result; |
| 1403 | 1403 | } |
| 1404 | + | |
| 1405 | +/* | |
| 1406 | +** Returns true if the checkin identified by the first parameter is | |
| 1407 | +** older than the given (valid) date/time string, else returns false. | |
| 1408 | +** Also returns true if rid does not refer to a checkin, but it is not | |
| 1409 | +** intended to be used for that case. | |
| 1410 | +*/ | |
| 1411 | +static int checkin_is_younger( | |
| 1412 | + int rid, /* The record ID of the ancestor */ | |
| 1413 | + const char *zDate /* Date & time of the current check-in */ | |
| 1414 | +){ | |
| 1415 | + return db_exists( | |
| 1416 | + "SELECT 1 FROM event" | |
| 1417 | + " WHERE datetime(mtime)>=%Q" | |
| 1418 | + " AND type='ci' AND objid=%d", | |
| 1419 | + zDate, rid | |
| 1420 | + ) ? 0 : 1; | |
| 1421 | +} | |
| 1404 | 1422 | |
| 1405 | 1423 | /* |
| 1406 | 1424 | ** Make sure the current check-in with timestamp zDate is younger than its |
| 1407 | 1425 | ** ancestor identified rid and zUuid. Throw a fatal error if not. |
| 1408 | 1426 | */ |
| @@ -1410,23 +1428,18 @@ | ||
| 1410 | 1428 | int rid, /* The record ID of the ancestor */ |
| 1411 | 1429 | const char *zUuid, /* The artifact ID of the ancestor */ |
| 1412 | 1430 | const char *zDate /* Date & time of the current check-in */ |
| 1413 | 1431 | ){ |
| 1414 | 1432 | #ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES |
| 1415 | - int b; | |
| 1416 | - b = db_exists( | |
| 1417 | - "SELECT 1 FROM event" | |
| 1418 | - " WHERE datetime(mtime)>=%Q" | |
| 1419 | - " AND type='ci' AND objid=%d", | |
| 1420 | - zDate, rid | |
| 1421 | - ); | |
| 1422 | - if( b ){ | |
| 1433 | + if(checkin_is_younger(rid,zDate)==0){ | |
| 1423 | 1434 | fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" |
| 1424 | 1435 | " Use --allow-older to override.", zUuid, zDate); |
| 1425 | 1436 | } |
| 1426 | 1437 | #endif |
| 1427 | 1438 | } |
| 1439 | + | |
| 1440 | + | |
| 1428 | 1441 | |
| 1429 | 1442 | /* |
| 1430 | 1443 | ** zDate should be a valid date string. Convert this string into the |
| 1431 | 1444 | ** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date, |
| 1432 | 1445 | ** print a fatal error and quit. |
| @@ -2745,13 +2758,26 @@ | ||
| 2745 | 2758 | ** marker is not permitted. This flag relaxes that requirement. |
| 2746 | 2759 | */ |
| 2747 | 2760 | CIMINI_ALLOW_MERGE_MARKER = 1<<3, |
| 2748 | 2761 | |
| 2749 | 2762 | /* |
| 2763 | +** By default mini-checkins are not allowed to be "older" | |
| 2764 | +** than their parent. i.e. they may not have a timestamp | |
| 2765 | +** which predates their parent. This flag bypasses that | |
| 2766 | +** check. | |
| 2767 | +*/ | |
| 2768 | +CIMINI_ALLOW_OLDER = 1<<4, | |
| 2769 | + | |
| 2770 | +/* | |
| 2771 | +** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer | |
| 2772 | +** creation of a delta manifest. | |
| 2773 | +*/ | |
| 2774 | +CIMINI_PREFER_DELTA = 1<<5, | |
| 2775 | +/* | |
| 2750 | 2776 | ** NOT YET IMPLEMENTED. |
| 2751 | 2777 | */ |
| 2752 | -CIMINI_ALLOW_CLOSED_LEAF = 1<<4 | |
| 2778 | +CIMINI_ALLOW_CLOSED_LEAF = 1<<6 | |
| 2753 | 2779 | }; |
| 2754 | 2780 | |
| 2755 | 2781 | /* |
| 2756 | 2782 | ** Creates a manifest file, written to pOut, from the state in the |
| 2757 | 2783 | ** fully-populated pCI argument. pCI is not *semantically* modified |
| @@ -2777,10 +2803,11 @@ | ||
| 2777 | 2803 | |
| 2778 | 2804 | assert(blob_str(&pCI->fileHash)); |
| 2779 | 2805 | assert(pCI->pParent); |
| 2780 | 2806 | assert(pCI->zFilename); |
| 2781 | 2807 | assert(pCI->zUser); |
| 2808 | + assert(pCI->zDate); | |
| 2782 | 2809 | |
| 2783 | 2810 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2784 | 2811 | /* Potential TODOs include... |
| 2785 | 2812 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2786 | 2813 | ** - Maybe add support for tags. Those can be edited via /info page. |
| @@ -2794,29 +2821,11 @@ | ||
| 2794 | 2821 | if(blob_size(&pCI->comment)!=0){ |
| 2795 | 2822 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2796 | 2823 | }else{ |
| 2797 | 2824 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2798 | 2825 | } |
| 2799 | - { | |
| 2800 | - /* | |
| 2801 | - ** We don't use date_in_standard_format() because that has | |
| 2802 | - ** side-effects we don't want to trigger here. | |
| 2803 | - */ | |
| 2804 | - char * zDVal = db_text( | |
| 2805 | - 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", | |
| 2806 | - pCI->zDate | |
| 2807 | - ? pCI->zDate | |
| 2808 | - : "now"); | |
| 2809 | - if(zDVal[0]==0){ | |
| 2810 | - fossil_free(zDVal); | |
| 2811 | - mf_err((pErr, | |
| 2812 | - "Invalid date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"", | |
| 2813 | - pCI->zDate)); | |
| 2814 | - } | |
| 2815 | - blob_appendf(pOut, "D %z\n", zDVal); | |
| 2816 | - } | |
| 2817 | - | |
| 2826 | + blob_appendf(pOut, "D %z\n", pCI->zDate); | |
| 2818 | 2827 | manifest_file_rewind(pCI->pParent); |
| 2819 | 2828 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2820 | 2829 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2821 | 2830 | if(cmp<0){ |
| 2822 | 2831 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| @@ -2893,13 +2902,21 @@ | ||
| 2893 | 2902 | ** This routine uses the state from the given fully-populated pCI |
| 2894 | 2903 | ** argument to add pCI->fileContent to the database, and create and |
| 2895 | 2904 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2896 | 2905 | ** are unchanged. |
| 2897 | 2906 | ** |
| 2898 | -** If pCI->fileHash is empty, this routine populates it with the | |
| 2899 | -** repository's preferred hash algorithm. pCI is not otherwise | |
| 2900 | -** modified, nor is its ownership modified. | |
| 2907 | +** pCI may be modified as follows: | |
| 2908 | +** | |
| 2909 | +** - If pCI->fileHash is empty, this routine populates it with the | |
| 2910 | +** repository's preferred hash algorithm. | |
| 2911 | +** | |
| 2912 | +** - pCI->zDate is normalized to/replaced with a valid date/time | |
| 2913 | +** string. If its original value cannot be validated then | |
| 2914 | +** this function fails. If pCI->zDate is NULL, the current time | |
| 2915 | +** is used. | |
| 2916 | +** | |
| 2917 | +** pCI's ownership is not modified. | |
| 2901 | 2918 | ** |
| 2902 | 2919 | ** This function validates several of the inputs and fails if any |
| 2903 | 2920 | ** validation fails. |
| 2904 | 2921 | ** |
| 2905 | 2922 | ** On error, returns false (0) and, if pErr is not NULL, writes a |
| @@ -2918,11 +2935,13 @@ | ||
| 2918 | 2935 | int rid = 0, frid = 0; /* various RIDs */ |
| 2919 | 2936 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2920 | 2937 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2921 | 2938 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| 2922 | 2939 | the file */ |
| 2940 | + | |
| 2923 | 2941 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 2942 | + | |
| 2924 | 2943 | db_begin_transaction(); |
| 2925 | 2944 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2926 | 2945 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 2927 | 2946 | } |
| 2928 | 2947 | assert(pCI->pParent->rid>0); |
| @@ -2942,14 +2961,42 @@ | ||
| 2942 | 2961 | } |
| 2943 | 2962 | if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags) |
| 2944 | 2963 | && contains_merge_marker(&pCI->fileContent)){ |
| 2945 | 2964 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 2946 | 2965 | } |
| 2966 | + | |
| 2967 | + { | |
| 2968 | + /* | |
| 2969 | + ** Normalize the timestamp. We don't use date_in_standard_format() | |
| 2970 | + ** because that has side-effects we don't want to trigger here. | |
| 2971 | + */ | |
| 2972 | + char * zDVal = db_text( | |
| 2973 | + 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", | |
| 2974 | + pCI->zDate ? pCI->zDate : "now"); | |
| 2975 | + if(zDVal[0]==0){ | |
| 2976 | + fossil_free(zDVal); | |
| 2977 | + ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); | |
| 2978 | + } | |
| 2979 | + fossil_free(pCI->zDate); | |
| 2980 | + pCI->zDate = zDVal; | |
| 2981 | + } | |
| 2982 | + if(!(CIMINI_ALLOW_OLDER & ciminiFlags) | |
| 2983 | + && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ | |
| 2984 | + ci_err((pErr,"Checkin time (%s) may not be older " | |
| 2985 | + "than its parent (%z).", | |
| 2986 | + pCI->zDate, | |
| 2987 | + db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", | |
| 2988 | + pCI->pParent->rDate) | |
| 2989 | + )); | |
| 2990 | + } | |
| 2991 | + | |
| 2947 | 2992 | /* Potential TODOs include: |
| 2948 | 2993 | ** |
| 2949 | 2994 | ** - Commit allows an empty checkin only with a flag, but we |
| 2950 | 2995 | ** currently disallow it entirely. Conform with commit? |
| 2996 | + ** | |
| 2997 | + ** Non-TODOs: | |
| 2951 | 2998 | ** |
| 2952 | 2999 | ** - Check for a commit lock would require auto-sync, which this |
| 2953 | 3000 | ** code cannot do if it's going to be run via a web page. |
| 2954 | 3001 | */ |
| 2955 | 3002 | |
| @@ -2969,20 +3016,24 @@ | ||
| 2969 | 3016 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 2970 | 3017 | "Adding new files is currently not allowed.", |
| 2971 | 3018 | pCI->zFilename, pCI->zParentUuid)); |
| 2972 | 3019 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 2973 | 3020 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 2974 | - }else{ | |
| 3021 | + } | |
| 3022 | + if(blob_size(&pCI->fileHash)==0){ | |
| 3023 | + /* Hash the content if it's not done already... */ | |
| 3024 | + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); | |
| 3025 | + assert(blob_size(&pCI->fileHash)>0); | |
| 3026 | + } | |
| 3027 | + if(zFile){ | |
| 3028 | + /* Has this file been changed since its previous commit? */ | |
| 3029 | + assert(blob_size(&pCI->fileHash)); | |
| 2975 | 3030 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 2976 | 3031 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 2977 | 3032 | } |
| 2978 | 3033 | zFilePrevUuid = zFile->zUuid; |
| 2979 | 3034 | } |
| 2980 | - if(blob_size(&pCI->fileHash)==0){ | |
| 2981 | - hname_hash(&pCI->fileContent, 0, &pCI->fileHash); | |
| 2982 | - assert(blob_size(&pCI->fileHash)>0); | |
| 2983 | - } | |
| 2984 | 3035 | /* Create the manifest... */ |
| 2985 | 3036 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 2986 | 3037 | return 0; |
| 2987 | 3038 | } |
| 2988 | 3039 | /* Save and deltify the file content... */ |
| @@ -2994,19 +3045,19 @@ | ||
| 2994 | 3045 | content_deltify(frid, &prevFRid, 1, 0); |
| 2995 | 3046 | } |
| 2996 | 3047 | /* Save, deltify, and crosslink the manifest... */ |
| 2997 | 3048 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2998 | 3049 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 2999 | - if(pRid!=0){ | |
| 3000 | - *pRid = rid; | |
| 3001 | - } | |
| 3002 | 3050 | if(ciminiFlags & CIMINI_DUMP_MANIFEST){ |
| 3003 | - fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); | |
| 3051 | + fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); | |
| 3004 | 3052 | } |
| 3005 | 3053 | manifest_crosslink(rid, &mf, 0); |
| 3006 | 3054 | blob_reset(&mf); |
| 3007 | 3055 | db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); |
| 3056 | + if(pRid!=0){ | |
| 3057 | + *pRid = rid; | |
| 3058 | + } | |
| 3008 | 3059 | return 1; |
| 3009 | 3060 | ci_error: |
| 3010 | 3061 | assert(db_transaction_nesting_depth()>0); |
| 3011 | 3062 | db_end_transaction(1); |
| 3012 | 3063 | return 0; |
| @@ -3040,10 +3091,12 @@ | ||
| 3040 | 3091 | ** is performed beforehand. |
| 3041 | 3092 | ** --allow-merge-conflict Allows checkin of a file even if it appears |
| 3042 | 3093 | ** to contain a fossil merge conflict marker. |
| 3043 | 3094 | ** --user-override USER USER to use instead of the current default. |
| 3044 | 3095 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3096 | +** --allow-older Allow a commit to be older than its | |
| 3097 | +** ancestor. | |
| 3045 | 3098 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3046 | 3099 | ** --wet-run Disables the default dry-run mode. |
| 3047 | 3100 | ** |
| 3048 | 3101 | ** Example: |
| 3049 | 3102 | ** |
| @@ -3078,10 +3131,13 @@ | ||
| 3078 | 3131 | if(find_option("dump-manifest","d",0)!=0){ |
| 3079 | 3132 | ciminiFlags |= CIMINI_DUMP_MANIFEST; |
| 3080 | 3133 | } |
| 3081 | 3134 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3082 | 3135 | ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3136 | + } | |
| 3137 | + if(find_option("allow-older",0,0)!=0){ | |
| 3138 | + ciminiFlags |= CIMINI_ALLOW_OLDER; | |
| 3083 | 3139 | } |
| 3084 | 3140 | |
| 3085 | 3141 | db_find_and_open_repository(0, 0); |
| 3086 | 3142 | verify_all_options(); |
| 3087 | 3143 | user_select(); |
| 3088 | 3144 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -1399,10 +1399,28 @@ | |
| 1399 | g.aCommitFile[jj] = 0; |
| 1400 | bag_clear(&toCommit); |
| 1401 | } |
| 1402 | return result; |
| 1403 | } |
| 1404 | |
| 1405 | /* |
| 1406 | ** Make sure the current check-in with timestamp zDate is younger than its |
| 1407 | ** ancestor identified rid and zUuid. Throw a fatal error if not. |
| 1408 | */ |
| @@ -1410,23 +1428,18 @@ | |
| 1410 | int rid, /* The record ID of the ancestor */ |
| 1411 | const char *zUuid, /* The artifact ID of the ancestor */ |
| 1412 | const char *zDate /* Date & time of the current check-in */ |
| 1413 | ){ |
| 1414 | #ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES |
| 1415 | int b; |
| 1416 | b = db_exists( |
| 1417 | "SELECT 1 FROM event" |
| 1418 | " WHERE datetime(mtime)>=%Q" |
| 1419 | " AND type='ci' AND objid=%d", |
| 1420 | zDate, rid |
| 1421 | ); |
| 1422 | if( b ){ |
| 1423 | fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" |
| 1424 | " Use --allow-older to override.", zUuid, zDate); |
| 1425 | } |
| 1426 | #endif |
| 1427 | } |
| 1428 | |
| 1429 | /* |
| 1430 | ** zDate should be a valid date string. Convert this string into the |
| 1431 | ** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date, |
| 1432 | ** print a fatal error and quit. |
| @@ -2745,13 +2758,26 @@ | |
| 2745 | ** marker is not permitted. This flag relaxes that requirement. |
| 2746 | */ |
| 2747 | CIMINI_ALLOW_MERGE_MARKER = 1<<3, |
| 2748 | |
| 2749 | /* |
| 2750 | ** NOT YET IMPLEMENTED. |
| 2751 | */ |
| 2752 | CIMINI_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. pCI is not *semantically* modified |
| @@ -2777,10 +2803,11 @@ | |
| 2777 | |
| 2778 | assert(blob_str(&pCI->fileHash)); |
| 2779 | assert(pCI->pParent); |
| 2780 | assert(pCI->zFilename); |
| 2781 | assert(pCI->zUser); |
| 2782 | |
| 2783 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2784 | /* Potential TODOs include... |
| 2785 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2786 | ** - Maybe add support for tags. Those can be edited via /info page. |
| @@ -2794,29 +2821,11 @@ | |
| 2794 | if(blob_size(&pCI->comment)!=0){ |
| 2795 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2796 | }else{ |
| 2797 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2798 | } |
| 2799 | { |
| 2800 | /* |
| 2801 | ** We don't use date_in_standard_format() because that has |
| 2802 | ** side-effects we don't want to trigger here. |
| 2803 | */ |
| 2804 | char * zDVal = db_text( |
| 2805 | 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", |
| 2806 | pCI->zDate |
| 2807 | ? pCI->zDate |
| 2808 | : "now"); |
| 2809 | if(zDVal[0]==0){ |
| 2810 | fossil_free(zDVal); |
| 2811 | mf_err((pErr, |
| 2812 | "Invalid date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"", |
| 2813 | pCI->zDate)); |
| 2814 | } |
| 2815 | blob_appendf(pOut, "D %z\n", zDVal); |
| 2816 | } |
| 2817 | |
| 2818 | manifest_file_rewind(pCI->pParent); |
| 2819 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2820 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2821 | if(cmp<0){ |
| 2822 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| @@ -2893,13 +2902,21 @@ | |
| 2893 | ** This routine uses the state from the given fully-populated pCI |
| 2894 | ** argument to add pCI->fileContent to the database, and create and |
| 2895 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2896 | ** are unchanged. |
| 2897 | ** |
| 2898 | ** If pCI->fileHash is empty, this routine populates it with the |
| 2899 | ** repository's preferred hash algorithm. pCI is not otherwise |
| 2900 | ** modified, nor is its ownership modified. |
| 2901 | ** |
| 2902 | ** This function validates several of the inputs and fails if any |
| 2903 | ** validation fails. |
| 2904 | ** |
| 2905 | ** On error, returns false (0) and, if pErr is not NULL, writes a |
| @@ -2918,11 +2935,13 @@ | |
| 2918 | int rid = 0, frid = 0; /* various RIDs */ |
| 2919 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2920 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2921 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| 2922 | the file */ |
| 2923 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 2924 | db_begin_transaction(); |
| 2925 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2926 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 2927 | } |
| 2928 | assert(pCI->pParent->rid>0); |
| @@ -2942,14 +2961,42 @@ | |
| 2942 | } |
| 2943 | if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags) |
| 2944 | && contains_merge_marker(&pCI->fileContent)){ |
| 2945 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 2946 | } |
| 2947 | /* Potential TODOs include: |
| 2948 | ** |
| 2949 | ** - Commit allows an empty checkin only with a flag, but we |
| 2950 | ** currently disallow it entirely. Conform with commit? |
| 2951 | ** |
| 2952 | ** - Check for a commit lock would require auto-sync, which this |
| 2953 | ** code cannot do if it's going to be run via a web page. |
| 2954 | */ |
| 2955 | |
| @@ -2969,20 +3016,24 @@ | |
| 2969 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 2970 | "Adding new files is currently not allowed.", |
| 2971 | pCI->zFilename, pCI->zParentUuid)); |
| 2972 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 2973 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 2974 | }else{ |
| 2975 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 2976 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 2977 | } |
| 2978 | zFilePrevUuid = zFile->zUuid; |
| 2979 | } |
| 2980 | if(blob_size(&pCI->fileHash)==0){ |
| 2981 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 2982 | assert(blob_size(&pCI->fileHash)>0); |
| 2983 | } |
| 2984 | /* Create the manifest... */ |
| 2985 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 2986 | return 0; |
| 2987 | } |
| 2988 | /* Save and deltify the file content... */ |
| @@ -2994,19 +3045,19 @@ | |
| 2994 | content_deltify(frid, &prevFRid, 1, 0); |
| 2995 | } |
| 2996 | /* Save, deltify, and crosslink the manifest... */ |
| 2997 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2998 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 2999 | if(pRid!=0){ |
| 3000 | *pRid = rid; |
| 3001 | } |
| 3002 | if(ciminiFlags & CIMINI_DUMP_MANIFEST){ |
| 3003 | fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); |
| 3004 | } |
| 3005 | manifest_crosslink(rid, &mf, 0); |
| 3006 | blob_reset(&mf); |
| 3007 | db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); |
| 3008 | return 1; |
| 3009 | ci_error: |
| 3010 | assert(db_transaction_nesting_depth()>0); |
| 3011 | db_end_transaction(1); |
| 3012 | return 0; |
| @@ -3040,10 +3091,12 @@ | |
| 3040 | ** is performed beforehand. |
| 3041 | ** --allow-merge-conflict Allows checkin of a file even if it appears |
| 3042 | ** to contain a fossil merge conflict marker. |
| 3043 | ** --user-override USER USER to use instead of the current default. |
| 3044 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3045 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3046 | ** --wet-run Disables the default dry-run mode. |
| 3047 | ** |
| 3048 | ** Example: |
| 3049 | ** |
| @@ -3078,10 +3131,13 @@ | |
| 3078 | if(find_option("dump-manifest","d",0)!=0){ |
| 3079 | ciminiFlags |= CIMINI_DUMP_MANIFEST; |
| 3080 | } |
| 3081 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3082 | ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3083 | } |
| 3084 | |
| 3085 | db_find_and_open_repository(0, 0); |
| 3086 | verify_all_options(); |
| 3087 | user_select(); |
| 3088 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -1399,10 +1399,28 @@ | |
| 1399 | g.aCommitFile[jj] = 0; |
| 1400 | bag_clear(&toCommit); |
| 1401 | } |
| 1402 | return result; |
| 1403 | } |
| 1404 | |
| 1405 | /* |
| 1406 | ** Returns true if the checkin identified by the first parameter is |
| 1407 | ** older than the given (valid) date/time string, else returns false. |
| 1408 | ** Also returns true if rid does not refer to a checkin, but it is not |
| 1409 | ** intended to be used for that case. |
| 1410 | */ |
| 1411 | static int checkin_is_younger( |
| 1412 | int rid, /* The record ID of the ancestor */ |
| 1413 | const char *zDate /* Date & time of the current check-in */ |
| 1414 | ){ |
| 1415 | return db_exists( |
| 1416 | "SELECT 1 FROM event" |
| 1417 | " WHERE datetime(mtime)>=%Q" |
| 1418 | " AND type='ci' AND objid=%d", |
| 1419 | zDate, rid |
| 1420 | ) ? 0 : 1; |
| 1421 | } |
| 1422 | |
| 1423 | /* |
| 1424 | ** Make sure the current check-in with timestamp zDate is younger than its |
| 1425 | ** ancestor identified rid and zUuid. Throw a fatal error if not. |
| 1426 | */ |
| @@ -1410,23 +1428,18 @@ | |
| 1428 | int rid, /* The record ID of the ancestor */ |
| 1429 | const char *zUuid, /* The artifact ID of the ancestor */ |
| 1430 | const char *zDate /* Date & time of the current check-in */ |
| 1431 | ){ |
| 1432 | #ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES |
| 1433 | if(checkin_is_younger(rid,zDate)==0){ |
| 1434 | fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" |
| 1435 | " Use --allow-older to override.", zUuid, zDate); |
| 1436 | } |
| 1437 | #endif |
| 1438 | } |
| 1439 | |
| 1440 | |
| 1441 | |
| 1442 | /* |
| 1443 | ** zDate should be a valid date string. Convert this string into the |
| 1444 | ** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date, |
| 1445 | ** print a fatal error and quit. |
| @@ -2745,13 +2758,26 @@ | |
| 2758 | ** marker is not permitted. This flag relaxes that requirement. |
| 2759 | */ |
| 2760 | CIMINI_ALLOW_MERGE_MARKER = 1<<3, |
| 2761 | |
| 2762 | /* |
| 2763 | ** By default mini-checkins are not allowed to be "older" |
| 2764 | ** than their parent. i.e. they may not have a timestamp |
| 2765 | ** which predates their parent. This flag bypasses that |
| 2766 | ** check. |
| 2767 | */ |
| 2768 | CIMINI_ALLOW_OLDER = 1<<4, |
| 2769 | |
| 2770 | /* |
| 2771 | ** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer |
| 2772 | ** creation of a delta manifest. |
| 2773 | */ |
| 2774 | CIMINI_PREFER_DELTA = 1<<5, |
| 2775 | /* |
| 2776 | ** NOT YET IMPLEMENTED. |
| 2777 | */ |
| 2778 | CIMINI_ALLOW_CLOSED_LEAF = 1<<6 |
| 2779 | }; |
| 2780 | |
| 2781 | /* |
| 2782 | ** Creates a manifest file, written to pOut, from the state in the |
| 2783 | ** fully-populated pCI argument. pCI is not *semantically* modified |
| @@ -2777,10 +2803,11 @@ | |
| 2803 | |
| 2804 | assert(blob_str(&pCI->fileHash)); |
| 2805 | assert(pCI->pParent); |
| 2806 | assert(pCI->zFilename); |
| 2807 | assert(pCI->zUser); |
| 2808 | assert(pCI->zDate); |
| 2809 | |
| 2810 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2811 | /* Potential TODOs include... |
| 2812 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2813 | ** - Maybe add support for tags. Those can be edited via /info page. |
| @@ -2794,29 +2821,11 @@ | |
| 2821 | if(blob_size(&pCI->comment)!=0){ |
| 2822 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2823 | }else{ |
| 2824 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2825 | } |
| 2826 | blob_appendf(pOut, "D %z\n", pCI->zDate); |
| 2827 | manifest_file_rewind(pCI->pParent); |
| 2828 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2829 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2830 | if(cmp<0){ |
| 2831 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| @@ -2893,13 +2902,21 @@ | |
| 2902 | ** This routine uses the state from the given fully-populated pCI |
| 2903 | ** argument to add pCI->fileContent to the database, and create and |
| 2904 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2905 | ** are unchanged. |
| 2906 | ** |
| 2907 | ** pCI may be modified as follows: |
| 2908 | ** |
| 2909 | ** - If pCI->fileHash is empty, this routine populates it with the |
| 2910 | ** repository's preferred hash algorithm. |
| 2911 | ** |
| 2912 | ** - pCI->zDate is normalized to/replaced with a valid date/time |
| 2913 | ** string. If its original value cannot be validated then |
| 2914 | ** this function fails. If pCI->zDate is NULL, the current time |
| 2915 | ** is used. |
| 2916 | ** |
| 2917 | ** pCI's ownership is not modified. |
| 2918 | ** |
| 2919 | ** This function validates several of the inputs and fails if any |
| 2920 | ** validation fails. |
| 2921 | ** |
| 2922 | ** On error, returns false (0) and, if pErr is not NULL, writes a |
| @@ -2918,11 +2935,13 @@ | |
| 2935 | int rid = 0, frid = 0; /* various RIDs */ |
| 2936 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2937 | ManifestFile * zFile; /* file from pCI->pParent */; |
| 2938 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| 2939 | the file */ |
| 2940 | |
| 2941 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 2942 | |
| 2943 | db_begin_transaction(); |
| 2944 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2945 | ci_err((pErr,"No such user: %s", pCI->zUser)); |
| 2946 | } |
| 2947 | assert(pCI->pParent->rid>0); |
| @@ -2942,14 +2961,42 @@ | |
| 2961 | } |
| 2962 | if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags) |
| 2963 | && contains_merge_marker(&pCI->fileContent)){ |
| 2964 | ci_err((pErr,"Content appears to contain a merge conflict marker.")); |
| 2965 | } |
| 2966 | |
| 2967 | { |
| 2968 | /* |
| 2969 | ** Normalize the timestamp. We don't use date_in_standard_format() |
| 2970 | ** because that has side-effects we don't want to trigger here. |
| 2971 | */ |
| 2972 | char * zDVal = db_text( |
| 2973 | 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", |
| 2974 | pCI->zDate ? pCI->zDate : "now"); |
| 2975 | if(zDVal[0]==0){ |
| 2976 | fossil_free(zDVal); |
| 2977 | ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); |
| 2978 | } |
| 2979 | fossil_free(pCI->zDate); |
| 2980 | pCI->zDate = zDVal; |
| 2981 | } |
| 2982 | if(!(CIMINI_ALLOW_OLDER & ciminiFlags) |
| 2983 | && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ |
| 2984 | ci_err((pErr,"Checkin time (%s) may not be older " |
| 2985 | "than its parent (%z).", |
| 2986 | pCI->zDate, |
| 2987 | db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", |
| 2988 | pCI->pParent->rDate) |
| 2989 | )); |
| 2990 | } |
| 2991 | |
| 2992 | /* Potential TODOs include: |
| 2993 | ** |
| 2994 | ** - Commit allows an empty checkin only with a flag, but we |
| 2995 | ** currently disallow it entirely. Conform with commit? |
| 2996 | ** |
| 2997 | ** Non-TODOs: |
| 2998 | ** |
| 2999 | ** - Check for a commit lock would require auto-sync, which this |
| 3000 | ** code cannot do if it's going to be run via a web page. |
| 3001 | */ |
| 3002 | |
| @@ -2969,20 +3016,24 @@ | |
| 3016 | ci_err((pErr,"File [%s] not found in manifest [%S]. " |
| 3017 | "Adding new files is currently not allowed.", |
| 3018 | pCI->zFilename, pCI->zParentUuid)); |
| 3019 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 3020 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 3021 | } |
| 3022 | if(blob_size(&pCI->fileHash)==0){ |
| 3023 | /* Hash the content if it's not done already... */ |
| 3024 | hname_hash(&pCI->fileContent, 0, &pCI->fileHash); |
| 3025 | assert(blob_size(&pCI->fileHash)>0); |
| 3026 | } |
| 3027 | if(zFile){ |
| 3028 | /* Has this file been changed since its previous commit? */ |
| 3029 | assert(blob_size(&pCI->fileHash)); |
| 3030 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 3031 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 3032 | } |
| 3033 | zFilePrevUuid = zFile->zUuid; |
| 3034 | } |
| 3035 | /* Create the manifest... */ |
| 3036 | if(create_manifest_mini(&mf, pCI, pErr)==0){ |
| 3037 | return 0; |
| 3038 | } |
| 3039 | /* Save and deltify the file content... */ |
| @@ -2994,19 +3045,19 @@ | |
| 3045 | content_deltify(frid, &prevFRid, 1, 0); |
| 3046 | } |
| 3047 | /* Save, deltify, and crosslink the manifest... */ |
| 3048 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 3049 | content_deltify(rid, &pCI->pParent->rid, 1, 0); |
| 3050 | if(ciminiFlags & CIMINI_DUMP_MANIFEST){ |
| 3051 | fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf); |
| 3052 | } |
| 3053 | manifest_crosslink(rid, &mf, 0); |
| 3054 | blob_reset(&mf); |
| 3055 | db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0); |
| 3056 | if(pRid!=0){ |
| 3057 | *pRid = rid; |
| 3058 | } |
| 3059 | return 1; |
| 3060 | ci_error: |
| 3061 | assert(db_transaction_nesting_depth()>0); |
| 3062 | db_end_transaction(1); |
| 3063 | return 0; |
| @@ -3040,10 +3091,12 @@ | |
| 3091 | ** is performed beforehand. |
| 3092 | ** --allow-merge-conflict Allows checkin of a file even if it appears |
| 3093 | ** to contain a fossil merge conflict marker. |
| 3094 | ** --user-override USER USER to use instead of the current default. |
| 3095 | ** --date-override DATETIME DATE to use instead of 'now'. |
| 3096 | ** --allow-older Allow a commit to be older than its |
| 3097 | ** ancestor. |
| 3098 | ** --dump-manifest|-d Dumps the generated manifest to stdout. |
| 3099 | ** --wet-run Disables the default dry-run mode. |
| 3100 | ** |
| 3101 | ** Example: |
| 3102 | ** |
| @@ -3078,10 +3131,13 @@ | |
| 3131 | if(find_option("dump-manifest","d",0)!=0){ |
| 3132 | ciminiFlags |= CIMINI_DUMP_MANIFEST; |
| 3133 | } |
| 3134 | if(find_option("allow-merge-conflict",0,0)!=0){ |
| 3135 | ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3136 | } |
| 3137 | if(find_option("allow-older",0,0)!=0){ |
| 3138 | ciminiFlags |= CIMINI_ALLOW_OLDER; |
| 3139 | } |
| 3140 | |
| 3141 | db_find_and_open_repository(0, 0); |
| 3142 | verify_all_options(); |
| 3143 | user_select(); |
| 3144 |