Fossil SCM
More work/cleanup on the single-file commit.
Commit
138c07be85232f05e93e23a4613380870bfd86f088cf6750dc86ddfce8ea1d3f
Parent
cb4d48ac058b901…
1 file changed
+134
-52
+134
-52
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2678,11 +2678,11 @@ | ||
| 2678 | 2678 | #if INTERFACE |
| 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 | -struct CheckinWebInfo { | |
| 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 | const char *zUser; /* User name */ |
| 2688 | 2688 | char *zFilename; /* Name of single file to commit. */ |
| @@ -2689,53 +2689,65 @@ | ||
| 2689 | 2689 | Blob fileContent; /* Content of the modified file. */ |
| 2690 | 2690 | Blob fileHash; /* Hash of this->fileContent */ |
| 2691 | 2691 | }; |
| 2692 | 2692 | #endif /* INTERFACE */ |
| 2693 | 2693 | |
| 2694 | -static void CheckinWebInfo_init( CheckinWebInfo * p ){ | |
| 2695 | - memset(p, 0, sizeof(struct CheckinWebInfo)); | |
| 2694 | +static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ | |
| 2695 | + memset(p, 0, sizeof(struct CheckinOneFileInfo)); | |
| 2696 | 2696 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2697 | 2697 | } |
| 2698 | 2698 | |
| 2699 | -static void CheckinWebInfo_cleanup( CheckinWebInfo * p ){ | |
| 2699 | +static void CheckinOneFileInfo_cleanup( CheckinOneFileInfo * p ){ | |
| 2700 | 2700 | blob_reset(&p->comment); |
| 2701 | 2701 | blob_reset(&p->fileContent); |
| 2702 | 2702 | blob_reset(&p->fileHash); |
| 2703 | 2703 | if(p->pParent){ |
| 2704 | 2704 | manifest_destroy(p->pParent); |
| 2705 | 2705 | } |
| 2706 | 2706 | fossil_free(p->zFilename); |
| 2707 | 2707 | fossil_free(p->zMimetype); |
| 2708 | - CheckinWebInfo_init(p); | |
| 2708 | + CheckinOneFileInfo_init(p); | |
| 2709 | 2709 | } |
| 2710 | 2710 | |
| 2711 | 2711 | /* |
| 2712 | 2712 | ** Creates a manifest file, written to pOut, from the state in the |
| 2713 | -** fully-populated pCI argument. On error, returns non-0 and, if | |
| 2714 | -** pErr is not NULL, writes an error message there. | |
| 2713 | +** fully-populated pCI argument. Returns true on success. On error, | |
| 2714 | +** returns 0 and, if pErr is not NULL, writes an error message there. | |
| 2715 | 2715 | */ |
| 2716 | -static int create_manifest_web( Blob * pOut, CheckinWebInfo * pCI, | |
| 2717 | - Blob * pErr){ | |
| 2718 | - Blob zCard = empty_blob; | |
| 2719 | - ManifestFile *zFile; | |
| 2720 | - int cmp = -99; | |
| 2721 | - const char *zPerm = ""; | |
| 2722 | - const char *zFilename = 0; | |
| 2723 | - const char *zUuid = 0; | |
| 2724 | - int (*fncmp)(char const *, char const *) = | |
| 2716 | +static int create_manifest_one_file( Blob * pOut, CheckinOneFileInfo * pCI, | |
| 2717 | + Blob * pErr){ | |
| 2718 | + Blob zCard = empty_blob; /* Z-card checksum */ | |
| 2719 | + ManifestFile *zFile; /* One file entry from the pCI->pParent */ | |
| 2720 | + int cmp = -99; /* filename comparison result */ | |
| 2721 | + int fperm = 0; /* file permissions */ | |
| 2722 | + const char *zPerm = 0; /* permissions for new F-card */ | |
| 2723 | + const char *zFilename = 0; /* filename to use for F-card */ | |
| 2724 | + const char *zUuid = 0; /* UUID for F-card */ | |
| 2725 | + const char *zErrMsg = 0; /* For error reporting. */ | |
| 2726 | + int (*fncmp)(char const *, char const *) = /* filename comparator */ | |
| 2725 | 2727 | filenames_are_case_sensitive() |
| 2726 | 2728 | ? fossil_strcmp |
| 2727 | 2729 | : fossil_stricmp; |
| 2728 | 2730 | |
| 2729 | -#define mf_err(MSG) if(pErr) blob_append(pErr,MSG,-1); return 1 | |
| 2731 | + assert(blob_str(&pCI->fileHash)); | |
| 2732 | + assert(pCI->pParent); | |
| 2733 | + assert(pCI->zFilename); | |
| 2734 | + assert(pCI->zUser); | |
| 2735 | + | |
| 2736 | +#define mf_err(MSG) zErrMsg = MSG; goto manifest_error | |
| 2730 | 2737 | /* Potential TODOs include... |
| 2731 | 2738 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2732 | 2739 | ** - Enable adding of new files? It's implemented by disabled until |
| 2733 | 2740 | ** we at least ensure that pCI->zFilename is a path-relative |
| 2734 | 2741 | ** filename. |
| 2735 | 2742 | ** - Maybe add support for tags. Those can be edited via /info page. |
| 2736 | 2743 | ** - Check for a commit to a closed leaf and re-open it. |
| 2744 | + ** - Symlinks: if we're really in a checkout, handle commit/add of | |
| 2745 | + ** symlinks like a normal commit would. For now we bail out if | |
| 2746 | + ** told to handle a symlink because there would seem to be no(?) | |
| 2747 | + ** sensible way to handle a symlink add/checkin without a | |
| 2748 | + ** checkout. | |
| 2737 | 2749 | */ |
| 2738 | 2750 | blob_zero(pOut); |
| 2739 | 2751 | if(blob_size(&pCI->comment)!=0){ |
| 2740 | 2752 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2741 | 2753 | }else{ |
| @@ -2745,54 +2757,58 @@ | ||
| 2745 | 2757 | |
| 2746 | 2758 | manifest_file_rewind(pCI->pParent); |
| 2747 | 2759 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2748 | 2760 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2749 | 2761 | if(cmp<0){ |
| 2750 | - blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, | |
| 2751 | - zFile->zPerm); | |
| 2762 | + blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, | |
| 2763 | + (zFile->zPerm && *zFile->zPerm) ? " " : "", | |
| 2764 | + (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); | |
| 2752 | 2765 | }else{ |
| 2753 | 2766 | break; |
| 2754 | 2767 | } |
| 2755 | 2768 | } |
| 2756 | 2769 | if(cmp==0){ /* Match */ |
| 2757 | - const int perm = manifest_file_mperm(zFile); | |
| 2758 | - if(PERM_LNK==perm){ | |
| 2759 | - mf_err("Cannot commit symlinks with this approach."); | |
| 2760 | - } | |
| 2761 | - zPerm = PERM_EXE==perm ? " x" : ""; | |
| 2770 | + fperm = manifest_file_mperm(zFile); | |
| 2762 | 2771 | zFilename = zFile->zName |
| 2763 | - /* use original name in case of case difference */; | |
| 2772 | + /* use original name in case of name-case difference */; | |
| 2764 | 2773 | zUuid = blob_str(&pCI->fileHash); |
| 2765 | 2774 | }else{ |
| 2766 | 2775 | /* This is a new file. */ |
| 2776 | + fperm = file_perm(pCI->zFilename, ExtFILE); | |
| 2767 | 2777 | zFilename = pCI->zFilename; |
| 2768 | - zPerm = ""; | |
| 2769 | 2778 | zUuid = blob_str(&pCI->fileHash); |
| 2770 | 2779 | if(cmp>0){ |
| 2771 | 2780 | assert(zFile!=0); |
| 2772 | 2781 | assert(pCI->pParent->iFile>0); |
| 2773 | 2782 | --pCI->pParent->iFile |
| 2774 | 2783 | /* so that the next step picks up that entry again */; |
| 2775 | 2784 | } |
| 2785 | + } | |
| 2786 | + if(PERM_LNK==fperm){ | |
| 2787 | + mf_err("Cannot commit symlinks with this approach."); | |
| 2788 | + }else if(PERM_EXE==fperm){ | |
| 2789 | + zPerm = " x"; | |
| 2790 | + }else{ | |
| 2791 | + zPerm = ""; | |
| 2776 | 2792 | } |
| 2777 | 2793 | assert(zFilename); |
| 2778 | 2794 | assert(zUuid); |
| 2779 | 2795 | assert(zPerm); |
| 2780 | 2796 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2781 | 2797 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2782 | 2798 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2783 | 2799 | assert(cmp>0); |
| 2784 | 2800 | if(cmp<=0){ |
| 2785 | - mf_err("Mis-ordering of F-cards."); | |
| 2801 | + mf_err("Internal error: mis-ordering of F-cards detected."); | |
| 2786 | 2802 | } |
| 2787 | 2803 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2788 | 2804 | zFile->zName, zFile->zUuid, |
| 2789 | 2805 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2790 | 2806 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2791 | 2807 | } |
| 2792 | 2808 | |
| 2793 | - if(pCI->zMimetype){ | |
| 2809 | + if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ | |
| 2794 | 2810 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2795 | 2811 | } |
| 2796 | 2812 | |
| 2797 | 2813 | blob_appendf(pOut, "P %z\n", |
| 2798 | 2814 | db_text(0,"SELECT uuid FROM blob WHERE rid=%d", |
| @@ -2800,120 +2816,186 @@ | ||
| 2800 | 2816 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2801 | 2817 | md5sum_blob(pOut, &zCard); |
| 2802 | 2818 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2803 | 2819 | blob_reset(&zCard); |
| 2804 | 2820 | return 0; |
| 2821 | +manifest_error: | |
| 2822 | + assert( zErrMsg ); | |
| 2823 | + if(pErr) blob_append(pErr,zErrMsg,-1); | |
| 2824 | + return 1; | |
| 2805 | 2825 | #undef mf_err |
| 2806 | 2826 | } |
| 2807 | 2827 | |
| 2808 | 2828 | /* |
| 2809 | -** EXPERIMENTAL! A so-called "web checkin" is a slimmed-down form of | |
| 2810 | -** the checkin command which accepts only a single file and is | |
| 2811 | -** intended to accept edits to a file via the web interface. | |
| 2829 | +** EXPERIMENTAL! Subject to change or removal at any time. | |
| 2830 | +** | |
| 2831 | +** A so-called "single-file checkin" is a slimmed-down form of the | |
| 2832 | +** checkin command which accepts only a single file and is intended to | |
| 2833 | +** accept edits to a file via the web interface or from the CLI from | |
| 2834 | +** outside of a checkout. | |
| 2812 | 2835 | ** |
| 2813 | 2836 | ** This routine uses the state from the given fully-populated pCI |
| 2814 | 2837 | ** argument to add pCI->fileContent to the database, and create and |
| 2815 | -** save a manifest for that change. | |
| 2838 | +** save a manifest for that change. Ownership of pCI and its contents | |
| 2839 | +** are unchanged. | |
| 2816 | 2840 | ** |
| 2817 | 2841 | ** Fails fatally on error. If pRid is not NULL, the RID of the |
| 2818 | 2842 | ** resulting manifest is written to *pRid. If bDryRun is true, |
| 2819 | 2843 | ** it rolls back its transaction, else it commits as usual. |
| 2820 | -** | |
| 2821 | 2844 | */ |
| 2822 | -void checkin_web( CheckinWebInfo * pCI, int *pRid, int bDryRun ){ | |
| 2845 | +void checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, int bDryRun ){ | |
| 2823 | 2846 | Blob mf = empty_blob; |
| 2824 | 2847 | Blob err = empty_blob; |
| 2825 | 2848 | int rid = 0, frid = 0; |
| 2826 | 2849 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2827 | 2850 | ManifestFile * zFile; |
| 2851 | + const char * zFilePrevUuid = 0; /* UUID of previous version of | |
| 2852 | + the file */ | |
| 2828 | 2853 | |
| 2829 | 2854 | db_begin_transaction(); |
| 2830 | 2855 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2831 | 2856 | fossil_fatal("no such user: %s", pCI->zUser); |
| 2832 | 2857 | } |
| 2858 | + /* | |
| 2859 | + ** Confirm that pCI->zFilename can be found in pCI->pParent. | |
| 2860 | + ** If not, fail. This is admittedly an artificial limitation, | |
| 2861 | + ** not strictly necessary. | |
| 2862 | + */ | |
| 2833 | 2863 | manifest_file_rewind(pCI->pParent); |
| 2834 | 2864 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2835 | 2865 | if(!zFile){ |
| 2836 | 2866 | fossil_fatal("File [%s] not found in manifest. " |
| 2837 | 2867 | "Adding new files is currently not allowed.", |
| 2838 | 2868 | pCI->zFilename); |
| 2869 | + }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ | |
| 2870 | + fossil_fatal("Cannot save a symlink this way."); | |
| 2871 | + }else{ | |
| 2872 | + if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ | |
| 2873 | + fossil_fatal("File is unchanged. Not saving."); | |
| 2874 | + } | |
| 2875 | + zFilePrevUuid = zFile->zUuid; | |
| 2839 | 2876 | } |
| 2840 | - if(create_manifest_web(&mf, pCI, &err)!=0){ | |
| 2841 | - fossil_fatal("create_manifest_web() failed: %B", &err); | |
| 2877 | + /* Create the manifest... */ | |
| 2878 | + if(create_manifest_one_file(&mf, pCI, &err)==0){ | |
| 2879 | + fossil_fatal("create_manifest_one_file() failed: %B", &err); | |
| 2842 | 2880 | } |
| 2881 | + /* Add the file content to the db... */ | |
| 2843 | 2882 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 2844 | 2883 | 0, 0, isPrivate); |
| 2845 | - manifest_file_rewind(pCI->pParent); | |
| 2846 | - zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); | |
| 2847 | - if(zFile!=0){ | |
| 2884 | + if(zFilePrevUuid!=0){ | |
| 2885 | + /* Deltify against previous file version... */ | |
| 2848 | 2886 | int prevFRid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", |
| 2849 | - zFile->zUuid); | |
| 2887 | + zFilePrevUuid); | |
| 2850 | 2888 | assert(prevFRid>0); |
| 2851 | 2889 | content_deltify(frid, &prevFRid, 1, 0); |
| 2852 | 2890 | } |
| 2891 | + /* Save and crosslink the manifest... */ | |
| 2853 | 2892 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2854 | 2893 | if(pRid!=0){ |
| 2855 | 2894 | *pRid = rid; |
| 2856 | 2895 | } |
| 2896 | +#if 1 | |
| 2897 | + /* Only for development/debugging... */ | |
| 2857 | 2898 | fossil_print("Manifest:\n%b", &mf); |
| 2899 | +#endif | |
| 2858 | 2900 | manifest_crosslink(rid, &mf, 0); |
| 2859 | 2901 | if(bDryRun){ |
| 2860 | 2902 | fossil_print("Rolling back transaction.\n"); |
| 2861 | 2903 | } |
| 2862 | 2904 | db_end_transaction(bDryRun ? 1 : 0); |
| 2863 | 2905 | blob_reset(&mf); |
| 2864 | 2906 | } |
| 2865 | 2907 | |
| 2866 | 2908 | /* |
| 2867 | -** COMMAND: test-ci-web | |
| 2909 | +** COMMAND: test-ci-one | |
| 2868 | 2910 | ** |
| 2869 | 2911 | ** This is an on-going experiment, subject to change or removal at |
| 2870 | 2912 | ** any time. |
| 2871 | 2913 | ** |
| 2872 | 2914 | ** Usage: %fossil ?OPTIONS? FILENAME |
| 2873 | 2915 | ** |
| 2874 | 2916 | ** where FILENAME is a repo-relative name as it would appear in the |
| 2875 | 2917 | ** vfile table. |
| 2918 | +** | |
| 2919 | +** Options: | |
| 2920 | +** | |
| 2921 | +** --as FILENAME The repository-side name of the input file, | |
| 2922 | +** relative to the top of the repostory. | |
| 2923 | +** Default is the same as the input file name. | |
| 2924 | +** --comment|-m COMMENT Optional checkin comment. | |
| 2925 | +** --revision|-r VERSION Commit from this version. Default is | |
| 2926 | +** the checkout version (if available) or | |
| 2927 | +** trunk (if used without a checkout). | |
| 2928 | +** --wet-run Disables the default dry-run mode. | |
| 2929 | +** | |
| 2930 | +** Example: | |
| 2931 | +** | |
| 2932 | +** %fossil -m ... -r foo --as src/myfile.c myfile.c | |
| 2933 | +** | |
| 2876 | 2934 | */ |
| 2877 | 2935 | void test_ci_one_cmd(){ |
| 2878 | - CheckinWebInfo cinf; | |
| 2879 | - int parentVid = 0, newRid = 0; | |
| 2880 | - const char * zFilename; | |
| 2881 | - const char * zComment; | |
| 2882 | - const char * zAsFilename; | |
| 2936 | + CheckinOneFileInfo cinf; /* checkin state */ | |
| 2937 | + int parentVid = 0, newRid = 0; /* RID of parent version and new | |
| 2938 | + version */ | |
| 2939 | + const char * zFilename; /* argv[2] */ | |
| 2940 | + const char * zComment; /* -m comment */ | |
| 2941 | + const char * zAsFilename; /* --as filename */ | |
| 2942 | + const char * zRevision; /* --revision|-r [=trunk|checkout] */ | |
| 2883 | 2943 | int wetRunFlag = 0; |
| 2884 | 2944 | |
| 2885 | 2945 | if(g.argc<3){ |
| 2886 | 2946 | usage("INFILE"); |
| 2887 | 2947 | } |
| 2888 | - db_find_and_open_repository(0, 0); | |
| 2889 | 2948 | wetRunFlag = find_option("wet-run",0,0)!=0; |
| 2890 | 2949 | zComment = find_option("comment","m",1); |
| 2891 | 2950 | zAsFilename = find_option("as",0,1); |
| 2951 | + zRevision = find_option("revision","r",1); | |
| 2952 | + | |
| 2953 | + db_find_and_open_repository(0, 0); | |
| 2892 | 2954 | verify_all_options(); |
| 2893 | 2955 | user_select(); |
| 2894 | 2956 | |
| 2957 | + if(1){ | |
| 2958 | + char * zProjCode = db_get("project-code",0); | |
| 2959 | + assert(zProjCode); | |
| 2960 | + if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", | |
| 2961 | + zProjCode)){ | |
| 2962 | + fossil_fatal("Never, ever run this in/on the core fossil repo " | |
| 2963 | + "until it's been well-vetted. Use a temp/test " | |
| 2964 | + "repo."); | |
| 2965 | + } | |
| 2966 | + } | |
| 2967 | + | |
| 2895 | 2968 | zFilename = g.argv[2]; |
| 2896 | - CheckinWebInfo_init(&cinf); | |
| 2969 | + CheckinOneFileInfo_init(&cinf); | |
| 2897 | 2970 | blob_append(&cinf.comment, |
| 2898 | 2971 | zComment ? zComment : "This is a test comment.", |
| 2899 | 2972 | -1); |
| 2900 | 2973 | cinf.zFilename = mprintf("%s", zAsFilename ? zAsFilename : zFilename); |
| 2901 | 2974 | cinf.zUser = login_name(); |
| 2902 | 2975 | |
| 2903 | - cinf.pParent = manifest_get_by_name("trunk", &parentVid); | |
| 2976 | + if(zRevision==0 || zRevision[0]==0){ | |
| 2977 | + if(g.localOpen/*checkout*/){ | |
| 2978 | + zRevision = db_lget("checkout-hash", 0) /*leak*/; | |
| 2979 | + }else{ | |
| 2980 | + zRevision = "trunk"; | |
| 2981 | + } | |
| 2982 | + if(zRevision==0 || zRevision[0]==0){ | |
| 2983 | + fossil_fatal("Cannot determine version to commit to."); | |
| 2984 | + } | |
| 2985 | + } | |
| 2986 | + cinf.pParent = manifest_get_by_name(zRevision, &parentVid); | |
| 2904 | 2987 | assert(parentVid>0); |
| 2905 | 2988 | assert(cinf.pParent!=0); |
| 2906 | 2989 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 2907 | 2990 | ExtFILE/*may want to reconsider*/); |
| 2908 | 2991 | sha3sum_init(256); |
| 2909 | 2992 | sha3sum_step_blob(&cinf.fileContent); |
| 2910 | 2993 | sha3sum_finish(&cinf.fileHash); |
| 2911 | - checkin_web(&cinf, &newRid, wetRunFlag ? 0 : 1); | |
| 2912 | - CheckinWebInfo_cleanup(&cinf); | |
| 2994 | + checkin_one_file(&cinf, &newRid, wetRunFlag ? 0 : 1); | |
| 2995 | + CheckinOneFileInfo_cleanup(&cinf); | |
| 2913 | 2996 | if(wetRunFlag!=0 && newRid!=0 && g.localOpen!=0){ |
| 2914 | 2997 | fossil_warning("The checkout state is now out of sync " |
| 2915 | 2998 | "with regards to this commit. It needs to be " |
| 2916 | 2999 | "'update'd or 'close'd and re-'open'ed."); |
| 2917 | - /* vfile_check_signature(newRid,0); does not do the trick */ | |
| 2918 | 3000 | } |
| 2919 | 3001 | } |
| 2920 | 3002 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2678,11 +2678,11 @@ | |
| 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 CheckinWebInfo { |
| 2684 | Blob comment; /* Check-in comment text */ |
| 2685 | char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
| 2686 | Manifest * pParent; /* parent checkin */ |
| 2687 | const char *zUser; /* User name */ |
| 2688 | char *zFilename; /* Name of single file to commit. */ |
| @@ -2689,53 +2689,65 @@ | |
| 2689 | Blob fileContent; /* Content of the modified file. */ |
| 2690 | Blob fileHash; /* Hash of this->fileContent */ |
| 2691 | }; |
| 2692 | #endif /* INTERFACE */ |
| 2693 | |
| 2694 | static void CheckinWebInfo_init( CheckinWebInfo * p ){ |
| 2695 | memset(p, 0, sizeof(struct CheckinWebInfo)); |
| 2696 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2697 | } |
| 2698 | |
| 2699 | static void CheckinWebInfo_cleanup( CheckinWebInfo * p ){ |
| 2700 | blob_reset(&p->comment); |
| 2701 | blob_reset(&p->fileContent); |
| 2702 | blob_reset(&p->fileHash); |
| 2703 | if(p->pParent){ |
| 2704 | manifest_destroy(p->pParent); |
| 2705 | } |
| 2706 | fossil_free(p->zFilename); |
| 2707 | fossil_free(p->zMimetype); |
| 2708 | CheckinWebInfo_init(p); |
| 2709 | } |
| 2710 | |
| 2711 | /* |
| 2712 | ** Creates a manifest file, written to pOut, from the state in the |
| 2713 | ** fully-populated pCI argument. On error, returns non-0 and, if |
| 2714 | ** pErr is not NULL, writes an error message there. |
| 2715 | */ |
| 2716 | static int create_manifest_web( Blob * pOut, CheckinWebInfo * pCI, |
| 2717 | Blob * pErr){ |
| 2718 | Blob zCard = empty_blob; |
| 2719 | ManifestFile *zFile; |
| 2720 | int cmp = -99; |
| 2721 | const char *zPerm = ""; |
| 2722 | const char *zFilename = 0; |
| 2723 | const char *zUuid = 0; |
| 2724 | int (*fncmp)(char const *, char const *) = |
| 2725 | filenames_are_case_sensitive() |
| 2726 | ? fossil_strcmp |
| 2727 | : fossil_stricmp; |
| 2728 | |
| 2729 | #define mf_err(MSG) if(pErr) blob_append(pErr,MSG,-1); return 1 |
| 2730 | /* Potential TODOs include... |
| 2731 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2732 | ** - Enable adding of new files? It's implemented by disabled until |
| 2733 | ** we at least ensure that pCI->zFilename is a path-relative |
| 2734 | ** filename. |
| 2735 | ** - Maybe add support for tags. Those can be edited via /info page. |
| 2736 | ** - Check for a commit to a closed leaf and re-open it. |
| 2737 | */ |
| 2738 | blob_zero(pOut); |
| 2739 | if(blob_size(&pCI->comment)!=0){ |
| 2740 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2741 | }else{ |
| @@ -2745,54 +2757,58 @@ | |
| 2745 | |
| 2746 | manifest_file_rewind(pCI->pParent); |
| 2747 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2748 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2749 | if(cmp<0){ |
| 2750 | blob_appendf(pOut, "F %F %s%s\n", zFile->zName, zFile->zUuid, |
| 2751 | zFile->zPerm); |
| 2752 | }else{ |
| 2753 | break; |
| 2754 | } |
| 2755 | } |
| 2756 | if(cmp==0){ /* Match */ |
| 2757 | const int perm = manifest_file_mperm(zFile); |
| 2758 | if(PERM_LNK==perm){ |
| 2759 | mf_err("Cannot commit symlinks with this approach."); |
| 2760 | } |
| 2761 | zPerm = PERM_EXE==perm ? " x" : ""; |
| 2762 | zFilename = zFile->zName |
| 2763 | /* use original name in case of case difference */; |
| 2764 | zUuid = blob_str(&pCI->fileHash); |
| 2765 | }else{ |
| 2766 | /* This is a new file. */ |
| 2767 | zFilename = pCI->zFilename; |
| 2768 | zPerm = ""; |
| 2769 | zUuid = blob_str(&pCI->fileHash); |
| 2770 | if(cmp>0){ |
| 2771 | assert(zFile!=0); |
| 2772 | assert(pCI->pParent->iFile>0); |
| 2773 | --pCI->pParent->iFile |
| 2774 | /* so that the next step picks up that entry again */; |
| 2775 | } |
| 2776 | } |
| 2777 | assert(zFilename); |
| 2778 | assert(zUuid); |
| 2779 | assert(zPerm); |
| 2780 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2781 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2782 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2783 | assert(cmp>0); |
| 2784 | if(cmp<=0){ |
| 2785 | mf_err("Mis-ordering of F-cards."); |
| 2786 | } |
| 2787 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2788 | zFile->zName, zFile->zUuid, |
| 2789 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2790 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2791 | } |
| 2792 | |
| 2793 | if(pCI->zMimetype){ |
| 2794 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2795 | } |
| 2796 | |
| 2797 | blob_appendf(pOut, "P %z\n", |
| 2798 | db_text(0,"SELECT uuid FROM blob WHERE rid=%d", |
| @@ -2800,120 +2816,186 @@ | |
| 2800 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2801 | md5sum_blob(pOut, &zCard); |
| 2802 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2803 | blob_reset(&zCard); |
| 2804 | return 0; |
| 2805 | #undef mf_err |
| 2806 | } |
| 2807 | |
| 2808 | /* |
| 2809 | ** EXPERIMENTAL! A so-called "web checkin" is a slimmed-down form of |
| 2810 | ** the checkin command which accepts only a single file and is |
| 2811 | ** intended to accept edits to a file via the web interface. |
| 2812 | ** |
| 2813 | ** This routine uses the state from the given fully-populated pCI |
| 2814 | ** argument to add pCI->fileContent to the database, and create and |
| 2815 | ** save a manifest for that change. |
| 2816 | ** |
| 2817 | ** Fails fatally on error. If pRid is not NULL, the RID of the |
| 2818 | ** resulting manifest is written to *pRid. If bDryRun is true, |
| 2819 | ** it rolls back its transaction, else it commits as usual. |
| 2820 | ** |
| 2821 | */ |
| 2822 | void checkin_web( CheckinWebInfo * pCI, int *pRid, int bDryRun ){ |
| 2823 | Blob mf = empty_blob; |
| 2824 | Blob err = empty_blob; |
| 2825 | int rid = 0, frid = 0; |
| 2826 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2827 | ManifestFile * zFile; |
| 2828 | |
| 2829 | db_begin_transaction(); |
| 2830 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2831 | fossil_fatal("no such user: %s", pCI->zUser); |
| 2832 | } |
| 2833 | manifest_file_rewind(pCI->pParent); |
| 2834 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2835 | if(!zFile){ |
| 2836 | fossil_fatal("File [%s] not found in manifest. " |
| 2837 | "Adding new files is currently not allowed.", |
| 2838 | pCI->zFilename); |
| 2839 | } |
| 2840 | if(create_manifest_web(&mf, pCI, &err)!=0){ |
| 2841 | fossil_fatal("create_manifest_web() failed: %B", &err); |
| 2842 | } |
| 2843 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 2844 | 0, 0, isPrivate); |
| 2845 | manifest_file_rewind(pCI->pParent); |
| 2846 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2847 | if(zFile!=0){ |
| 2848 | int prevFRid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", |
| 2849 | zFile->zUuid); |
| 2850 | assert(prevFRid>0); |
| 2851 | content_deltify(frid, &prevFRid, 1, 0); |
| 2852 | } |
| 2853 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2854 | if(pRid!=0){ |
| 2855 | *pRid = rid; |
| 2856 | } |
| 2857 | fossil_print("Manifest:\n%b", &mf); |
| 2858 | manifest_crosslink(rid, &mf, 0); |
| 2859 | if(bDryRun){ |
| 2860 | fossil_print("Rolling back transaction.\n"); |
| 2861 | } |
| 2862 | db_end_transaction(bDryRun ? 1 : 0); |
| 2863 | blob_reset(&mf); |
| 2864 | } |
| 2865 | |
| 2866 | /* |
| 2867 | ** COMMAND: test-ci-web |
| 2868 | ** |
| 2869 | ** This is an on-going experiment, subject to change or removal at |
| 2870 | ** any time. |
| 2871 | ** |
| 2872 | ** Usage: %fossil ?OPTIONS? FILENAME |
| 2873 | ** |
| 2874 | ** where FILENAME is a repo-relative name as it would appear in the |
| 2875 | ** vfile table. |
| 2876 | */ |
| 2877 | void test_ci_one_cmd(){ |
| 2878 | CheckinWebInfo cinf; |
| 2879 | int parentVid = 0, newRid = 0; |
| 2880 | const char * zFilename; |
| 2881 | const char * zComment; |
| 2882 | const char * zAsFilename; |
| 2883 | int wetRunFlag = 0; |
| 2884 | |
| 2885 | if(g.argc<3){ |
| 2886 | usage("INFILE"); |
| 2887 | } |
| 2888 | db_find_and_open_repository(0, 0); |
| 2889 | wetRunFlag = find_option("wet-run",0,0)!=0; |
| 2890 | zComment = find_option("comment","m",1); |
| 2891 | zAsFilename = find_option("as",0,1); |
| 2892 | verify_all_options(); |
| 2893 | user_select(); |
| 2894 | |
| 2895 | zFilename = g.argv[2]; |
| 2896 | CheckinWebInfo_init(&cinf); |
| 2897 | blob_append(&cinf.comment, |
| 2898 | zComment ? zComment : "This is a test comment.", |
| 2899 | -1); |
| 2900 | cinf.zFilename = mprintf("%s", zAsFilename ? zAsFilename : zFilename); |
| 2901 | cinf.zUser = login_name(); |
| 2902 | |
| 2903 | cinf.pParent = manifest_get_by_name("trunk", &parentVid); |
| 2904 | assert(parentVid>0); |
| 2905 | assert(cinf.pParent!=0); |
| 2906 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 2907 | ExtFILE/*may want to reconsider*/); |
| 2908 | sha3sum_init(256); |
| 2909 | sha3sum_step_blob(&cinf.fileContent); |
| 2910 | sha3sum_finish(&cinf.fileHash); |
| 2911 | checkin_web(&cinf, &newRid, wetRunFlag ? 0 : 1); |
| 2912 | CheckinWebInfo_cleanup(&cinf); |
| 2913 | if(wetRunFlag!=0 && newRid!=0 && g.localOpen!=0){ |
| 2914 | fossil_warning("The checkout state is now out of sync " |
| 2915 | "with regards to this commit. It needs to be " |
| 2916 | "'update'd or 'close'd and re-'open'ed."); |
| 2917 | /* vfile_check_signature(newRid,0); does not do the trick */ |
| 2918 | } |
| 2919 | } |
| 2920 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2678,11 +2678,11 @@ | |
| 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 | const char *zUser; /* User name */ |
| 2688 | char *zFilename; /* Name of single file to commit. */ |
| @@ -2689,53 +2689,65 @@ | |
| 2689 | Blob fileContent; /* Content of the modified file. */ |
| 2690 | Blob fileHash; /* Hash of this->fileContent */ |
| 2691 | }; |
| 2692 | #endif /* INTERFACE */ |
| 2693 | |
| 2694 | static void CheckinOneFileInfo_init( CheckinOneFileInfo * p ){ |
| 2695 | memset(p, 0, sizeof(struct CheckinOneFileInfo)); |
| 2696 | p->comment = p->fileContent = p->fileHash = empty_blob; |
| 2697 | } |
| 2698 | |
| 2699 | static void CheckinOneFileInfo_cleanup( CheckinOneFileInfo * p ){ |
| 2700 | blob_reset(&p->comment); |
| 2701 | blob_reset(&p->fileContent); |
| 2702 | blob_reset(&p->fileHash); |
| 2703 | if(p->pParent){ |
| 2704 | manifest_destroy(p->pParent); |
| 2705 | } |
| 2706 | fossil_free(p->zFilename); |
| 2707 | fossil_free(p->zMimetype); |
| 2708 | CheckinOneFileInfo_init(p); |
| 2709 | } |
| 2710 | |
| 2711 | /* |
| 2712 | ** Creates a manifest file, written to pOut, from the state in the |
| 2713 | ** fully-populated pCI argument. Returns true on success. On error, |
| 2714 | ** returns 0 and, if pErr is not NULL, writes an error message there. |
| 2715 | */ |
| 2716 | static int create_manifest_one_file( Blob * pOut, CheckinOneFileInfo * pCI, |
| 2717 | Blob * pErr){ |
| 2718 | Blob zCard = empty_blob; /* Z-card checksum */ |
| 2719 | ManifestFile *zFile; /* One file entry from the pCI->pParent */ |
| 2720 | int cmp = -99; /* filename comparison result */ |
| 2721 | int fperm = 0; /* file permissions */ |
| 2722 | const char *zPerm = 0; /* permissions for new F-card */ |
| 2723 | const char *zFilename = 0; /* filename to use for F-card */ |
| 2724 | const char *zUuid = 0; /* UUID for F-card */ |
| 2725 | const char *zErrMsg = 0; /* For error reporting. */ |
| 2726 | int (*fncmp)(char const *, char const *) = /* filename comparator */ |
| 2727 | filenames_are_case_sensitive() |
| 2728 | ? fossil_strcmp |
| 2729 | : fossil_stricmp; |
| 2730 | |
| 2731 | assert(blob_str(&pCI->fileHash)); |
| 2732 | assert(pCI->pParent); |
| 2733 | assert(pCI->zFilename); |
| 2734 | assert(pCI->zUser); |
| 2735 | |
| 2736 | #define mf_err(MSG) zErrMsg = MSG; goto manifest_error |
| 2737 | /* Potential TODOs include... |
| 2738 | ** - Create a delta manifest, if possible, rather than a baseline. |
| 2739 | ** - Enable adding of new files? It's implemented by disabled until |
| 2740 | ** we at least ensure that pCI->zFilename is a path-relative |
| 2741 | ** filename. |
| 2742 | ** - Maybe add support for tags. Those can be edited via /info page. |
| 2743 | ** - Check for a commit to a closed leaf and re-open it. |
| 2744 | ** - Symlinks: if we're really in a checkout, handle commit/add of |
| 2745 | ** symlinks like a normal commit would. For now we bail out if |
| 2746 | ** told to handle a symlink because there would seem to be no(?) |
| 2747 | ** sensible way to handle a symlink add/checkin without a |
| 2748 | ** checkout. |
| 2749 | */ |
| 2750 | blob_zero(pOut); |
| 2751 | if(blob_size(&pCI->comment)!=0){ |
| 2752 | blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); |
| 2753 | }else{ |
| @@ -2745,54 +2757,58 @@ | |
| 2757 | |
| 2758 | manifest_file_rewind(pCI->pParent); |
| 2759 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2760 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2761 | if(cmp<0){ |
| 2762 | blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid, |
| 2763 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2764 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2765 | }else{ |
| 2766 | break; |
| 2767 | } |
| 2768 | } |
| 2769 | if(cmp==0){ /* Match */ |
| 2770 | fperm = manifest_file_mperm(zFile); |
| 2771 | zFilename = zFile->zName |
| 2772 | /* use original name in case of name-case difference */; |
| 2773 | zUuid = blob_str(&pCI->fileHash); |
| 2774 | }else{ |
| 2775 | /* This is a new file. */ |
| 2776 | fperm = file_perm(pCI->zFilename, ExtFILE); |
| 2777 | zFilename = pCI->zFilename; |
| 2778 | zUuid = blob_str(&pCI->fileHash); |
| 2779 | if(cmp>0){ |
| 2780 | assert(zFile!=0); |
| 2781 | assert(pCI->pParent->iFile>0); |
| 2782 | --pCI->pParent->iFile |
| 2783 | /* so that the next step picks up that entry again */; |
| 2784 | } |
| 2785 | } |
| 2786 | if(PERM_LNK==fperm){ |
| 2787 | mf_err("Cannot commit symlinks with this approach."); |
| 2788 | }else if(PERM_EXE==fperm){ |
| 2789 | zPerm = " x"; |
| 2790 | }else{ |
| 2791 | zPerm = ""; |
| 2792 | } |
| 2793 | assert(zFilename); |
| 2794 | assert(zUuid); |
| 2795 | assert(zPerm); |
| 2796 | blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm); |
| 2797 | while((zFile = manifest_file_next(pCI->pParent, 0))){ |
| 2798 | cmp = fncmp(zFile->zName, pCI->zFilename); |
| 2799 | assert(cmp>0); |
| 2800 | if(cmp<=0){ |
| 2801 | mf_err("Internal error: mis-ordering of F-cards detected."); |
| 2802 | } |
| 2803 | blob_appendf(pOut, "F %F %s%s%s\n", |
| 2804 | zFile->zName, zFile->zUuid, |
| 2805 | (zFile->zPerm && *zFile->zPerm) ? " " : "", |
| 2806 | (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : ""); |
| 2807 | } |
| 2808 | |
| 2809 | if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){ |
| 2810 | blob_appendf(pOut, "N %F\n", pCI->zMimetype); |
| 2811 | } |
| 2812 | |
| 2813 | blob_appendf(pOut, "P %z\n", |
| 2814 | db_text(0,"SELECT uuid FROM blob WHERE rid=%d", |
| @@ -2800,120 +2816,186 @@ | |
| 2816 | blob_appendf(pOut, "U %F\n", pCI->zUser); |
| 2817 | md5sum_blob(pOut, &zCard); |
| 2818 | blob_appendf(pOut, "Z %b\n", &zCard); |
| 2819 | blob_reset(&zCard); |
| 2820 | return 0; |
| 2821 | manifest_error: |
| 2822 | assert( zErrMsg ); |
| 2823 | if(pErr) blob_append(pErr,zErrMsg,-1); |
| 2824 | return 1; |
| 2825 | #undef mf_err |
| 2826 | } |
| 2827 | |
| 2828 | /* |
| 2829 | ** EXPERIMENTAL! Subject to change or removal at any time. |
| 2830 | ** |
| 2831 | ** A so-called "single-file checkin" is a slimmed-down form of the |
| 2832 | ** checkin command which accepts only a single file and is intended to |
| 2833 | ** accept edits to a file via the web interface or from the CLI from |
| 2834 | ** outside of a checkout. |
| 2835 | ** |
| 2836 | ** This routine uses the state from the given fully-populated pCI |
| 2837 | ** argument to add pCI->fileContent to the database, and create and |
| 2838 | ** save a manifest for that change. Ownership of pCI and its contents |
| 2839 | ** are unchanged. |
| 2840 | ** |
| 2841 | ** Fails fatally on error. If pRid is not NULL, the RID of the |
| 2842 | ** resulting manifest is written to *pRid. If bDryRun is true, |
| 2843 | ** it rolls back its transaction, else it commits as usual. |
| 2844 | */ |
| 2845 | void checkin_one_file( CheckinOneFileInfo * pCI, int *pRid, int bDryRun ){ |
| 2846 | Blob mf = empty_blob; |
| 2847 | Blob err = empty_blob; |
| 2848 | int rid = 0, frid = 0; |
| 2849 | const int isPrivate = content_is_private(pCI->pParent->rid); |
| 2850 | ManifestFile * zFile; |
| 2851 | const char * zFilePrevUuid = 0; /* UUID of previous version of |
| 2852 | the file */ |
| 2853 | |
| 2854 | db_begin_transaction(); |
| 2855 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ |
| 2856 | fossil_fatal("no such user: %s", pCI->zUser); |
| 2857 | } |
| 2858 | /* |
| 2859 | ** Confirm that pCI->zFilename can be found in pCI->pParent. |
| 2860 | ** If not, fail. This is admittedly an artificial limitation, |
| 2861 | ** not strictly necessary. |
| 2862 | */ |
| 2863 | manifest_file_rewind(pCI->pParent); |
| 2864 | zFile = manifest_file_seek(pCI->pParent, pCI->zFilename, 0); |
| 2865 | if(!zFile){ |
| 2866 | fossil_fatal("File [%s] not found in manifest. " |
| 2867 | "Adding new files is currently not allowed.", |
| 2868 | pCI->zFilename); |
| 2869 | }else if(zFile->zPerm && strstr(zFile->zPerm, "l")){ |
| 2870 | fossil_fatal("Cannot save a symlink this way."); |
| 2871 | }else{ |
| 2872 | if(0==fossil_strcmp(zFile->zUuid, blob_str(&pCI->fileHash))){ |
| 2873 | fossil_fatal("File is unchanged. Not saving."); |
| 2874 | } |
| 2875 | zFilePrevUuid = zFile->zUuid; |
| 2876 | } |
| 2877 | /* Create the manifest... */ |
| 2878 | if(create_manifest_one_file(&mf, pCI, &err)==0){ |
| 2879 | fossil_fatal("create_manifest_one_file() failed: %B", &err); |
| 2880 | } |
| 2881 | /* Add the file content to the db... */ |
| 2882 | frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), |
| 2883 | 0, 0, isPrivate); |
| 2884 | if(zFilePrevUuid!=0){ |
| 2885 | /* Deltify against previous file version... */ |
| 2886 | int prevFRid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", |
| 2887 | zFilePrevUuid); |
| 2888 | assert(prevFRid>0); |
| 2889 | content_deltify(frid, &prevFRid, 1, 0); |
| 2890 | } |
| 2891 | /* Save and crosslink the manifest... */ |
| 2892 | rid = content_put_ex(&mf, 0, 0, 0, isPrivate); |
| 2893 | if(pRid!=0){ |
| 2894 | *pRid = rid; |
| 2895 | } |
| 2896 | #if 1 |
| 2897 | /* Only for development/debugging... */ |
| 2898 | fossil_print("Manifest:\n%b", &mf); |
| 2899 | #endif |
| 2900 | manifest_crosslink(rid, &mf, 0); |
| 2901 | if(bDryRun){ |
| 2902 | fossil_print("Rolling back transaction.\n"); |
| 2903 | } |
| 2904 | db_end_transaction(bDryRun ? 1 : 0); |
| 2905 | blob_reset(&mf); |
| 2906 | } |
| 2907 | |
| 2908 | /* |
| 2909 | ** COMMAND: test-ci-one |
| 2910 | ** |
| 2911 | ** This is an on-going experiment, subject to change or removal at |
| 2912 | ** any time. |
| 2913 | ** |
| 2914 | ** Usage: %fossil ?OPTIONS? FILENAME |
| 2915 | ** |
| 2916 | ** where FILENAME is a repo-relative name as it would appear in the |
| 2917 | ** vfile table. |
| 2918 | ** |
| 2919 | ** Options: |
| 2920 | ** |
| 2921 | ** --as FILENAME The repository-side name of the input file, |
| 2922 | ** relative to the top of the repostory. |
| 2923 | ** Default is the same as the input file name. |
| 2924 | ** --comment|-m COMMENT Optional checkin comment. |
| 2925 | ** --revision|-r VERSION Commit from this version. Default is |
| 2926 | ** the checkout version (if available) or |
| 2927 | ** trunk (if used without a checkout). |
| 2928 | ** --wet-run Disables the default dry-run mode. |
| 2929 | ** |
| 2930 | ** Example: |
| 2931 | ** |
| 2932 | ** %fossil -m ... -r foo --as src/myfile.c myfile.c |
| 2933 | ** |
| 2934 | */ |
| 2935 | void test_ci_one_cmd(){ |
| 2936 | CheckinOneFileInfo cinf; /* checkin state */ |
| 2937 | int parentVid = 0, newRid = 0; /* RID of parent version and new |
| 2938 | version */ |
| 2939 | const char * zFilename; /* argv[2] */ |
| 2940 | const char * zComment; /* -m comment */ |
| 2941 | const char * zAsFilename; /* --as filename */ |
| 2942 | const char * zRevision; /* --revision|-r [=trunk|checkout] */ |
| 2943 | int wetRunFlag = 0; |
| 2944 | |
| 2945 | if(g.argc<3){ |
| 2946 | usage("INFILE"); |
| 2947 | } |
| 2948 | wetRunFlag = find_option("wet-run",0,0)!=0; |
| 2949 | zComment = find_option("comment","m",1); |
| 2950 | zAsFilename = find_option("as",0,1); |
| 2951 | zRevision = find_option("revision","r",1); |
| 2952 | |
| 2953 | db_find_and_open_repository(0, 0); |
| 2954 | verify_all_options(); |
| 2955 | user_select(); |
| 2956 | |
| 2957 | if(1){ |
| 2958 | char * zProjCode = db_get("project-code",0); |
| 2959 | assert(zProjCode); |
| 2960 | if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", |
| 2961 | zProjCode)){ |
| 2962 | fossil_fatal("Never, ever run this in/on the core fossil repo " |
| 2963 | "until it's been well-vetted. Use a temp/test " |
| 2964 | "repo."); |
| 2965 | } |
| 2966 | } |
| 2967 | |
| 2968 | zFilename = g.argv[2]; |
| 2969 | CheckinOneFileInfo_init(&cinf); |
| 2970 | blob_append(&cinf.comment, |
| 2971 | zComment ? zComment : "This is a test comment.", |
| 2972 | -1); |
| 2973 | cinf.zFilename = mprintf("%s", zAsFilename ? zAsFilename : zFilename); |
| 2974 | cinf.zUser = login_name(); |
| 2975 | |
| 2976 | if(zRevision==0 || zRevision[0]==0){ |
| 2977 | if(g.localOpen/*checkout*/){ |
| 2978 | zRevision = db_lget("checkout-hash", 0) /*leak*/; |
| 2979 | }else{ |
| 2980 | zRevision = "trunk"; |
| 2981 | } |
| 2982 | if(zRevision==0 || zRevision[0]==0){ |
| 2983 | fossil_fatal("Cannot determine version to commit to."); |
| 2984 | } |
| 2985 | } |
| 2986 | cinf.pParent = manifest_get_by_name(zRevision, &parentVid); |
| 2987 | assert(parentVid>0); |
| 2988 | assert(cinf.pParent!=0); |
| 2989 | blob_read_from_file(&cinf.fileContent, zFilename, |
| 2990 | ExtFILE/*may want to reconsider*/); |
| 2991 | sha3sum_init(256); |
| 2992 | sha3sum_step_blob(&cinf.fileContent); |
| 2993 | sha3sum_finish(&cinf.fileHash); |
| 2994 | checkin_one_file(&cinf, &newRid, wetRunFlag ? 0 : 1); |
| 2995 | CheckinOneFileInfo_cleanup(&cinf); |
| 2996 | if(wetRunFlag!=0 && newRid!=0 && g.localOpen!=0){ |
| 2997 | fossil_warning("The checkout state is now out of sync " |
| 2998 | "with regards to this commit. It needs to be " |
| 2999 | "'update'd or 'close'd and re-'open'ed."); |
| 3000 | } |
| 3001 | } |
| 3002 |