Fossil SCM

Initial infrastructure for "web commit".

stephan 2020-04-27 19:36 trunk
Commit cb4d48ac058b9012cc339dd67f67723b7f5909cb3a5646918b6d9d20266a9bb6
1 file changed +243
+243
--- src/checkin.c
+++ src/checkin.c
@@ -2672,5 +2672,248 @@
26722672
}
26732673
if( count_nonbranch_children(vid)>1 ){
26742674
fossil_print("**** warning: a fork has occurred *****\n");
26752675
}
26762676
}
2677
+
2678
+#if INTERFACE
2679
+/*
2680
+** State for the "web-checkin" infrastructure, which enables the
2681
+** ability to commit changes to a single file via an HTTP request.
2682
+ */
2683
+struct 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
+ 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{
2742
+ blob_append(pOut, "C (no\\scomment)\n", 16);
2743
+ }
2744
+ blob_appendf(pOut, "D %z\n", date_in_standard_format("now"));
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",
2799
+ pCI->pParent->rid));
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
+}
26772920
--- src/checkin.c
+++ src/checkin.c
@@ -2672,5 +2672,248 @@
2672 }
2673 if( count_nonbranch_children(vid)>1 ){
2674 fossil_print("**** warning: a fork has occurred *****\n");
2675 }
2676 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2677
--- src/checkin.c
+++ src/checkin.c
@@ -2672,5 +2672,248 @@
2672 }
2673 if( count_nonbranch_children(vid)>1 ){
2674 fossil_print("**** warning: a fork has occurred *****\n");
2675 }
2676 }
2677
2678 #if INTERFACE
2679 /*
2680 ** State for the "web-checkin" infrastructure, which enables the
2681 ** ability to commit changes to a single file via an HTTP request.
2682 */
2683 struct 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 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{
2742 blob_append(pOut, "C (no\\scomment)\n", 16);
2743 }
2744 blob_appendf(pOut, "D %z\n", date_in_standard_format("now"));
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",
2799 pCI->pParent->rid));
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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button