Fossil SCM
Improved error message propagation and added several checkin flags.
Commit
1d0cc12583de3976ab37416e094fd84326cb4011a611e1cd8e426e325597ebc4
Parent
e9e68a6e0179db9…
1 file changed
+153
-76
+153
-76
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2679,26 +2679,36 @@ | ||
| 2679 | 2679 | /* |
| 2680 | 2680 | ** State for the "web-checkin" infrastructure, which enables the |
| 2681 | 2681 | ** ability to commit changes to a single file via an HTTP request. |
| 2682 | 2682 | */ |
| 2683 | 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 | - const char *zUser; /* User name */ | |
| 2689 | - char *zFilename; /* Name of single file to commit. */ | |
| 2690 | - Blob fileContent; /* Content of the modified file. */ | |
| 2691 | - Blob fileHash; /* Hash of this->fileContent */ | |
| 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 | + 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 */ | |
| 2692 | 2696 | }; |
| 2693 | 2697 | #endif /* INTERFACE */ |
| 2694 | 2698 | |
| 2699 | +/* | |
| 2700 | +** Initializes p to a known-valid default state. | |
| 2701 | +*/ | |
| 2695 | 2702 | static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ |
| 2696 | 2703 | memset(p, 0, sizeof(struct CheckinOneFileInfo)); |
| 2697 | 2704 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2698 | 2705 | } |
| 2699 | 2706 | |
| 2707 | +/* | |
| 2708 | +** Frees all memory owned by p, but does not free p. | |
| 2709 | + */ | |
| 2700 | 2710 | static void CheckinOneFileInfo_cleanup( CheckinOneFileInfo * p ){ |
| 2701 | 2711 | blob_reset(&p->comment); |
| 2702 | 2712 | blob_reset(&p->fileContent); |
| 2703 | 2713 | blob_reset(&p->fileHash); |
| 2704 | 2714 | if(p->pParent){ |
| @@ -2705,12 +2715,32 @@ | ||
| 2705 | 2715 | manifest_destroy(p->pParent); |
| 2706 | 2716 | } |
| 2707 | 2717 | fossil_free(p->zFilename); |
| 2708 | 2718 | fossil_free(p->zMimetype); |
| 2709 | 2719 | fossil_free(p->zParentUuid); |
| 2720 | + fossil_free(p->zUser); | |
| 2721 | + fossil_free(p->zDate); | |
| 2710 | 2722 | CheckinOneFileInfo_init(p); |
| 2711 | 2723 | } |
| 2724 | + | |
| 2725 | +/* | |
| 2726 | +** Flags for checkin_one_file() | |
| 2727 | +*/ | |
| 2728 | +enum fossil_ckin1_flags { | |
| 2729 | +/* | |
| 2730 | +** Tells checkin_one_file() to use dry-run mode. | |
| 2731 | +*/ | |
| 2732 | +CKIN1_DRY_RUN = 1, | |
| 2733 | +/* | |
| 2734 | +** Tells checkin_one_file() to allow forking from a non-leaf commit. | |
| 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 | +}; | |
| 2712 | 2742 | |
| 2713 | 2743 | /* |
| 2714 | 2744 | ** Creates a manifest file, written to pOut, from the state in the |
| 2715 | 2745 | ** fully-populated pCI argument. Returns true on success. On error, |
| 2716 | 2746 | ** returns 0 and, if pErr is not NULL, writes an error message there. |
| @@ -2722,11 +2752,10 @@ | ||
| 2722 | 2752 | int cmp = -99; /* filename comparison result */ |
| 2723 | 2753 | int fperm = 0; /* file permissions */ |
| 2724 | 2754 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2725 | 2755 | const char *zFilename = 0; /* filename to use for F-card */ |
| 2726 | 2756 | const char *zUuid = 0; /* UUID for F-card */ |
| 2727 | - const char *zErrMsg = 0; /* For error reporting. */ | |
| 2728 | 2757 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2729 | 2758 | filenames_are_case_sensitive() |
| 2730 | 2759 | ? fossil_strcmp |
| 2731 | 2760 | : fossil_stricmp; |
| 2732 | 2761 | |
| @@ -2733,11 +2762,11 @@ | ||
| 2733 | 2762 | assert(blob_str(&pCI->fileHash)); |
| 2734 | 2763 | assert(pCI->pParent); |
| 2735 | 2764 | assert(pCI->zFilename); |
| 2736 | 2765 | assert(pCI->zUser); |
| 2737 | 2766 | |
| 2738 | -#define mf_err(MSG) zErrMsg = MSG; goto manifest_error | |
| 2767 | +#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 | |
| 2739 | 2768 | /* Potential TODOs include... |
| 2740 | 2769 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2741 | 2770 | ** - Enable adding of new files? It's implemented by disabled until |
| 2742 | 2771 | ** we at least ensure that pCI->zFilename is a path-relative |
| 2743 | 2772 | ** filename. |
| @@ -2753,11 +2782,28 @@ | ||
| 2753 | 2782 | if(blob_size(&pCI->comment)!=0){ |
| 2754 | 2783 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2755 | 2784 | }else{ |
| 2756 | 2785 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2757 | 2786 | } |
| 2758 | - blob_appendf(pOut, "D %z\n", date_in_standard_format("now")); | |
| 2787 | + { | |
| 2788 | + /* | |
| 2789 | + ** We don't use date_in_standard_format() because that has | |
| 2790 | + ** side-effects we don't want to trigger here. | |
| 2791 | + */ | |
| 2792 | + char * zDVal = db_text( | |
| 2793 | + 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", | |
| 2794 | + pCI->zDate | |
| 2795 | + ? pCI->zDate | |
| 2796 | + : "now"); | |
| 2797 | + if(zDVal[0]==0){ | |
| 2798 | + fossil_free(zDVal); | |
| 2799 | + mf_err((pErr, | |
| 2800 | + "Invalid date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"", | |
| 2801 | + pCI->zDate)); | |
| 2802 | + } | |
| 2803 | + blob_appendf(pOut, "D %z\n", zDVal); | |
| 2804 | + } | |
| 2759 | 2805 | |
| 2760 | 2806 | manifest_file_rewind(pCI->pParent); |
| 2761 | 2807 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2762 | 2808 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2763 | 2809 | if(cmp<0){ |
| @@ -2784,11 +2830,11 @@ | ||
| 2784 | 2830 | --pCI->pParent->iFile |
| 2785 | 2831 | /* so that the next step picks up that entry again */; |
| 2786 | 2832 | } |
| 2787 | 2833 | } |
| 2788 | 2834 | if(PERM_LNK==fperm){ |
| 2789 | - mf_err("Cannot commit symlinks with this approach."); | |
| 2835 | + mf_err((pErr,"Cannot commit symlinks with this approach.")); | |
| 2790 | 2836 | }else if(PERM_EXE==fperm){ |
| 2791 | 2837 | zPerm = " x"; |
| 2792 | 2838 | }else{ |
| 2793 | 2839 | zPerm = ""; |
| 2794 | 2840 | } |
| @@ -2798,11 +2844,11 @@ | ||
| 2798 | 2844 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2799 | 2845 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2800 | 2846 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2801 | 2847 | assert(cmp>0); |
| 2802 | 2848 | if(cmp<=0){ |
| 2803 | - mf_err("Internal error: mis-ordering of F-cards detected."); | |
| 2849 | + mf_err((pErr,"Internal error: mis-ordering of F-cards detected.")); | |
| 2804 | 2850 | } |
| 2805 | 2851 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2806 | 2852 | zFile->zName, zFile->zUuid, |
| 2807 | 2853 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2808 | 2854 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| @@ -2818,22 +2864,13 @@ | ||
| 2818 | 2864 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2819 | 2865 | md5sum_blob(pOut, &zCard); |
| 2820 | 2866 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2821 | 2867 | blob_reset(&zCard); |
| 2822 | 2868 | return 1; |
| 2823 | -manifest_error: | |
| 2824 | - assert( zErrMsg ); | |
| 2825 | - if(pErr) blob_append(pErr,zErrMsg,-1); | |
| 2826 | - return 0; | |
| 2827 | 2869 | #undef mf_err |
| 2828 | 2870 | } |
| 2829 | 2871 | |
| 2830 | -enum fossil_ckin1_flags { | |
| 2831 | -CKIN1_DRY_RUN = 1, | |
| 2832 | -CKIN1_ALLOW_FORK = 1<<1 | |
| 2833 | -}; | |
| 2834 | - | |
| 2835 | 2872 | /* |
| 2836 | 2873 | ** EXPERIMENTAL! Subject to change or removal at any time. |
| 2837 | 2874 | ** |
| 2838 | 2875 | ** A so-called "single-file checkin" is a slimmed-down form of the |
| 2839 | 2876 | ** checkin command which accepts only a single file and is intended to |
| @@ -2843,63 +2880,61 @@ | ||
| 2843 | 2880 | ** This routine uses the state from the given fully-populated pCI |
| 2844 | 2881 | ** argument to add pCI->fileContent to the database, and create and |
| 2845 | 2882 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2846 | 2883 | ** are unchanged. |
| 2847 | 2884 | ** |
| 2848 | -** Fails fatally on error. If pRid is not NULL, the RID of the | |
| 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 | |
| 2849 | 2889 | ** resulting manifest is written to *pRid. |
| 2850 | 2890 | ** |
| 2851 | -** ckin1Flags is a bitmask of optional flags from fossil_ckin1_flags enum: | |
| 2852 | -** | |
| 2853 | -** CKIN1_DRY_RUN: rolls back the transaction, else it commits as | |
| 2854 | -** usual. | |
| 2855 | -** | |
| 2856 | -** CKIN1_ALLOW_FORK: if false, checkin will fail if pCI->pParent is not | |
| 2857 | -** currently a leaf. | |
| 2891 | +** ckin1Flags is a bitmask of optional flags from fossil_ckin1_flags | |
| 2892 | +** enum. See that enum for the docs for each flag. | |
| 2858 | 2893 | ** |
| 2859 | 2894 | */ |
| 2860 | -void checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, int ckin1Flags ){ | |
| 2861 | - Blob mf = empty_blob; | |
| 2862 | - Blob err = empty_blob; | |
| 2863 | - int rid = 0, frid = 0, parentRid; | |
| 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 */ | |
| 2864 | 2899 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2865 | - ManifestFile * zFile; | |
| 2866 | - const char * zFilePrevUuid = 0; /* UUID of previous version of | |
| 2867 | - the file */ | |
| 2868 | - | |
| 2900 | + ManifestFile * zFile; /* file from pCI->pParent */; | |
| 2901 | + const char * zFilePrevUuid = 0; /* UUID of previous version of | |
| 2902 | + the file */ | |
| 2903 | +#define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error | |
| 2869 | 2904 | db_begin_transaction(); |
| 2870 | 2905 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2871 | - fossil_fatal("no such user: %s", pCI->zUser); | |
| 2906 | + ci_err((pErr,"No such user: %s", pCI->zUser)); | |
| 2872 | 2907 | } |
| 2873 | - parentRid = name_to_typed_rid(pCI->zParentUuid, "ci"); | |
| 2874 | - assert(parentRid>0); | |
| 2908 | + assert(pCI->pParent->rid>0); | |
| 2875 | 2909 | if(!(CKIN1_ALLOW_FORK & ckin1Flags) |
| 2876 | - && !is_a_leaf(parentRid)){ | |
| 2877 | - fossil_fatal("Parent [%S] is not a leaf and forking is disabled."); | |
| 2910 | + && !is_a_leaf(pCI->pParent->rid)){ | |
| 2911 | + ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", | |
| 2912 | + pCI->zParentUuid)); | |
| 2878 | 2913 | } |
| 2879 | 2914 | /* |
| 2880 | 2915 | ** Confirm that pCI->zFilename can be found in pCI->pParent. |
| 2881 | 2916 | ** If not, fail. This is admittedly an artificial limitation, |
| 2882 | 2917 | ** not strictly necessary. |
| 2883 | 2918 | */ |
| 2884 | 2919 | manifest_file_rewind(pCI->pParent); |
| 2885 | 2920 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2886 | 2921 | if(!zFile){ |
| 2887 | - fossil_fatal("File [%s] not found in manifest [%S]. " | |
| 2888 | - "Adding new files is currently not allowed.", | |
| 2889 | - pCI->zFilename, pCI->zParentUuid); | |
| 2922 | + ci_err((pErr,"File [%s] not found in manifest [%S]. " | |
| 2923 | + "Adding new files is currently not allowed.", | |
| 2924 | + pCI->zFilename, pCI->zParentUuid)); | |
| 2890 | 2925 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 2891 | - fossil_fatal("Cannot save a symlink this way."); | |
| 2926 | + ci_err((pErr,"Cannot save a symlink this way.")); | |
| 2892 | 2927 | }else{ |
| 2893 | 2928 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 2894 | - fossil_fatal("File is unchanged. Not saving."); | |
| 2929 | + ci_err((pErr,"File is unchanged. Not saving.")); | |
| 2895 | 2930 | } |
| 2896 | 2931 | zFilePrevUuid = zFile->zUuid; |
| 2897 | 2932 | } |
| 2898 | 2933 | /* Create the manifest... */ |
| 2899 | - if(create_manifest_one_file(&mf, pCI, &err)==0){ | |
| 2900 | - fossil_fatal("create_manifest_one_file() failed: %B", &err); | |
| 2934 | + if(create_manifest_one_file(&mf, pCI, pErr)==0){ | |
| 2935 | + return 0; | |
| 2901 | 2936 | } |
| 2902 | 2937 | /* Add the file content to the db... */ |
| 2903 | 2938 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 2904 | 2939 | 0, 0, isPrivate); |
| 2905 | 2940 | if(zFilePrevUuid!=0){ |
| @@ -2912,22 +2947,27 @@ | ||
| 2912 | 2947 | /* Save and crosslink the manifest... */ |
| 2913 | 2948 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2914 | 2949 | if(pRid!=0){ |
| 2915 | 2950 | *pRid = rid; |
| 2916 | 2951 | } |
| 2917 | -#if 1 | |
| 2918 | - /* Only for development/debugging... */ | |
| 2919 | - fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); | |
| 2920 | -#endif | |
| 2952 | + if( ckin1Flags & CKIN1_DUMP_MANIFEST ){ | |
| 2953 | + fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); | |
| 2954 | + } | |
| 2921 | 2955 | manifest_crosslink(rid, &mf, 0); |
| 2922 | 2956 | if(CKIN1_DRY_RUN & ckin1Flags){ |
| 2923 | 2957 | fossil_print("Rolling back transaction.\n"); |
| 2924 | 2958 | db_end_transaction(1); |
| 2925 | 2959 | }else{ |
| 2926 | 2960 | db_end_transaction(0); |
| 2927 | 2961 | } |
| 2928 | 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; | |
| 2968 | +#undef ci_err | |
| 2929 | 2969 | } |
| 2930 | 2970 | |
| 2931 | 2971 | /* |
| 2932 | 2972 | ** COMMAND: test-ci-one |
| 2933 | 2973 | ** |
| @@ -2939,50 +2979,79 @@ | ||
| 2939 | 2979 | ** where FILENAME is a repo-relative name as it would appear in the |
| 2940 | 2980 | ** vfile table. |
| 2941 | 2981 | ** |
| 2942 | 2982 | ** Options: |
| 2943 | 2983 | ** |
| 2944 | -** --as FILENAME The repository-side name of the input file, | |
| 2945 | -** relative to the top of the repostory. | |
| 2946 | -** Default is the same as the input file name. | |
| 2947 | -** --comment|-m COMMENT Optional checkin comment. | |
| 2948 | -** --revision|-r VERSION Commit from this version. Default is | |
| 2949 | -** the checkout version (if available) or | |
| 2950 | -** trunk (if used without a checkout). | |
| 2951 | -** --wet-run Disables the default dry-run mode. | |
| 2952 | -** --allow-fork Allows the commit to be made against a | |
| 2953 | -** non-leaf parent. Note that no autosync | |
| 2954 | -** is performed beforehand. | |
| 2984 | +** --repository|-R REPO The repository file to commit to. | |
| 2985 | +** --as FILENAME The repository-side name of the input file, | |
| 2986 | +** relative to the top of the repository. | |
| 2987 | +** Default is the same as the input file name. | |
| 2988 | +** --comment|-m COMMENT Required checkin comment. | |
| 2989 | +** --comment-file|-M FILE Reads checkin comment from the given file. | |
| 2990 | +** --revision|-r VERSION Commit from this version. Default is | |
| 2991 | +** the checkout version (if available) or | |
| 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. | |
| 2955 | 3000 | ** |
| 2956 | 3001 | ** Example: |
| 2957 | 3002 | ** |
| 2958 | 3003 | ** %fossil test-ci-one -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| 2959 | 3004 | ** |
| 2960 | 3005 | */ |
| 2961 | 3006 | void test_ci_one_cmd(){ |
| 2962 | - CheckinOneFileInfo cinf; /* checkin state */ | |
| 3007 | + CheckinOneFileInfo cinf; /* checkin state */ | |
| 2963 | 3008 | int parentVid = 0, newRid = 0; /* RID of parent version and new |
| 2964 | 3009 | version */ |
| 2965 | 3010 | const char * zFilename; /* argv[2] */ |
| 2966 | 3011 | const char * zComment; /* -m comment */ |
| 3012 | + const char * zCommentFile; /* -M FILE */ | |
| 2967 | 3013 | const char * zAsFilename; /* --as filename */ |
| 2968 | 3014 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3015 | + const char * zUser; /* --user-override */ | |
| 3016 | + const char * zDate; /* --date-override */ | |
| 2969 | 3017 | int ckin1Flags = 0; /* flags for checkin_one_file(). */ |
| 2970 | 3018 | if(g.argc<3){ |
| 2971 | 3019 | usage("INFILE"); |
| 2972 | 3020 | } |
| 2973 | 3021 | zComment = find_option("comment","m",1); |
| 3022 | + zCommentFile = find_option("comment-file","M",1); | |
| 2974 | 3023 | zAsFilename = find_option("as",0,1); |
| 2975 | 3024 | zRevision = find_option("revision","r",1); |
| 2976 | - | |
| 3025 | + zUser = find_option("user-override",0,1); | |
| 3026 | + zDate = find_option("date-override",0,1); | |
| 2977 | 3027 | if(find_option("wet-run",0,0)==0){ |
| 2978 | 3028 | ckin1Flags |= CKIN1_DRY_RUN; |
| 2979 | 3029 | } |
| 2980 | - if(find_option("allow-fork",0,0)==0){ | |
| 3030 | + if(find_option("allow-fork",0,0)!=0){ | |
| 2981 | 3031 | ckin1Flags |= CKIN1_ALLOW_FORK; |
| 2982 | 3032 | } |
| 3033 | + if(find_option("dump-manifest","d",0)!=0){ | |
| 3034 | + ckin1Flags |= CKIN1_DUMP_MANIFEST; | |
| 3035 | + } | |
| 3036 | + | |
| 3037 | + CheckinOneFileInfo_init(&cinf); | |
| 2983 | 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 | + } | |
| 3051 | + } | |
| 3052 | + | |
| 2984 | 3053 | db_find_and_open_repository(0, 0); |
| 2985 | 3054 | verify_all_options(); |
| 2986 | 3055 | user_select(); |
| 2987 | 3056 | |
| 2988 | 3057 | if(1){ |
| @@ -2995,17 +3064,16 @@ | ||
| 2995 | 3064 | "repo."); |
| 2996 | 3065 | } |
| 2997 | 3066 | } |
| 2998 | 3067 | |
| 2999 | 3068 | zFilename = g.argv[2]; |
| 3000 | - CheckinOneFileInfo_init(&cinf); | |
| 3001 | - blob_append(&cinf.comment, | |
| 3002 | - zComment ? zComment : "This is a test comment.", | |
| 3003 | - -1); | |
| 3069 | + blob_append(&cinf.comment, zComment, -1); | |
| 3004 | 3070 | cinf.zFilename = mprintf("%s", zAsFilename ? zAsFilename : zFilename); |
| 3005 | - cinf.zUser = login_name(); | |
| 3006 | - | |
| 3071 | + cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); | |
| 3072 | + if(zDate){ | |
| 3073 | + cinf.zDate = mprintf("%s", zDate); | |
| 3074 | + } | |
| 3007 | 3075 | if(zRevision==0 || zRevision[0]==0){ |
| 3008 | 3076 | if(g.localOpen/*checkout*/){ |
| 3009 | 3077 | zRevision = db_lget("checkout-hash", 0)/*leak*/; |
| 3010 | 3078 | }else{ |
| 3011 | 3079 | zRevision = "trunk"; |
| @@ -3021,13 +3089,22 @@ | ||
| 3021 | 3089 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3022 | 3090 | ExtFILE/*may want to reconsider*/); |
| 3023 | 3091 | sha3sum_init(256); |
| 3024 | 3092 | sha3sum_step_blob(&cinf.fileContent); |
| 3025 | 3093 | sha3sum_finish(&cinf.fileHash); |
| 3026 | - checkin_one_file(&cinf, &newRid, ckin1Flags); | |
| 3027 | - CheckinOneFileInfo_cleanup(&cinf); | |
| 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 | + assert(blob_size(&errMsg)==0); | |
| 3100 | + }else{ | |
| 3101 | + assert(blob_size(&errMsg)); | |
| 3102 | + fossil_fatal("%b", &errMsg); | |
| 3103 | + } | |
| 3104 | + } | |
| 3028 | 3105 | if(!(ckin1Flags & CKIN1_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3029 | 3106 | fossil_warning("The checkout state is now out of sync " |
| 3030 | 3107 | "with regards to this commit. It needs to be " |
| 3031 | 3108 | "'update'd or 'close'd and re-'open'ed."); |
| 3032 | 3109 | } |
| 3033 | 3110 | } |
| 3034 | 3111 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2679,26 +2679,36 @@ | |
| 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 | const char *zUser; /* User name */ |
| 2689 | char *zFilename; /* Name of single file to commit. */ |
| 2690 | Blob fileContent; /* Content of the modified file. */ |
| 2691 | Blob fileHash; /* Hash of this->fileContent */ |
| 2692 | }; |
| 2693 | #endif /* INTERFACE */ |
| 2694 | |
| 2695 | static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ |
| 2696 | memset(p, 0, sizeof(struct CheckinOneFileInfo)); |
| 2697 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2698 | } |
| 2699 | |
| 2700 | static void CheckinOneFileInfo_cleanup( CheckinOneFileInfo * p ){ |
| 2701 | blob_reset(&p->comment); |
| 2702 | blob_reset(&p->fileContent); |
| 2703 | blob_reset(&p->fileHash); |
| 2704 | if(p->pParent){ |
| @@ -2705,12 +2715,32 @@ | |
| 2705 | manifest_destroy(p->pParent); |
| 2706 | } |
| 2707 | fossil_free(p->zFilename); |
| 2708 | fossil_free(p->zMimetype); |
| 2709 | fossil_free(p->zParentUuid); |
| 2710 | CheckinOneFileInfo_init(p); |
| 2711 | } |
| 2712 | |
| 2713 | /* |
| 2714 | ** Creates a manifest file, written to pOut, from the state in the |
| 2715 | ** fully-populated pCI argument. Returns true on success. On error, |
| 2716 | ** returns 0 and, if pErr is not NULL, writes an error message there. |
| @@ -2722,11 +2752,10 @@ | |
| 2722 | int cmp = -99; /* filename comparison result */ |
| 2723 | int fperm = 0; /* file permissions */ |
| 2724 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2725 | const char *zFilename = 0; /* filename to use for F-card */ |
| 2726 | const char *zUuid = 0; /* UUID for F-card */ |
| 2727 | const char *zErrMsg = 0; /* For error reporting. */ |
| 2728 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2729 | filenames_are_case_sensitive() |
| 2730 | ? fossil_strcmp |
| 2731 | : fossil_stricmp; |
| 2732 | |
| @@ -2733,11 +2762,11 @@ | |
| 2733 | assert(blob_str(&pCI->fileHash)); |
| 2734 | assert(pCI->pParent); |
| 2735 | assert(pCI->zFilename); |
| 2736 | assert(pCI->zUser); |
| 2737 | |
| 2738 | #define mf_err(MSG) zErrMsg = MSG; goto manifest_error |
| 2739 | /* Potential TODOs include... |
| 2740 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2741 | ** - Enable adding of new files? It's implemented by disabled until |
| 2742 | ** we at least ensure that pCI->zFilename is a path-relative |
| 2743 | ** filename. |
| @@ -2753,11 +2782,28 @@ | |
| 2753 | if(blob_size(&pCI->comment)!=0){ |
| 2754 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2755 | }else{ |
| 2756 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2757 | } |
| 2758 | blob_appendf(pOut, "D %z\n", date_in_standard_format("now")); |
| 2759 | |
| 2760 | manifest_file_rewind(pCI->pParent); |
| 2761 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2762 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2763 | if(cmp<0){ |
| @@ -2784,11 +2830,11 @@ | |
| 2784 | --pCI->pParent->iFile |
| 2785 | /* so that the next step picks up that entry again */; |
| 2786 | } |
| 2787 | } |
| 2788 | if(PERM_LNK==fperm){ |
| 2789 | mf_err("Cannot commit symlinks with this approach."); |
| 2790 | }else if(PERM_EXE==fperm){ |
| 2791 | zPerm = " x"; |
| 2792 | }else{ |
| 2793 | zPerm = ""; |
| 2794 | } |
| @@ -2798,11 +2844,11 @@ | |
| 2798 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2799 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2800 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2801 | assert(cmp>0); |
| 2802 | if(cmp<=0){ |
| 2803 | mf_err("Internal error: mis-ordering of F-cards detected."); |
| 2804 | } |
| 2805 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2806 | zFile->zName, zFile->zUuid, |
| 2807 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2808 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| @@ -2818,22 +2864,13 @@ | |
| 2818 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2819 | md5sum_blob(pOut, &zCard); |
| 2820 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2821 | blob_reset(&zCard); |
| 2822 | return 1; |
| 2823 | manifest_error: |
| 2824 | assert( zErrMsg ); |
| 2825 | if(pErr) blob_append(pErr,zErrMsg,-1); |
| 2826 | return 0; |
| 2827 | #undef mf_err |
| 2828 | } |
| 2829 | |
| 2830 | enum fossil_ckin1_flags { |
| 2831 | CKIN1_DRY_RUN = 1, |
| 2832 | CKIN1_ALLOW_FORK = 1<<1 |
| 2833 | }; |
| 2834 | |
| 2835 | /* |
| 2836 | ** EXPERIMENTAL! Subject to change or removal at any time. |
| 2837 | ** |
| 2838 | ** A so-called "single-file checkin" is a slimmed-down form of the |
| 2839 | ** checkin command which accepts only a single file and is intended to |
| @@ -2843,63 +2880,61 @@ | |
| 2843 | ** This routine uses the state from the given fully-populated pCI |
| 2844 | ** argument to add pCI->fileContent to the database, and create and |
| 2845 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2846 | ** are unchanged. |
| 2847 | ** |
| 2848 | ** Fails fatally on error. If pRid is not NULL, the RID of the |
| 2849 | ** resulting manifest is written to *pRid. |
| 2850 | ** |
| 2851 | ** ckin1Flags is a bitmask of optional flags from fossil_ckin1_flags enum: |
| 2852 | ** |
| 2853 | ** CKIN1_DRY_RUN: rolls back the transaction, else it commits as |
| 2854 | ** usual. |
| 2855 | ** |
| 2856 | ** CKIN1_ALLOW_FORK: if false, checkin will fail if pCI->pParent is not |
| 2857 | ** currently a leaf. |
| 2858 | ** |
| 2859 | */ |
| 2860 | void checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, int ckin1Flags ){ |
| 2861 | Blob mf = empty_blob; |
| 2862 | Blob err = empty_blob; |
| 2863 | int rid = 0, frid = 0, parentRid; |
| 2864 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2865 | ManifestFile * zFile; |
| 2866 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| 2867 | the file */ |
| 2868 | |
| 2869 | db_begin_transaction(); |
| 2870 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2871 | fossil_fatal("no such user: %s", pCI->zUser); |
| 2872 | } |
| 2873 | parentRid = name_to_typed_rid(pCI->zParentUuid, "ci"); |
| 2874 | assert(parentRid>0); |
| 2875 | if(!(CKIN1_ALLOW_FORK & ckin1Flags) |
| 2876 | && !is_a_leaf(parentRid)){ |
| 2877 | fossil_fatal("Parent [%S] is not a leaf and forking is disabled."); |
| 2878 | } |
| 2879 | /* |
| 2880 | ** Confirm that pCI->zFilename can be found in pCI->pParent. |
| 2881 | ** If not, fail. This is admittedly an artificial limitation, |
| 2882 | ** not strictly necessary. |
| 2883 | */ |
| 2884 | manifest_file_rewind(pCI->pParent); |
| 2885 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2886 | if(!zFile){ |
| 2887 | fossil_fatal("File [%s] not found in manifest [%S]. " |
| 2888 | "Adding new files is currently not allowed.", |
| 2889 | pCI->zFilename, pCI->zParentUuid); |
| 2890 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 2891 | fossil_fatal("Cannot save a symlink this way."); |
| 2892 | }else{ |
| 2893 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 2894 | fossil_fatal("File is unchanged. Not saving."); |
| 2895 | } |
| 2896 | zFilePrevUuid = zFile->zUuid; |
| 2897 | } |
| 2898 | /* Create the manifest... */ |
| 2899 | if(create_manifest_one_file(&mf, pCI, &err)==0){ |
| 2900 | fossil_fatal("create_manifest_one_file() failed: %B", &err); |
| 2901 | } |
| 2902 | /* Add the file content to the db... */ |
| 2903 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 2904 | 0, 0, isPrivate); |
| 2905 | if(zFilePrevUuid!=0){ |
| @@ -2912,22 +2947,27 @@ | |
| 2912 | /* Save and crosslink the manifest... */ |
| 2913 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2914 | if(pRid!=0){ |
| 2915 | *pRid = rid; |
| 2916 | } |
| 2917 | #if 1 |
| 2918 | /* Only for development/debugging... */ |
| 2919 | fossil_print("Manifest: %z\n%b", rid_to_uuid(rid), &mf); |
| 2920 | #endif |
| 2921 | manifest_crosslink(rid, &mf, 0); |
| 2922 | if(CKIN1_DRY_RUN & ckin1Flags){ |
| 2923 | fossil_print("Rolling back transaction.\n"); |
| 2924 | db_end_transaction(1); |
| 2925 | }else{ |
| 2926 | db_end_transaction(0); |
| 2927 | } |
| 2928 | blob_reset(&mf); |
| 2929 | } |
| 2930 | |
| 2931 | /* |
| 2932 | ** COMMAND: test-ci-one |
| 2933 | ** |
| @@ -2939,50 +2979,79 @@ | |
| 2939 | ** where FILENAME is a repo-relative name as it would appear in the |
| 2940 | ** vfile table. |
| 2941 | ** |
| 2942 | ** Options: |
| 2943 | ** |
| 2944 | ** --as FILENAME The repository-side name of the input file, |
| 2945 | ** relative to the top of the repostory. |
| 2946 | ** Default is the same as the input file name. |
| 2947 | ** --comment|-m COMMENT Optional checkin comment. |
| 2948 | ** --revision|-r VERSION Commit from this version. Default is |
| 2949 | ** the checkout version (if available) or |
| 2950 | ** trunk (if used without a checkout). |
| 2951 | ** --wet-run Disables the default dry-run mode. |
| 2952 | ** --allow-fork Allows the commit to be made against a |
| 2953 | ** non-leaf parent. Note that no autosync |
| 2954 | ** is performed beforehand. |
| 2955 | ** |
| 2956 | ** Example: |
| 2957 | ** |
| 2958 | ** %fossil test-ci-one -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| 2959 | ** |
| 2960 | */ |
| 2961 | void test_ci_one_cmd(){ |
| 2962 | CheckinOneFileInfo cinf; /* checkin state */ |
| 2963 | int parentVid = 0, newRid = 0; /* RID of parent version and new |
| 2964 | version */ |
| 2965 | const char * zFilename; /* argv[2] */ |
| 2966 | const char * zComment; /* -m comment */ |
| 2967 | const char * zAsFilename; /* --as filename */ |
| 2968 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 2969 | int ckin1Flags = 0; /* flags for checkin_one_file(). */ |
| 2970 | if(g.argc<3){ |
| 2971 | usage("INFILE"); |
| 2972 | } |
| 2973 | zComment = find_option("comment","m",1); |
| 2974 | zAsFilename = find_option("as",0,1); |
| 2975 | zRevision = find_option("revision","r",1); |
| 2976 | |
| 2977 | if(find_option("wet-run",0,0)==0){ |
| 2978 | ckin1Flags |= CKIN1_DRY_RUN; |
| 2979 | } |
| 2980 | if(find_option("allow-fork",0,0)==0){ |
| 2981 | ckin1Flags |= CKIN1_ALLOW_FORK; |
| 2982 | } |
| 2983 | |
| 2984 | db_find_and_open_repository(0, 0); |
| 2985 | verify_all_options(); |
| 2986 | user_select(); |
| 2987 | |
| 2988 | if(1){ |
| @@ -2995,17 +3064,16 @@ | |
| 2995 | "repo."); |
| 2996 | } |
| 2997 | } |
| 2998 | |
| 2999 | zFilename = g.argv[2]; |
| 3000 | CheckinOneFileInfo_init(&cinf); |
| 3001 | blob_append(&cinf.comment, |
| 3002 | zComment ? zComment : "This is a test comment.", |
| 3003 | -1); |
| 3004 | cinf.zFilename = mprintf("%s", zAsFilename ? zAsFilename : zFilename); |
| 3005 | cinf.zUser = login_name(); |
| 3006 | |
| 3007 | if(zRevision==0 || zRevision[0]==0){ |
| 3008 | if(g.localOpen/*checkout*/){ |
| 3009 | zRevision = db_lget("checkout-hash", 0)/*leak*/; |
| 3010 | }else{ |
| 3011 | zRevision = "trunk"; |
| @@ -3021,13 +3089,22 @@ | |
| 3021 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 3022 | ExtFILE/*may want to reconsider*/); |
| 3023 | sha3sum_init(256); |
| 3024 | sha3sum_step_blob(&cinf.fileContent); |
| 3025 | sha3sum_finish(&cinf.fileHash); |
| 3026 | checkin_one_file(&cinf, &newRid, ckin1Flags); |
| 3027 | CheckinOneFileInfo_cleanup(&cinf); |
| 3028 | if(!(ckin1Flags & CKIN1_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3029 | fossil_warning("The checkout state is now out of sync " |
| 3030 | "with regards to this commit. It needs to be " |
| 3031 | "'update'd or 'close'd and re-'open'ed."); |
| 3032 | } |
| 3033 | } |
| 3034 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2679,26 +2679,36 @@ | |
| 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 | 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. |
| 2709 | */ |
| 2710 | static void CheckinOneFileInfo_cleanup( CheckinOneFileInfo * p ){ |
| 2711 | blob_reset(&p->comment); |
| 2712 | blob_reset(&p->fileContent); |
| 2713 | blob_reset(&p->fileHash); |
| 2714 | if(p->pParent){ |
| @@ -2705,12 +2715,32 @@ | |
| 2715 | manifest_destroy(p->pParent); |
| 2716 | } |
| 2717 | fossil_free(p->zFilename); |
| 2718 | fossil_free(p->zMimetype); |
| 2719 | fossil_free(p->zParentUuid); |
| 2720 | fossil_free(p->zUser); |
| 2721 | fossil_free(p->zDate); |
| 2722 | CheckinOneFileInfo_init(p); |
| 2723 | } |
| 2724 | |
| 2725 | /* |
| 2726 | ** Flags for checkin_one_file() |
| 2727 | */ |
| 2728 | enum fossil_ckin1_flags { |
| 2729 | /* |
| 2730 | ** Tells checkin_one_file() to use dry-run mode. |
| 2731 | */ |
| 2732 | CKIN1_DRY_RUN = 1, |
| 2733 | /* |
| 2734 | ** Tells checkin_one_file() to allow forking from a non-leaf commit. |
| 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, |
| 2746 | ** returns 0 and, if pErr is not NULL, writes an error message there. |
| @@ -2722,11 +2752,10 @@ | |
| 2752 | int cmp = -99; /* filename comparison result */ |
| 2753 | int fperm = 0; /* file permissions */ |
| 2754 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2755 | const char *zFilename = 0; /* filename to use for F-card */ |
| 2756 | const char *zUuid = 0; /* UUID for F-card */ |
| 2757 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2758 | filenames_are_case_sensitive() |
| 2759 | ? fossil_strcmp |
| 2760 | : fossil_stricmp; |
| 2761 | |
| @@ -2733,11 +2762,11 @@ | |
| 2762 | assert(blob_str(&pCI->fileHash)); |
| 2763 | assert(pCI->pParent); |
| 2764 | assert(pCI->zFilename); |
| 2765 | assert(pCI->zUser); |
| 2766 | |
| 2767 | #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 |
| 2768 | /* Potential TODOs include... |
| 2769 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2770 | ** - Enable adding of new files? It's implemented by disabled until |
| 2771 | ** we at least ensure that pCI->zFilename is a path-relative |
| 2772 | ** filename. |
| @@ -2753,11 +2782,28 @@ | |
| 2782 | if(blob_size(&pCI->comment)!=0){ |
| 2783 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2784 | }else{ |
| 2785 | blob_append(pOut, "C (no\\scomment)\n", 16); |
| 2786 | } |
| 2787 | { |
| 2788 | /* |
| 2789 | ** We don't use date_in_standard_format() because that has |
| 2790 | ** side-effects we don't want to trigger here. |
| 2791 | */ |
| 2792 | char * zDVal = db_text( |
| 2793 | 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", |
| 2794 | pCI->zDate |
| 2795 | ? pCI->zDate |
| 2796 | : "now"); |
| 2797 | if(zDVal[0]==0){ |
| 2798 | fossil_free(zDVal); |
| 2799 | mf_err((pErr, |
| 2800 | "Invalid date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"", |
| 2801 | pCI->zDate)); |
| 2802 | } |
| 2803 | blob_appendf(pOut, "D %z\n", zDVal); |
| 2804 | } |
| 2805 | |
| 2806 | manifest_file_rewind(pCI->pParent); |
| 2807 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2808 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2809 | if(cmp<0){ |
| @@ -2784,11 +2830,11 @@ | |
| 2830 | --pCI->pParent->iFile |
| 2831 | /* so that the next step picks up that entry again */; |
| 2832 | } |
| 2833 | } |
| 2834 | if(PERM_LNK==fperm){ |
| 2835 | mf_err((pErr,"Cannot commit symlinks with this approach.")); |
| 2836 | }else if(PERM_EXE==fperm){ |
| 2837 | zPerm = " x"; |
| 2838 | }else{ |
| 2839 | zPerm = ""; |
| 2840 | } |
| @@ -2798,11 +2844,11 @@ | |
| 2844 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2845 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2846 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2847 | assert(cmp>0); |
| 2848 | if(cmp<=0){ |
| 2849 | mf_err((pErr,"Internal error: mis-ordering of F-cards detected.")); |
| 2850 | } |
| 2851 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2852 | zFile->zName, zFile->zUuid, |
| 2853 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2854 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| @@ -2818,22 +2864,13 @@ | |
| 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; |
| 2869 | #undef mf_err |
| 2870 | } |
| 2871 | |
| 2872 | /* |
| 2873 | ** EXPERIMENTAL! Subject to change or removal at any time. |
| 2874 | ** |
| 2875 | ** A so-called "single-file checkin" is a slimmed-down form of the |
| 2876 | ** checkin command which accepts only a single file and is intended to |
| @@ -2843,63 +2880,61 @@ | |
| 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 |
| 2889 | ** resulting manifest is written to *pRid. |
| 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 |
| 2902 | the file */ |
| 2903 | #define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error |
| 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]. " |
| 2923 | "Adding new files is currently not allowed.", |
| 2924 | pCI->zFilename, pCI->zParentUuid)); |
| 2925 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 2926 | ci_err((pErr,"Cannot save a symlink this way.")); |
| 2927 | }else{ |
| 2928 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 2929 | ci_err((pErr,"File is unchanged. Not saving.")); |
| 2930 | } |
| 2931 | zFilePrevUuid = zFile->zUuid; |
| 2932 | } |
| 2933 | /* Create the manifest... */ |
| 2934 | if(create_manifest_one_file(&mf, pCI, pErr)==0){ |
| 2935 | return 0; |
| 2936 | } |
| 2937 | /* Add the file content to the db... */ |
| 2938 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 2939 | 0, 0, isPrivate); |
| 2940 | if(zFilePrevUuid!=0){ |
| @@ -2912,22 +2947,27 @@ | |
| 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; |
| 2968 | #undef ci_err |
| 2969 | } |
| 2970 | |
| 2971 | /* |
| 2972 | ** COMMAND: test-ci-one |
| 2973 | ** |
| @@ -2939,50 +2979,79 @@ | |
| 2979 | ** where FILENAME is a repo-relative name as it would appear in the |
| 2980 | ** vfile table. |
| 2981 | ** |
| 2982 | ** Options: |
| 2983 | ** |
| 2984 | ** --repository|-R REPO The repository file to commit to. |
| 2985 | ** --as FILENAME The repository-side name of the input file, |
| 2986 | ** relative to the top of the repository. |
| 2987 | ** Default is the same as the input file name. |
| 2988 | ** --comment|-m COMMENT Required checkin comment. |
| 2989 | ** --comment-file|-M FILE Reads checkin comment from the given file. |
| 2990 | ** --revision|-r VERSION Commit from this version. Default is |
| 2991 | ** the checkout version (if available) or |
| 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: |
| 3002 | ** |
| 3003 | ** %fossil test-ci-one -R REPO -m ... -r foo --as src/myfile.c myfile.c |
| 3004 | ** |
| 3005 | */ |
| 3006 | void test_ci_one_cmd(){ |
| 3007 | CheckinOneFileInfo cinf; /* checkin state */ |
| 3008 | int parentVid = 0, newRid = 0; /* RID of parent version and new |
| 3009 | version */ |
| 3010 | const char * zFilename; /* argv[2] */ |
| 3011 | const char * zComment; /* -m comment */ |
| 3012 | const char * zCommentFile; /* -M FILE */ |
| 3013 | const char * zAsFilename; /* --as filename */ |
| 3014 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 3015 | const char * zUser; /* --user-override */ |
| 3016 | const char * zDate; /* --date-override */ |
| 3017 | int ckin1Flags = 0; /* flags for checkin_one_file(). */ |
| 3018 | if(g.argc<3){ |
| 3019 | usage("INFILE"); |
| 3020 | } |
| 3021 | zComment = find_option("comment","m",1); |
| 3022 | zCommentFile = find_option("comment-file","M",1); |
| 3023 | zAsFilename = find_option("as",0,1); |
| 3024 | zRevision = find_option("revision","r",1); |
| 3025 | zUser = find_option("user-override",0,1); |
| 3026 | zDate = find_option("date-override",0,1); |
| 3027 | if(find_option("wet-run",0,0)==0){ |
| 3028 | ckin1Flags |= CKIN1_DRY_RUN; |
| 3029 | } |
| 3030 | if(find_option("allow-fork",0,0)!=0){ |
| 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 | } |
| 3051 | } |
| 3052 | |
| 3053 | db_find_and_open_repository(0, 0); |
| 3054 | verify_all_options(); |
| 3055 | user_select(); |
| 3056 | |
| 3057 | if(1){ |
| @@ -2995,17 +3064,16 @@ | |
| 3064 | "repo."); |
| 3065 | } |
| 3066 | } |
| 3067 | |
| 3068 | zFilename = g.argv[2]; |
| 3069 | blob_append(&cinf.comment, zComment, -1); |
| 3070 | cinf.zFilename = mprintf("%s", zAsFilename ? zAsFilename : zFilename); |
| 3071 | cinf.zUser = mprintf("%s", zUser ? zUser : login_name()); |
| 3072 | if(zDate){ |
| 3073 | cinf.zDate = mprintf("%s", zDate); |
| 3074 | } |
| 3075 | if(zRevision==0 || zRevision[0]==0){ |
| 3076 | if(g.localOpen/*checkout*/){ |
| 3077 | zRevision = db_lget("checkout-hash", 0)/*leak*/; |
| 3078 | }else{ |
| 3079 | zRevision = "trunk"; |
| @@ -3021,13 +3089,22 @@ | |
| 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 | assert(blob_size(&errMsg)==0); |
| 3100 | }else{ |
| 3101 | assert(blob_size(&errMsg)); |
| 3102 | fossil_fatal("%b", &errMsg); |
| 3103 | } |
| 3104 | } |
| 3105 | if(!(ckin1Flags & CKIN1_DRY_RUN) && newRid!=0 && g.localOpen!=0){ |
| 3106 | fossil_warning("The checkout state is now out of sync " |
| 3107 | "with regards to this commit. It needs to be " |
| 3108 | "'update'd or 'close'd and re-'open'ed."); |
| 3109 | } |
| 3110 | } |
| 3111 |