Fossil SCM

Mini-checkin can now create a delta manifest if its parent is a baseline and a flag indicating a preference for delta manifests is set.

stephan 2020-04-30 11:53 UTC checkin-without-checkout
Commit 9699997444fd6d64cf6ad0c0132083acfe96c9d1d8442b2da05b96181fb52a9b
1 file changed +188 -110
+188 -110
--- src/checkin.c
+++ src/checkin.c
@@ -2685,38 +2685,42 @@
26852685
fossil_print("**** warning: a fork has occurred *****\n");
26862686
}
26872687
}
26882688
26892689
/*
2690
-** State for the "web-checkin" infrastructure, which enables the
2690
+** State for the "mini-checkin" infrastructure, which enables the
26912691
** ability to commit changes to a single file via an HTTP request.
26922692
**
26932693
** Memory for all non-const (char *) members is owned by the
26942694
** CheckinMiniInfo instance.
26952695
*/
26962696
struct CheckinMiniInfo {
2697
+ Manifest * pParent; /* parent checkin */
2698
+ char *zParentUuid; /* UUID of pParent */
26972699
Blob comment; /* Check-in comment text */
26982700
char *zMimetype; /* Mimetype of check-in command. May be NULL */
2699
- Manifest * pParent; /* parent checkin */
2700
- char *zParentUuid; /* UUID of pParent */
27012701
char *zUser; /* User name */
27022702
char *zDate; /* Optionally force this date string
27032703
(anything supported by
2704
- date_in_standard_format().
2704
+ date_in_standard_format()).
27052705
Maybe be NULL. */
27062706
char *zFilename; /* Name of single file to commit. Must be
27072707
relative to the top of the repo. */
27082708
Blob fileContent; /* Content of file referred to by zFilename. */
27092709
Blob fileHash; /* Hash of this->fileContent, using the
27102710
repo's preferred hash method. */
2711
+ int flags; /* Bitmask of fossil_cimini_flags for
2712
+ communication from checkin_mini() to
2713
+ create_manifest_mini(). */
27112714
};
27122715
typedef struct CheckinMiniInfo CheckinMiniInfo;
27132716
/*
27142717
** Initializes p to a known-valid default state.
27152718
*/
27162719
static void CheckinMiniInfo_init( CheckinMiniInfo * p ){
27172720
memset(p, 0, sizeof(CheckinMiniInfo));
2721
+ p->flags = 0;
27182722
p->comment = p->fileContent = p->fileHash = empty_blob;
27192723
}
27202724
27212725
/*
27222726
** Frees all memory owned by p, but does not free p.
@@ -2738,10 +2742,11 @@
27382742
27392743
/*
27402744
** Flags for checkin_mini()
27412745
*/
27422746
enum fossil_cimini_flags {
2747
+CIMINI_NONE = 0,
27432748
/*
27442749
** Tells checkin_mini() to use dry-run mode.
27452750
*/
27462751
CIMINI_DRY_RUN = 1,
27472752
/*
@@ -2774,12 +2779,11 @@
27742779
** affected, not the original file (if any).
27752780
*/
27762781
CIMINI_CONVERT_EOL = 1<<5,
27772782
27782783
/*
2779
-** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer
2780
-** creation of a delta manifest.
2784
+** A hint to checkin_mini() to prefer creation of a delta manifest.
27812785
*/
27822786
CIMINI_PREFER_DELTA = 1<<6,
27832787
27842788
/*
27852789
** NOT YET IMPLEMENTED.
@@ -2786,31 +2790,137 @@
27862790
*/
27872791
CIMINI_ALLOW_CLOSED_LEAF = 1<<7
27882792
};
27892793
27902794
/*
2791
-** Creates a manifest file, written to pOut, from the state in the
2792
-** fully-populated pCI argument. pCI is not *semantically* modified
2793
-** but cannot be const because blob_str() may need to NUL-terminate
2794
-** any given blob.
2795
+** Handles the F-card parts for create_manifest_mini().
2796
+**
2797
+** If asDelta is true, pCI->pParent MUST be a baseline, else an
2798
+** assert() is triggered. Still TODO is creating a delta from
2799
+** pCI->pParent when that object is itself a delta.
27952800
**
2796
-** Returns true on success. On error, returns 0 and, if pErr is not
2797
-** NULL, writes an error message there.
2801
+** Returns 1 on success, 0 on error, and writes any error message to
2802
+** pErr (if it's not NULL).
27982803
*/
2799
-static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
2800
- Blob * pErr){
2801
- Blob zCard = empty_blob; /* Z-card checksum */
2804
+static int create_manifest_mini_fcards( Blob * pOut,
2805
+ CheckinMiniInfo * pCI,
2806
+ int asDelta,
2807
+ Blob * pErr){
28022808
ManifestFile *zFile; /* One file entry from the pCI->pParent*/
2803
- int cmp = -99; /* filename comparison result */
28042809
int fperm = 0; /* file permissions */
28052810
const char *zPerm = 0; /* permissions for new F-card */
2806
- const char *zFilename = 0; /* filename to use for F-card */
2807
- const char *zUuid = 0; /* UUID for F-card */
2811
+ const char *zFilename = 0; /* filename for new F-card */
2812
+ const char *zUuid = 0; /* UUID for new F-card */
2813
+ int cmp = 0; /* filename comparison result */
28082814
int (*fncmp)(char const *, char const *) = /* filename comparator */
28092815
filenames_are_case_sensitive()
28102816
? fossil_strcmp
28112817
: fossil_stricmp;
2818
+#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0
2819
+
2820
+ /* Potential TODOs:
2821
+ **
2822
+ ** - When updating a file and the new one has the +x bit, add that
2823
+ ** bit if needed. We also need that logic in the upstream "has
2824
+ ** this file changed?" check. We currently always inherit the old
2825
+ ** perms.
2826
+ */
2827
+
2828
+ manifest_file_rewind(pCI->pParent);
2829
+ if(asDelta){
2830
+ /* Parent is a baseline and we have only 1 file to modify, so this
2831
+ ** is the simplest case...
2832
+ */
2833
+ assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI.");
2834
+ zFile = manifest_file_seek(pCI->pParent, pCI->zFilename,0);
2835
+ if(zFile==0){
2836
+ /* New file */
2837
+ fperm = file_perm(pCI->zFilename, ExtFILE);
2838
+ zFilename = pCI->zFilename;
2839
+ }else{
2840
+ /* Replacement file */
2841
+ fperm = manifest_file_mperm(zFile);
2842
+ zFilename = zFile->zName
2843
+ /* use original name in case of name-case difference */;
2844
+ zFile = 0;
2845
+ }
2846
+ }else{
2847
+ /* Non-delta: write F-cards which lexically preceed pCI->zFilename */
2848
+ while((zFile = manifest_file_next(pCI->pParent, 0))){
2849
+ cmp = fncmp(zFile->zName, pCI->zFilename);
2850
+ if(cmp<0){
2851
+ blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid,
2852
+ (zFile->zPerm && *zFile->zPerm) ? " " : "",
2853
+ (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2854
+ }else{
2855
+ break;
2856
+ }
2857
+ }
2858
+ /* Figure out file perms and name to save... */
2859
+ if(cmp==0){
2860
+ /* Match: override this F-card */
2861
+ fperm = manifest_file_mperm(zFile);
2862
+ zFilename = zFile->zName
2863
+ /* use original name in case of name-case difference */;
2864
+ zFile = 0;
2865
+ }else{
2866
+ /* This is a new file. */
2867
+ fperm = file_perm(pCI->zFilename, ExtFILE);
2868
+ zFilename = pCI->zFilename;
2869
+ }
2870
+ }
2871
+ assert(fperm!=PERM_LNK && "This should have been validated before.");
2872
+ if(PERM_LNK==fperm){
2873
+ mf_err((pErr,"Cannot commit symlinks via mini-checkin."));
2874
+ }else if(PERM_EXE==fperm){
2875
+ zPerm = " x";
2876
+ }else{
2877
+ zPerm = "";
2878
+ }
2879
+ zUuid = blob_str(&pCI->fileHash);
2880
+ assert(zFile ? cmp>0&&asDelta==0 : asDelta!=0||cmp==0);
2881
+ assert(zFilename);
2882
+ assert(zUuid);
2883
+ assert(zPerm);
2884
+ blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm);
2885
+ /* Non-delta: write F-cards which lexically follow pCI->zFilename */
2886
+ while(zFile!=0){
2887
+#ifndef NDEBUG
2888
+ cmp = fncmp(zFile->zName, pCI->zFilename);
2889
+ assert(cmp>0);
2890
+ if(cmp<=0){
2891
+ mf_err((pErr,"Internal error: mis-ordering of "
2892
+ "F-cards detected."));
2893
+ }
2894
+#endif
2895
+ blob_appendf(pOut, "F %F %s%s%s\n",
2896
+ zFile->zName, zFile->zUuid,
2897
+ (zFile->zPerm && *zFile->zPerm) ? " " : "",
2898
+ (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2899
+ zFile = manifest_file_next(pCI->pParent, 0);
2900
+ }
2901
+#undef mf_err
2902
+ return 1;
2903
+}
2904
+
2905
+
2906
+/*
2907
+** Creates a manifest file, written to pOut, from the state in the
2908
+** fully-populated and semantically valid pCI argument. pCI is not
2909
+** *semantically* modified but cannot be const because blob_str() may
2910
+** need to NUL-terminate any given blob.
2911
+**
2912
+** Returns true on success. On error, returns 0 and, if pErr is not
2913
+** NULL, writes an error message there.
2914
+**
2915
+** Intended only to be called via checkin_mini() or routines which
2916
+** have already completely vetted pCI.
2917
+*/
2918
+static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
2919
+ Blob * pErr){
2920
+ Blob zCard = empty_blob; /* Z-card checksum */
2921
+ int asDelta = 0;
28122922
28132923
assert(blob_str(&pCI->fileHash));
28142924
assert(pCI->pParent);
28152925
assert(pCI->zFilename);
28162926
assert(pCI->zUser);
@@ -2825,65 +2935,26 @@
28252935
** told to handle a symlink because there would seem to be no(?)
28262936
** sensible way to handle a symlink add/checkin without a
28272937
** checkout.
28282938
*/
28292939
blob_zero(pOut);
2940
+ if((pCI->flags & CIMINI_PREFER_DELTA)
2941
+ && pCI->pParent->zBaseline==0){
2942
+ asDelta = 1;
2943
+ }
2944
+ if(asDelta){
2945
+ blob_appendf(pOut, "B %s\n", pCI->zParentUuid);
2946
+ }
28302947
if(blob_size(&pCI->comment)!=0){
28312948
blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment));
28322949
}else{
28332950
blob_append(pOut, "C (no\\scomment)\n", 16);
28342951
}
28352952
blob_appendf(pOut, "D %z\n", pCI->zDate);
2836
- manifest_file_rewind(pCI->pParent);
2837
- while((zFile = manifest_file_next(pCI->pParent, 0))){
2838
- cmp = fncmp(zFile->zName, pCI->zFilename);
2839
- if(cmp<0){
2840
- blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid,
2841
- (zFile->zPerm && *zFile->zPerm) ? " " : "",
2842
- (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2843
- }else{
2844
- break;
2845
- }
2846
- }
2847
- if(cmp==0){ /* Match */
2848
- fperm = manifest_file_mperm(zFile);
2849
- zFilename = zFile->zName
2850
- /* use original name in case of name-case difference */;
2851
- zUuid = blob_str(&pCI->fileHash);
2852
- }else{
2853
- /* This is a new file. */
2854
- fperm = file_perm(pCI->zFilename, ExtFILE);
2855
- zFilename = pCI->zFilename;
2856
- zUuid = blob_str(&pCI->fileHash);
2857
- if(cmp>0){
2858
- assert(zFile!=0);
2859
- assert(pCI->pParent->iFile>0);
2860
- --pCI->pParent->iFile
2861
- /* so that the next step picks up that entry again */;
2862
- }
2863
- }
2864
- if(PERM_LNK==fperm){
2865
- mf_err((pErr,"Cannot commit symlinks with this approach."));
2866
- }else if(PERM_EXE==fperm){
2867
- zPerm = " x";
2868
- }else{
2869
- zPerm = "";
2870
- }
2871
- assert(zFilename);
2872
- assert(zUuid);
2873
- assert(zPerm);
2874
- blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm);
2875
- while((zFile = manifest_file_next(pCI->pParent, 0))){
2876
- cmp = fncmp(zFile->zName, pCI->zFilename);
2877
- assert(cmp>0);
2878
- if(cmp<=0){
2879
- mf_err((pErr,"Internal error: mis-ordering of F-cards detected."));
2880
- }
2881
- blob_appendf(pOut, "F %F %s%s%s\n",
2882
- zFile->zName, zFile->zUuid,
2883
- (zFile->zPerm && *zFile->zPerm) ? " " : "",
2884
- (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2953
+
2954
+ if(!create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)){
2955
+ return 0;
28852956
}
28862957
28872958
if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){
28882959
blob_appendf(pOut, "N %F\n", pCI->zMimetype);
28892960
}
@@ -2936,16 +3007,15 @@
29363007
** diagnostic message there.
29373008
**
29383009
** Returns true on success. If pRid is not NULL, the RID of the
29393010
** resulting manifest is written to *pRid.
29403011
**
2941
-** ciminiFlags is a bitmask of optional flags from fossil_cimini_flags
2942
-** enum. See that enum for the docs for each flag. Pass 0 for no
2943
-** flags.
3012
+** The checkin process is largely influenced by pCI->flags, and that
3013
+** must be populated before calling this. See the fossil_cimini_flags
3014
+** enum for the docs for each flag.
29443015
*/
2945
-static int checkin_mini( CheckinMiniInfo * pCI, int *pRid,
2946
- int ciminiFlags, Blob * pErr ){
3016
+static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){
29473017
Blob mf = empty_blob; /* output manifest */
29483018
int rid = 0, frid = 0; /* various RIDs */
29493019
const int isPrivate = content_is_private(pCI->pParent->rid);
29503020
ManifestFile * zFilePrev; /* file entry from pCI->pParent */
29513021
int prevFRid = 0; /* RID of file's prev. version */
@@ -2964,20 +3034,24 @@
29643034
** no reason we can't do that via the generated manifest,
29653035
** but the commit command does not offer that option, so
29663036
** we won't, either.
29673037
*/
29683038
}
2969
- if(!(CIMINI_ALLOW_FORK & ciminiFlags)
3039
+ if(!(CIMINI_ALLOW_FORK & pCI->flags)
29703040
&& !is_a_leaf(pCI->pParent->rid)){
29713041
ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.",
29723042
pCI->zParentUuid));
29733043
}
2974
- if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags)
3044
+ if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags)
29753045
&& contains_merge_marker(&pCI->fileContent)){
29763046
ci_err((pErr,"Content appears to contain a merge conflict marker."));
29773047
}
2978
-
3048
+ if(!file_is_simple_pathname(pCI->zFilename, 1)){
3049
+ ci_err((pErr,"Invalid filename for use in a repository: %s",
3050
+ pCI->zFilename));
3051
+ }
3052
+
29793053
{
29803054
/*
29813055
** Normalize the timestamp. We don't use date_in_standard_format()
29823056
** because that has side-effects we don't want to trigger here.
29833057
*/
@@ -2989,11 +3063,11 @@
29893063
ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate));
29903064
}
29913065
fossil_free(pCI->zDate);
29923066
pCI->zDate = zDVal;
29933067
}
2994
- if(!(CIMINI_ALLOW_OLDER & ciminiFlags)
3068
+ if(!(CIMINI_ALLOW_OLDER & pCI->flags)
29953069
&& !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){
29963070
ci_err((pErr,"Checkin time (%s) may not be older "
29973071
"than its parent (%z).",
29983072
pCI->zDate,
29993073
db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)",
@@ -3024,37 +3098,38 @@
30243098
*/
30253099
manifest_file_rewind(pCI->pParent);
30263100
zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0);
30273101
if(!zFilePrev){
30283102
ci_err((pErr,"File [%s] not found in manifest [%S]. "
3029
- "Adding new files is currently not allowed.",
3103
+ "Adding new files is currently not permitted.",
30303104
pCI->zFilename, pCI->zParentUuid));
3031
- }else if(zFilePrev->zPerm && strstr(zFilePrev->zPerm, "l")){
3032
- ci_err((pErr,"Cannot save a symlink this way."));
3105
+ }else if(zFilePrev->zPerm
3106
+ && manifest_file_mperm(zFilePrev)==PERM_LNK){
3107
+ ci_err((pErr,"Cannot save a symlink via a mini-checkin."));
30333108
}
30343109
if(zFilePrev){
30353110
prevFRid = fast_uuid_to_rid(zFilePrev->zUuid);
30363111
}
30373112
3038
- /* Confirm that the new content has the same EOL style as its
3039
- ** predecessor and convert it, if needed, to the same style. Note
3040
- ** that this inherently runs a risk of breaking content, e.g. string
3041
- ** literals which contain embedded newlines. Note that HTML5
3042
- ** specifies that form-submitted TEXTAREA content gets normalized to
3043
- ** CRLF-style:
3044
- **
3045
- ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
3046
- **
3047
- ** More performant/efficient would be to offer a flag which says
3048
- ** which newline form to use, converting the new copy (if needed)
3049
- ** without having to examine the original. Since the primary use
3050
- ** case is a web interface, it would be easy to offer it as a
3051
- ** checkbox there.
3052
- */
3053
- if((CIMINI_CONVERT_EOL & ciminiFlags)
3113
+ if((CIMINI_CONVERT_EOL & pCI->flags)
30543114
&& zFilePrev!=0
30553115
&& blob_size(&pCI->fileContent)>0){
3116
+ /* Confirm that the new content has the same EOL style as its
3117
+ ** predecessor and convert it, if needed, to the same style. Note
3118
+ ** that this inherently runs a risk of breaking content,
3119
+ ** e.g. string literals which contain embedded newlines. Note that
3120
+ ** HTML5 specifies that form-submitted TEXTAREA content gets
3121
+ ** normalized to CRLF-style:
3122
+ **
3123
+ ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
3124
+ **
3125
+ ** More performant/efficient would be to offer a flag which says
3126
+ ** which newline form to use, converting the new copy (if needed)
3127
+ ** without having to examine the original. Since the primary use
3128
+ ** case is a web interface, it would be easy to offer it as a
3129
+ ** checkbox there.
3130
+ */
30563131
const int pseudoBinary = LOOK_LONG | LOOK_NUL;
30573132
const int lookFlags = LOOK_CRLF | pseudoBinary;
30583133
const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags );
30593134
if(!(pseudoBinary & lookNew)){
30603135
Blob contentPrev = empty_blob;
@@ -3101,11 +3176,11 @@
31013176
if(create_manifest_mini(&mf, pCI, pErr)==0){
31023177
return 0;
31033178
}
31043179
rid = content_put_ex(&mf, 0, 0, 0, isPrivate);
31053180
content_deltify(rid, &pCI->pParent->rid, 1, 0);
3106
- if(ciminiFlags & CIMINI_DUMP_MANIFEST){
3181
+ if(pCI->flags & CIMINI_DUMP_MANIFEST){
31073182
fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf);
31083183
}
31093184
manifest_crosslink(rid, &mf, 0);
31103185
blob_reset(&mf);
31113186
/* Save and deltify the file content... */
@@ -3113,11 +3188,11 @@
31133188
0, 0, isPrivate);
31143189
if(zFilePrev!=0){
31153190
assert(prevFRid>0);
31163191
content_deltify(frid, &prevFRid, 1, 0);
31173192
}
3118
- db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0);
3193
+ db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0);
31193194
if(pRid!=0){
31203195
*pRid = rid;
31213196
}
31223197
return 1;
31233198
ci_error:
@@ -3160,10 +3235,12 @@
31603235
** ancestor.
31613236
** --convert-eol Convert EOL style of the checkin to match
31623237
** the previous version's content. Does not
31633238
** modify the original file, only the
31643239
** checked-in content.
3240
+** --delta Prefer to generate a delta manifest, if
3241
+** able.
31653242
** --dump-manifest|-d Dumps the generated manifest to stdout.
31663243
** --wet-run Disables the default dry-run mode.
31673244
**
31683245
** Example:
31693246
**
@@ -3178,47 +3255,49 @@
31783255
const char * zCommentFile; /* -M FILE */
31793256
const char * zAsFilename; /* --as filename */
31803257
const char * zRevision; /* --revision|-r [=trunk|checkout] */
31813258
const char * zUser; /* --user-override */
31823259
const char * zDate; /* --date-override */
3183
- int ciminiFlags = 0; /* flags for checkin_mini(). */
31843260
31853261
CheckinMiniInfo_init(&cinf);
31863262
zComment = find_option("comment","m",1);
31873263
zCommentFile = find_option("comment-file","M",1);
31883264
zAsFilename = find_option("as",0,1);
31893265
zRevision = find_option("revision","r",1);
31903266
zUser = find_option("user-override",0,1);
31913267
zDate = find_option("date-override",0,1);
31923268
if(find_option("wet-run",0,0)==0){
3193
- ciminiFlags |= CIMINI_DRY_RUN;
3269
+ cinf.flags |= CIMINI_DRY_RUN;
31943270
}
31953271
if(find_option("allow-fork",0,0)!=0){
3196
- ciminiFlags |= CIMINI_ALLOW_FORK;
3272
+ cinf.flags |= CIMINI_ALLOW_FORK;
31973273
}
31983274
if(find_option("dump-manifest","d",0)!=0){
3199
- ciminiFlags |= CIMINI_DUMP_MANIFEST;
3275
+ cinf.flags |= CIMINI_DUMP_MANIFEST;
32003276
}
32013277
if(find_option("allow-merge-conflict",0,0)!=0){
3202
- ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER;
3278
+ cinf.flags |= CIMINI_ALLOW_MERGE_MARKER;
32033279
}
32043280
if(find_option("allow-older",0,0)!=0){
3205
- ciminiFlags |= CIMINI_ALLOW_OLDER;
3281
+ cinf.flags |= CIMINI_ALLOW_OLDER;
32063282
}
32073283
if(find_option("convert-eol",0,0)!=0){
3208
- ciminiFlags |= CIMINI_CONVERT_EOL;
3284
+ cinf.flags |= CIMINI_CONVERT_EOL;
32093285
}
3210
-
3286
+ if(find_option("delta",0,0)!=0){
3287
+ cinf.flags |= CIMINI_PREFER_DELTA;
3288
+ }
32113289
db_find_and_open_repository(0, 0);
32123290
verify_all_options();
32133291
user_select();
3214
-
32153292
if(g.argc!=3){
32163293
usage("INFILE");
32173294
}
32183295
3219
- if(1){
3296
+ if(!(cinf.flags & CIMINI_DRY_RUN)){
3297
+ /* Until this feature is fully vetted, disallow it in the main
3298
+ ** fossil repo unless dry-run mode is being used. */
32203299
char * zProjCode = db_get("project-code",0);
32213300
assert(zProjCode);
32223301
if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333",
32233302
zProjCode)){
32243303
fossil_fatal("Never, ever run this in/on the core fossil repo "
@@ -3261,21 +3340,20 @@
32613340
assert(cinf.pParent!=0);
32623341
blob_read_from_file(&cinf.fileContent, zFilename,
32633342
ExtFILE/*may want to reconsider*/);
32643343
{
32653344
Blob errMsg = empty_blob;
3266
- const int rc = checkin_mini(&cinf, &newRid, ciminiFlags,
3267
- &errMsg);
3345
+ const int rc = checkin_mini(&cinf, &newRid, &errMsg);
32683346
CheckinMiniInfo_cleanup(&cinf);
32693347
if(rc){
32703348
assert(blob_size(&errMsg)==0);
32713349
}else{
32723350
assert(blob_size(&errMsg));
32733351
fossil_fatal("%b", &errMsg);
32743352
}
32753353
}
3276
- if(!(ciminiFlags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){
3354
+ if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){
32773355
fossil_warning("The checkout state is now out of sync "
32783356
"with regards to this commit. It needs to be "
32793357
"'update'd or 'close'd and re-'open'ed.");
32803358
}
32813359
}
32823360
--- src/checkin.c
+++ src/checkin.c
@@ -2685,38 +2685,42 @@
2685 fossil_print("**** warning: a fork has occurred *****\n");
2686 }
2687 }
2688
2689 /*
2690 ** State for the "web-checkin" infrastructure, which enables the
2691 ** ability to commit changes to a single file via an HTTP request.
2692 **
2693 ** Memory for all non-const (char *) members is owned by the
2694 ** CheckinMiniInfo instance.
2695 */
2696 struct CheckinMiniInfo {
 
 
2697 Blob comment; /* Check-in comment text */
2698 char *zMimetype; /* Mimetype of check-in command. May be NULL */
2699 Manifest * pParent; /* parent checkin */
2700 char *zParentUuid; /* UUID of pParent */
2701 char *zUser; /* User name */
2702 char *zDate; /* Optionally force this date string
2703 (anything supported by
2704 date_in_standard_format().
2705 Maybe be NULL. */
2706 char *zFilename; /* Name of single file to commit. Must be
2707 relative to the top of the repo. */
2708 Blob fileContent; /* Content of file referred to by zFilename. */
2709 Blob fileHash; /* Hash of this->fileContent, using the
2710 repo's preferred hash method. */
 
 
 
2711 };
2712 typedef struct CheckinMiniInfo CheckinMiniInfo;
2713 /*
2714 ** Initializes p to a known-valid default state.
2715 */
2716 static void CheckinMiniInfo_init( CheckinMiniInfo * p ){
2717 memset(p, 0, sizeof(CheckinMiniInfo));
 
2718 p->comment = p->fileContent = p->fileHash = empty_blob;
2719 }
2720
2721 /*
2722 ** Frees all memory owned by p, but does not free p.
@@ -2738,10 +2742,11 @@
2738
2739 /*
2740 ** Flags for checkin_mini()
2741 */
2742 enum fossil_cimini_flags {
 
2743 /*
2744 ** Tells checkin_mini() to use dry-run mode.
2745 */
2746 CIMINI_DRY_RUN = 1,
2747 /*
@@ -2774,12 +2779,11 @@
2774 ** affected, not the original file (if any).
2775 */
2776 CIMINI_CONVERT_EOL = 1<<5,
2777
2778 /*
2779 ** NOT YET IMPLEMENTED. A hint to checkin_mini() to prefer
2780 ** creation of a delta manifest.
2781 */
2782 CIMINI_PREFER_DELTA = 1<<6,
2783
2784 /*
2785 ** NOT YET IMPLEMENTED.
@@ -2786,31 +2790,137 @@
2786 */
2787 CIMINI_ALLOW_CLOSED_LEAF = 1<<7
2788 };
2789
2790 /*
2791 ** Creates a manifest file, written to pOut, from the state in the
2792 ** fully-populated pCI argument. pCI is not *semantically* modified
2793 ** but cannot be const because blob_str() may need to NUL-terminate
2794 ** any given blob.
 
2795 **
2796 ** Returns true on success. On error, returns 0 and, if pErr is not
2797 ** NULL, writes an error message there.
2798 */
2799 static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
2800 Blob * pErr){
2801 Blob zCard = empty_blob; /* Z-card checksum */
 
2802 ManifestFile *zFile; /* One file entry from the pCI->pParent*/
2803 int cmp = -99; /* filename comparison result */
2804 int fperm = 0; /* file permissions */
2805 const char *zPerm = 0; /* permissions for new F-card */
2806 const char *zFilename = 0; /* filename to use for F-card */
2807 const char *zUuid = 0; /* UUID for F-card */
 
2808 int (*fncmp)(char const *, char const *) = /* filename comparator */
2809 filenames_are_case_sensitive()
2810 ? fossil_strcmp
2811 : fossil_stricmp;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2812
2813 assert(blob_str(&pCI->fileHash));
2814 assert(pCI->pParent);
2815 assert(pCI->zFilename);
2816 assert(pCI->zUser);
@@ -2825,65 +2935,26 @@
2825 ** told to handle a symlink because there would seem to be no(?)
2826 ** sensible way to handle a symlink add/checkin without a
2827 ** checkout.
2828 */
2829 blob_zero(pOut);
 
 
 
 
 
 
 
2830 if(blob_size(&pCI->comment)!=0){
2831 blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment));
2832 }else{
2833 blob_append(pOut, "C (no\\scomment)\n", 16);
2834 }
2835 blob_appendf(pOut, "D %z\n", pCI->zDate);
2836 manifest_file_rewind(pCI->pParent);
2837 while((zFile = manifest_file_next(pCI->pParent, 0))){
2838 cmp = fncmp(zFile->zName, pCI->zFilename);
2839 if(cmp<0){
2840 blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid,
2841 (zFile->zPerm && *zFile->zPerm) ? " " : "",
2842 (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2843 }else{
2844 break;
2845 }
2846 }
2847 if(cmp==0){ /* Match */
2848 fperm = manifest_file_mperm(zFile);
2849 zFilename = zFile->zName
2850 /* use original name in case of name-case difference */;
2851 zUuid = blob_str(&pCI->fileHash);
2852 }else{
2853 /* This is a new file. */
2854 fperm = file_perm(pCI->zFilename, ExtFILE);
2855 zFilename = pCI->zFilename;
2856 zUuid = blob_str(&pCI->fileHash);
2857 if(cmp>0){
2858 assert(zFile!=0);
2859 assert(pCI->pParent->iFile>0);
2860 --pCI->pParent->iFile
2861 /* so that the next step picks up that entry again */;
2862 }
2863 }
2864 if(PERM_LNK==fperm){
2865 mf_err((pErr,"Cannot commit symlinks with this approach."));
2866 }else if(PERM_EXE==fperm){
2867 zPerm = " x";
2868 }else{
2869 zPerm = "";
2870 }
2871 assert(zFilename);
2872 assert(zUuid);
2873 assert(zPerm);
2874 blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm);
2875 while((zFile = manifest_file_next(pCI->pParent, 0))){
2876 cmp = fncmp(zFile->zName, pCI->zFilename);
2877 assert(cmp>0);
2878 if(cmp<=0){
2879 mf_err((pErr,"Internal error: mis-ordering of F-cards detected."));
2880 }
2881 blob_appendf(pOut, "F %F %s%s%s\n",
2882 zFile->zName, zFile->zUuid,
2883 (zFile->zPerm && *zFile->zPerm) ? " " : "",
2884 (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2885 }
2886
2887 if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){
2888 blob_appendf(pOut, "N %F\n", pCI->zMimetype);
2889 }
@@ -2936,16 +3007,15 @@
2936 ** diagnostic message there.
2937 **
2938 ** Returns true on success. If pRid is not NULL, the RID of the
2939 ** resulting manifest is written to *pRid.
2940 **
2941 ** ciminiFlags is a bitmask of optional flags from fossil_cimini_flags
2942 ** enum. See that enum for the docs for each flag. Pass 0 for no
2943 ** flags.
2944 */
2945 static int checkin_mini( CheckinMiniInfo * pCI, int *pRid,
2946 int ciminiFlags, Blob * pErr ){
2947 Blob mf = empty_blob; /* output manifest */
2948 int rid = 0, frid = 0; /* various RIDs */
2949 const int isPrivate = content_is_private(pCI->pParent->rid);
2950 ManifestFile * zFilePrev; /* file entry from pCI->pParent */
2951 int prevFRid = 0; /* RID of file's prev. version */
@@ -2964,20 +3034,24 @@
2964 ** no reason we can't do that via the generated manifest,
2965 ** but the commit command does not offer that option, so
2966 ** we won't, either.
2967 */
2968 }
2969 if(!(CIMINI_ALLOW_FORK & ciminiFlags)
2970 && !is_a_leaf(pCI->pParent->rid)){
2971 ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.",
2972 pCI->zParentUuid));
2973 }
2974 if(!(CIMINI_ALLOW_MERGE_MARKER & ciminiFlags)
2975 && contains_merge_marker(&pCI->fileContent)){
2976 ci_err((pErr,"Content appears to contain a merge conflict marker."));
2977 }
2978
 
 
 
 
2979 {
2980 /*
2981 ** Normalize the timestamp. We don't use date_in_standard_format()
2982 ** because that has side-effects we don't want to trigger here.
2983 */
@@ -2989,11 +3063,11 @@
2989 ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate));
2990 }
2991 fossil_free(pCI->zDate);
2992 pCI->zDate = zDVal;
2993 }
2994 if(!(CIMINI_ALLOW_OLDER & ciminiFlags)
2995 && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){
2996 ci_err((pErr,"Checkin time (%s) may not be older "
2997 "than its parent (%z).",
2998 pCI->zDate,
2999 db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)",
@@ -3024,37 +3098,38 @@
3024 */
3025 manifest_file_rewind(pCI->pParent);
3026 zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0);
3027 if(!zFilePrev){
3028 ci_err((pErr,"File [%s] not found in manifest [%S]. "
3029 "Adding new files is currently not allowed.",
3030 pCI->zFilename, pCI->zParentUuid));
3031 }else if(zFilePrev->zPerm && strstr(zFilePrev->zPerm, "l")){
3032 ci_err((pErr,"Cannot save a symlink this way."));
 
3033 }
3034 if(zFilePrev){
3035 prevFRid = fast_uuid_to_rid(zFilePrev->zUuid);
3036 }
3037
3038 /* Confirm that the new content has the same EOL style as its
3039 ** predecessor and convert it, if needed, to the same style. Note
3040 ** that this inherently runs a risk of breaking content, e.g. string
3041 ** literals which contain embedded newlines. Note that HTML5
3042 ** specifies that form-submitted TEXTAREA content gets normalized to
3043 ** CRLF-style:
3044 **
3045 ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
3046 **
3047 ** More performant/efficient would be to offer a flag which says
3048 ** which newline form to use, converting the new copy (if needed)
3049 ** without having to examine the original. Since the primary use
3050 ** case is a web interface, it would be easy to offer it as a
3051 ** checkbox there.
3052 */
3053 if((CIMINI_CONVERT_EOL & ciminiFlags)
3054 && zFilePrev!=0
3055 && blob_size(&pCI->fileContent)>0){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3056 const int pseudoBinary = LOOK_LONG | LOOK_NUL;
3057 const int lookFlags = LOOK_CRLF | pseudoBinary;
3058 const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags );
3059 if(!(pseudoBinary & lookNew)){
3060 Blob contentPrev = empty_blob;
@@ -3101,11 +3176,11 @@
3101 if(create_manifest_mini(&mf, pCI, pErr)==0){
3102 return 0;
3103 }
3104 rid = content_put_ex(&mf, 0, 0, 0, isPrivate);
3105 content_deltify(rid, &pCI->pParent->rid, 1, 0);
3106 if(ciminiFlags & CIMINI_DUMP_MANIFEST){
3107 fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf);
3108 }
3109 manifest_crosslink(rid, &mf, 0);
3110 blob_reset(&mf);
3111 /* Save and deltify the file content... */
@@ -3113,11 +3188,11 @@
3113 0, 0, isPrivate);
3114 if(zFilePrev!=0){
3115 assert(prevFRid>0);
3116 content_deltify(frid, &prevFRid, 1, 0);
3117 }
3118 db_end_transaction((CIMINI_DRY_RUN & ciminiFlags) ? 1 : 0);
3119 if(pRid!=0){
3120 *pRid = rid;
3121 }
3122 return 1;
3123 ci_error:
@@ -3160,10 +3235,12 @@
3160 ** ancestor.
3161 ** --convert-eol Convert EOL style of the checkin to match
3162 ** the previous version's content. Does not
3163 ** modify the original file, only the
3164 ** checked-in content.
 
 
3165 ** --dump-manifest|-d Dumps the generated manifest to stdout.
3166 ** --wet-run Disables the default dry-run mode.
3167 **
3168 ** Example:
3169 **
@@ -3178,47 +3255,49 @@
3178 const char * zCommentFile; /* -M FILE */
3179 const char * zAsFilename; /* --as filename */
3180 const char * zRevision; /* --revision|-r [=trunk|checkout] */
3181 const char * zUser; /* --user-override */
3182 const char * zDate; /* --date-override */
3183 int ciminiFlags = 0; /* flags for checkin_mini(). */
3184
3185 CheckinMiniInfo_init(&cinf);
3186 zComment = find_option("comment","m",1);
3187 zCommentFile = find_option("comment-file","M",1);
3188 zAsFilename = find_option("as",0,1);
3189 zRevision = find_option("revision","r",1);
3190 zUser = find_option("user-override",0,1);
3191 zDate = find_option("date-override",0,1);
3192 if(find_option("wet-run",0,0)==0){
3193 ciminiFlags |= CIMINI_DRY_RUN;
3194 }
3195 if(find_option("allow-fork",0,0)!=0){
3196 ciminiFlags |= CIMINI_ALLOW_FORK;
3197 }
3198 if(find_option("dump-manifest","d",0)!=0){
3199 ciminiFlags |= CIMINI_DUMP_MANIFEST;
3200 }
3201 if(find_option("allow-merge-conflict",0,0)!=0){
3202 ciminiFlags |= CIMINI_ALLOW_MERGE_MARKER;
3203 }
3204 if(find_option("allow-older",0,0)!=0){
3205 ciminiFlags |= CIMINI_ALLOW_OLDER;
3206 }
3207 if(find_option("convert-eol",0,0)!=0){
3208 ciminiFlags |= CIMINI_CONVERT_EOL;
3209 }
3210
 
 
3211 db_find_and_open_repository(0, 0);
3212 verify_all_options();
3213 user_select();
3214
3215 if(g.argc!=3){
3216 usage("INFILE");
3217 }
3218
3219 if(1){
 
 
3220 char * zProjCode = db_get("project-code",0);
3221 assert(zProjCode);
3222 if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333",
3223 zProjCode)){
3224 fossil_fatal("Never, ever run this in/on the core fossil repo "
@@ -3261,21 +3340,20 @@
3261 assert(cinf.pParent!=0);
3262 blob_read_from_file(&cinf.fileContent, zFilename,
3263 ExtFILE/*may want to reconsider*/);
3264 {
3265 Blob errMsg = empty_blob;
3266 const int rc = checkin_mini(&cinf, &newRid, ciminiFlags,
3267 &errMsg);
3268 CheckinMiniInfo_cleanup(&cinf);
3269 if(rc){
3270 assert(blob_size(&errMsg)==0);
3271 }else{
3272 assert(blob_size(&errMsg));
3273 fossil_fatal("%b", &errMsg);
3274 }
3275 }
3276 if(!(ciminiFlags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){
3277 fossil_warning("The checkout state is now out of sync "
3278 "with regards to this commit. It needs to be "
3279 "'update'd or 'close'd and re-'open'ed.");
3280 }
3281 }
3282
--- src/checkin.c
+++ src/checkin.c
@@ -2685,38 +2685,42 @@
2685 fossil_print("**** warning: a fork has occurred *****\n");
2686 }
2687 }
2688
2689 /*
2690 ** State for the "mini-checkin" infrastructure, which enables the
2691 ** ability to commit changes to a single file via an HTTP request.
2692 **
2693 ** Memory for all non-const (char *) members is owned by the
2694 ** CheckinMiniInfo instance.
2695 */
2696 struct CheckinMiniInfo {
2697 Manifest * pParent; /* parent checkin */
2698 char *zParentUuid; /* UUID of pParent */
2699 Blob comment; /* Check-in comment text */
2700 char *zMimetype; /* Mimetype of check-in command. May be NULL */
 
 
2701 char *zUser; /* User name */
2702 char *zDate; /* Optionally force this date string
2703 (anything supported by
2704 date_in_standard_format()).
2705 Maybe be NULL. */
2706 char *zFilename; /* Name of single file to commit. Must be
2707 relative to the top of the repo. */
2708 Blob fileContent; /* Content of file referred to by zFilename. */
2709 Blob fileHash; /* Hash of this->fileContent, using the
2710 repo's preferred hash method. */
2711 int flags; /* Bitmask of fossil_cimini_flags for
2712 communication from checkin_mini() to
2713 create_manifest_mini(). */
2714 };
2715 typedef struct CheckinMiniInfo CheckinMiniInfo;
2716 /*
2717 ** Initializes p to a known-valid default state.
2718 */
2719 static void CheckinMiniInfo_init( CheckinMiniInfo * p ){
2720 memset(p, 0, sizeof(CheckinMiniInfo));
2721 p->flags = 0;
2722 p->comment = p->fileContent = p->fileHash = empty_blob;
2723 }
2724
2725 /*
2726 ** Frees all memory owned by p, but does not free p.
@@ -2738,10 +2742,11 @@
2742
2743 /*
2744 ** Flags for checkin_mini()
2745 */
2746 enum fossil_cimini_flags {
2747 CIMINI_NONE = 0,
2748 /*
2749 ** Tells checkin_mini() to use dry-run mode.
2750 */
2751 CIMINI_DRY_RUN = 1,
2752 /*
@@ -2774,12 +2779,11 @@
2779 ** affected, not the original file (if any).
2780 */
2781 CIMINI_CONVERT_EOL = 1<<5,
2782
2783 /*
2784 ** A hint to checkin_mini() to prefer creation of a delta manifest.
 
2785 */
2786 CIMINI_PREFER_DELTA = 1<<6,
2787
2788 /*
2789 ** NOT YET IMPLEMENTED.
@@ -2786,31 +2790,137 @@
2790 */
2791 CIMINI_ALLOW_CLOSED_LEAF = 1<<7
2792 };
2793
2794 /*
2795 ** Handles the F-card parts for create_manifest_mini().
2796 **
2797 ** If asDelta is true, pCI->pParent MUST be a baseline, else an
2798 ** assert() is triggered. Still TODO is creating a delta from
2799 ** pCI->pParent when that object is itself a delta.
2800 **
2801 ** Returns 1 on success, 0 on error, and writes any error message to
2802 ** pErr (if it's not NULL).
2803 */
2804 static int create_manifest_mini_fcards( Blob * pOut,
2805 CheckinMiniInfo * pCI,
2806 int asDelta,
2807 Blob * pErr){
2808 ManifestFile *zFile; /* One file entry from the pCI->pParent*/
 
2809 int fperm = 0; /* file permissions */
2810 const char *zPerm = 0; /* permissions for new F-card */
2811 const char *zFilename = 0; /* filename for new F-card */
2812 const char *zUuid = 0; /* UUID for new F-card */
2813 int cmp = 0; /* filename comparison result */
2814 int (*fncmp)(char const *, char const *) = /* filename comparator */
2815 filenames_are_case_sensitive()
2816 ? fossil_strcmp
2817 : fossil_stricmp;
2818 #define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0
2819
2820 /* Potential TODOs:
2821 **
2822 ** - When updating a file and the new one has the +x bit, add that
2823 ** bit if needed. We also need that logic in the upstream "has
2824 ** this file changed?" check. We currently always inherit the old
2825 ** perms.
2826 */
2827
2828 manifest_file_rewind(pCI->pParent);
2829 if(asDelta){
2830 /* Parent is a baseline and we have only 1 file to modify, so this
2831 ** is the simplest case...
2832 */
2833 assert(pCI->pParent->zBaseline==0 && "Delta-from-delta is NYI.");
2834 zFile = manifest_file_seek(pCI->pParent, pCI->zFilename,0);
2835 if(zFile==0){
2836 /* New file */
2837 fperm = file_perm(pCI->zFilename, ExtFILE);
2838 zFilename = pCI->zFilename;
2839 }else{
2840 /* Replacement file */
2841 fperm = manifest_file_mperm(zFile);
2842 zFilename = zFile->zName
2843 /* use original name in case of name-case difference */;
2844 zFile = 0;
2845 }
2846 }else{
2847 /* Non-delta: write F-cards which lexically preceed pCI->zFilename */
2848 while((zFile = manifest_file_next(pCI->pParent, 0))){
2849 cmp = fncmp(zFile->zName, pCI->zFilename);
2850 if(cmp<0){
2851 blob_appendf(pOut, "F %F %s%s%s\n", zFile->zName, zFile->zUuid,
2852 (zFile->zPerm && *zFile->zPerm) ? " " : "",
2853 (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2854 }else{
2855 break;
2856 }
2857 }
2858 /* Figure out file perms and name to save... */
2859 if(cmp==0){
2860 /* Match: override this F-card */
2861 fperm = manifest_file_mperm(zFile);
2862 zFilename = zFile->zName
2863 /* use original name in case of name-case difference */;
2864 zFile = 0;
2865 }else{
2866 /* This is a new file. */
2867 fperm = file_perm(pCI->zFilename, ExtFILE);
2868 zFilename = pCI->zFilename;
2869 }
2870 }
2871 assert(fperm!=PERM_LNK && "This should have been validated before.");
2872 if(PERM_LNK==fperm){
2873 mf_err((pErr,"Cannot commit symlinks via mini-checkin."));
2874 }else if(PERM_EXE==fperm){
2875 zPerm = " x";
2876 }else{
2877 zPerm = "";
2878 }
2879 zUuid = blob_str(&pCI->fileHash);
2880 assert(zFile ? cmp>0&&asDelta==0 : asDelta!=0||cmp==0);
2881 assert(zFilename);
2882 assert(zUuid);
2883 assert(zPerm);
2884 blob_appendf(pOut, "F %F %s%s\n", zFilename, zUuid, zPerm);
2885 /* Non-delta: write F-cards which lexically follow pCI->zFilename */
2886 while(zFile!=0){
2887 #ifndef NDEBUG
2888 cmp = fncmp(zFile->zName, pCI->zFilename);
2889 assert(cmp>0);
2890 if(cmp<=0){
2891 mf_err((pErr,"Internal error: mis-ordering of "
2892 "F-cards detected."));
2893 }
2894 #endif
2895 blob_appendf(pOut, "F %F %s%s%s\n",
2896 zFile->zName, zFile->zUuid,
2897 (zFile->zPerm && *zFile->zPerm) ? " " : "",
2898 (zFile->zPerm && *zFile->zPerm) ? zFile->zPerm : "");
2899 zFile = manifest_file_next(pCI->pParent, 0);
2900 }
2901 #undef mf_err
2902 return 1;
2903 }
2904
2905
2906 /*
2907 ** Creates a manifest file, written to pOut, from the state in the
2908 ** fully-populated and semantically valid pCI argument. pCI is not
2909 ** *semantically* modified but cannot be const because blob_str() may
2910 ** need to NUL-terminate any given blob.
2911 **
2912 ** Returns true on success. On error, returns 0 and, if pErr is not
2913 ** NULL, writes an error message there.
2914 **
2915 ** Intended only to be called via checkin_mini() or routines which
2916 ** have already completely vetted pCI.
2917 */
2918 static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
2919 Blob * pErr){
2920 Blob zCard = empty_blob; /* Z-card checksum */
2921 int asDelta = 0;
2922
2923 assert(blob_str(&pCI->fileHash));
2924 assert(pCI->pParent);
2925 assert(pCI->zFilename);
2926 assert(pCI->zUser);
@@ -2825,65 +2935,26 @@
2935 ** told to handle a symlink because there would seem to be no(?)
2936 ** sensible way to handle a symlink add/checkin without a
2937 ** checkout.
2938 */
2939 blob_zero(pOut);
2940 if((pCI->flags & CIMINI_PREFER_DELTA)
2941 && pCI->pParent->zBaseline==0){
2942 asDelta = 1;
2943 }
2944 if(asDelta){
2945 blob_appendf(pOut, "B %s\n", pCI->zParentUuid);
2946 }
2947 if(blob_size(&pCI->comment)!=0){
2948 blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment));
2949 }else{
2950 blob_append(pOut, "C (no\\scomment)\n", 16);
2951 }
2952 blob_appendf(pOut, "D %z\n", pCI->zDate);
2953
2954 if(!create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)){
2955 return 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2956 }
2957
2958 if(pCI->zMimetype!=0 && pCI->zMimetype[0]!=0){
2959 blob_appendf(pOut, "N %F\n", pCI->zMimetype);
2960 }
@@ -2936,16 +3007,15 @@
3007 ** diagnostic message there.
3008 **
3009 ** Returns true on success. If pRid is not NULL, the RID of the
3010 ** resulting manifest is written to *pRid.
3011 **
3012 ** The checkin process is largely influenced by pCI->flags, and that
3013 ** must be populated before calling this. See the fossil_cimini_flags
3014 ** enum for the docs for each flag.
3015 */
3016 static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){
 
3017 Blob mf = empty_blob; /* output manifest */
3018 int rid = 0, frid = 0; /* various RIDs */
3019 const int isPrivate = content_is_private(pCI->pParent->rid);
3020 ManifestFile * zFilePrev; /* file entry from pCI->pParent */
3021 int prevFRid = 0; /* RID of file's prev. version */
@@ -2964,20 +3034,24 @@
3034 ** no reason we can't do that via the generated manifest,
3035 ** but the commit command does not offer that option, so
3036 ** we won't, either.
3037 */
3038 }
3039 if(!(CIMINI_ALLOW_FORK & pCI->flags)
3040 && !is_a_leaf(pCI->pParent->rid)){
3041 ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.",
3042 pCI->zParentUuid));
3043 }
3044 if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags)
3045 && contains_merge_marker(&pCI->fileContent)){
3046 ci_err((pErr,"Content appears to contain a merge conflict marker."));
3047 }
3048 if(!file_is_simple_pathname(pCI->zFilename, 1)){
3049 ci_err((pErr,"Invalid filename for use in a repository: %s",
3050 pCI->zFilename));
3051 }
3052
3053 {
3054 /*
3055 ** Normalize the timestamp. We don't use date_in_standard_format()
3056 ** because that has side-effects we don't want to trigger here.
3057 */
@@ -2989,11 +3063,11 @@
3063 ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate));
3064 }
3065 fossil_free(pCI->zDate);
3066 pCI->zDate = zDVal;
3067 }
3068 if(!(CIMINI_ALLOW_OLDER & pCI->flags)
3069 && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){
3070 ci_err((pErr,"Checkin time (%s) may not be older "
3071 "than its parent (%z).",
3072 pCI->zDate,
3073 db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)",
@@ -3024,37 +3098,38 @@
3098 */
3099 manifest_file_rewind(pCI->pParent);
3100 zFilePrev = manifest_file_seek(pCI->pParent, pCI->zFilename, 0);
3101 if(!zFilePrev){
3102 ci_err((pErr,"File [%s] not found in manifest [%S]. "
3103 "Adding new files is currently not permitted.",
3104 pCI->zFilename, pCI->zParentUuid));
3105 }else if(zFilePrev->zPerm
3106 && manifest_file_mperm(zFilePrev)==PERM_LNK){
3107 ci_err((pErr,"Cannot save a symlink via a mini-checkin."));
3108 }
3109 if(zFilePrev){
3110 prevFRid = fast_uuid_to_rid(zFilePrev->zUuid);
3111 }
3112
3113 if((CIMINI_CONVERT_EOL & pCI->flags)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3114 && zFilePrev!=0
3115 && blob_size(&pCI->fileContent)>0){
3116 /* Confirm that the new content has the same EOL style as its
3117 ** predecessor and convert it, if needed, to the same style. Note
3118 ** that this inherently runs a risk of breaking content,
3119 ** e.g. string literals which contain embedded newlines. Note that
3120 ** HTML5 specifies that form-submitted TEXTAREA content gets
3121 ** normalized to CRLF-style:
3122 **
3123 ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
3124 **
3125 ** More performant/efficient would be to offer a flag which says
3126 ** which newline form to use, converting the new copy (if needed)
3127 ** without having to examine the original. Since the primary use
3128 ** case is a web interface, it would be easy to offer it as a
3129 ** checkbox there.
3130 */
3131 const int pseudoBinary = LOOK_LONG | LOOK_NUL;
3132 const int lookFlags = LOOK_CRLF | pseudoBinary;
3133 const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags );
3134 if(!(pseudoBinary & lookNew)){
3135 Blob contentPrev = empty_blob;
@@ -3101,11 +3176,11 @@
3176 if(create_manifest_mini(&mf, pCI, pErr)==0){
3177 return 0;
3178 }
3179 rid = content_put_ex(&mf, 0, 0, 0, isPrivate);
3180 content_deltify(rid, &pCI->pParent->rid, 1, 0);
3181 if(pCI->flags & CIMINI_DUMP_MANIFEST){
3182 fossil_print("Manifest %z:\n%b", rid_to_uuid(rid), &mf);
3183 }
3184 manifest_crosslink(rid, &mf, 0);
3185 blob_reset(&mf);
3186 /* Save and deltify the file content... */
@@ -3113,11 +3188,11 @@
3188 0, 0, isPrivate);
3189 if(zFilePrev!=0){
3190 assert(prevFRid>0);
3191 content_deltify(frid, &prevFRid, 1, 0);
3192 }
3193 db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0);
3194 if(pRid!=0){
3195 *pRid = rid;
3196 }
3197 return 1;
3198 ci_error:
@@ -3160,10 +3235,12 @@
3235 ** ancestor.
3236 ** --convert-eol Convert EOL style of the checkin to match
3237 ** the previous version's content. Does not
3238 ** modify the original file, only the
3239 ** checked-in content.
3240 ** --delta Prefer to generate a delta manifest, if
3241 ** able.
3242 ** --dump-manifest|-d Dumps the generated manifest to stdout.
3243 ** --wet-run Disables the default dry-run mode.
3244 **
3245 ** Example:
3246 **
@@ -3178,47 +3255,49 @@
3255 const char * zCommentFile; /* -M FILE */
3256 const char * zAsFilename; /* --as filename */
3257 const char * zRevision; /* --revision|-r [=trunk|checkout] */
3258 const char * zUser; /* --user-override */
3259 const char * zDate; /* --date-override */
 
3260
3261 CheckinMiniInfo_init(&cinf);
3262 zComment = find_option("comment","m",1);
3263 zCommentFile = find_option("comment-file","M",1);
3264 zAsFilename = find_option("as",0,1);
3265 zRevision = find_option("revision","r",1);
3266 zUser = find_option("user-override",0,1);
3267 zDate = find_option("date-override",0,1);
3268 if(find_option("wet-run",0,0)==0){
3269 cinf.flags |= CIMINI_DRY_RUN;
3270 }
3271 if(find_option("allow-fork",0,0)!=0){
3272 cinf.flags |= CIMINI_ALLOW_FORK;
3273 }
3274 if(find_option("dump-manifest","d",0)!=0){
3275 cinf.flags |= CIMINI_DUMP_MANIFEST;
3276 }
3277 if(find_option("allow-merge-conflict",0,0)!=0){
3278 cinf.flags |= CIMINI_ALLOW_MERGE_MARKER;
3279 }
3280 if(find_option("allow-older",0,0)!=0){
3281 cinf.flags |= CIMINI_ALLOW_OLDER;
3282 }
3283 if(find_option("convert-eol",0,0)!=0){
3284 cinf.flags |= CIMINI_CONVERT_EOL;
3285 }
3286 if(find_option("delta",0,0)!=0){
3287 cinf.flags |= CIMINI_PREFER_DELTA;
3288 }
3289 db_find_and_open_repository(0, 0);
3290 verify_all_options();
3291 user_select();
 
3292 if(g.argc!=3){
3293 usage("INFILE");
3294 }
3295
3296 if(!(cinf.flags & CIMINI_DRY_RUN)){
3297 /* Until this feature is fully vetted, disallow it in the main
3298 ** fossil repo unless dry-run mode is being used. */
3299 char * zProjCode = db_get("project-code",0);
3300 assert(zProjCode);
3301 if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333",
3302 zProjCode)){
3303 fossil_fatal("Never, ever run this in/on the core fossil repo "
@@ -3261,21 +3340,20 @@
3340 assert(cinf.pParent!=0);
3341 blob_read_from_file(&cinf.fileContent, zFilename,
3342 ExtFILE/*may want to reconsider*/);
3343 {
3344 Blob errMsg = empty_blob;
3345 const int rc = checkin_mini(&cinf, &newRid, &errMsg);
 
3346 CheckinMiniInfo_cleanup(&cinf);
3347 if(rc){
3348 assert(blob_size(&errMsg)==0);
3349 }else{
3350 assert(blob_size(&errMsg));
3351 fossil_fatal("%b", &errMsg);
3352 }
3353 }
3354 if(!(cinf.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){
3355 fossil_warning("The checkout state is now out of sync "
3356 "with regards to this commit. It needs to be "
3357 "'update'd or 'close'd and re-'open'ed.");
3358 }
3359 }
3360

Keyboard Shortcuts

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