Fossil SCM

Allow patches to be sent and received via standard input and standard output.

drh 2021-06-22 01:32 patch-cmd
Commit 9180106327c3863cc4cc1b18b41508193639234ce4bbd95938ecb1b298d11de2
1 file changed +87 -25
+87 -25
--- src/patch.c
+++ src/patch.c
@@ -116,20 +116,20 @@
116116
** named zOut.
117117
*/
118118
void patch_create(const char *zOut){
119119
int vid;
120120
121
- if( file_isdir(zOut, ExtFILE)!=0 ){
121
+ if( zOut && file_isdir(zOut, ExtFILE)!=0 ){
122122
fossil_fatal("patch file already exists: %s", zOut);
123123
}
124124
add_content_sql_commands(g.db);
125125
deltafunc_init(g.db);
126126
sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0,
127127
readfileFunc, 0, 0);
128128
sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0,
129129
mkdeltaFunc, 0, 0);
130
- db_multi_exec("ATTACH %Q AS patch;", zOut);
130
+ db_multi_exec("ATTACH %Q AS patch;", zOut ? zOut : ":memory:");
131131
db_multi_exec(
132132
"PRAGMA patch.journal_mode=OFF;\n"
133133
"PRAGMA patch.page_size=512;\n"
134134
"CREATE TABLE patch.chng(\n"
135135
" pathname TEXT,\n" /* Filename */
@@ -189,25 +189,67 @@
189189
"INSERT INTO patch.patchmerge(type,mhash)"
190190
" SELECT tmap.type,vmerge.mhash FROM vmerge, tmap"
191191
" WHERE tmap.id=vmerge.id;"
192192
);
193193
}
194
+
195
+ /* Write the database to standard output if zOut==0 */
196
+ if( zOut==0 ){
197
+ sqlite3_int64 sz;
198
+ unsigned char *pData;
199
+ pData = sqlite3_serialize(g.db, "patch", &sz, 0);
200
+ if( pData==0 ){
201
+ fossil_fatal("out of memory");
202
+ }
203
+ fossil_print("%025lld\n", sz);
204
+ fwrite(pData, sz, 1, stdout);
205
+ sqlite3_free(pData);
206
+ fflush(stdout);
207
+ }
194208
}
195209
196210
/*
197211
** Attempt to load and validate a patchfile identified by the first
198212
** argument.
199213
*/
200214
void patch_attach(const char *zIn){
201215
Stmt q;
202
- if( !file_isfile(zIn, ExtFILE) ){
203
- fossil_fatal("no such file: %s", zIn);
204
- }
205216
if( g.db==0 ){
206217
sqlite3_open(":memory:", &g.db);
207218
}
208
- db_multi_exec("ATTACH %Q AS patch", zIn);
219
+ if( zIn==0 ){
220
+ char zBuf[26];
221
+ size_t n;
222
+ sqlite3_int64 sz;
223
+ unsigned char *aIn;
224
+
225
+ n = fread(zBuf, sizeof(zBuf), 1, stdin);
226
+ if( n!=1 ){
227
+ fossil_fatal("unable to read size of input\n");
228
+ }
229
+ zBuf[sizeof(zBuf)-1] = 0;
230
+ sz = atoll(zBuf);
231
+ if( sz<512 || (sz%512)!=0 ){
232
+ fossil_fatal("bad size for input: %lld", sz);
233
+ }
234
+ aIn = sqlite3_malloc64( sz );
235
+ if( aIn==0 ){
236
+ fossil_fatal("out of memory");
237
+ }
238
+ n = fread(aIn, 1, sz, stdin);
239
+ if( n!=sz ){
240
+ fossil_fatal("got only %lld of %lld input bytes",
241
+ (sqlite3_int64)n, sz);
242
+ }
243
+ db_multi_exec("ATTACH ':memory:' AS patch");
244
+ sqlite3_deserialize(g.db, "patch", aIn, sz, sz,
245
+ SQLITE_DESERIALIZE_FREEONCLOSE);
246
+ }else if( !file_isfile(zIn, ExtFILE) ){
247
+ fossil_fatal("no such file: %s", zIn);
248
+ }else{
249
+ db_multi_exec("ATTACH %Q AS patch", zIn);
250
+ }
209251
db_prepare(&q, "PRAGMA patch.quick_check");
210252
while( db_step(&q)==SQLITE_ROW ){
211253
if( fossil_strcmp(db_column_text(&q,0),"ok")!=0 ){
212254
fossil_fatal("file %s is not a well-formed Fossil patchfile", zIn);
213255
}
@@ -472,29 +514,31 @@
472514
** This command is used to creates, view, and apply Fossil binary patches.
473515
** A Fossil binary patch is a single (binary) file that captures all of the
474516
** uncommitted changes of a check-out. Use Fossil binary patches to transfer
475517
** proposed or incomplete changes between machines for testing or analysis.
476518
**
477
-** > fossil patch create FILENAME
519
+** > fossil patch create [DIRECTORY] FILENAME
478520
**
479521
** Create a new binary patch in FILENAME that captures all uncommitted
480
-** changes in the current check-out.
522
+** changes in the check-out at DIRECTORY, or the current directory if
523
+** DIRECTORY is omitted.
524
+**
525
+** If FILENAME is "-" then the binary patch is written to standard
526
+** output, preceeded by 26 bytes of header that is an ASCII
527
+** representation of the number of bytes in the patch followed by a
528
+** newline.
481529
**
482
-** > fossil patch apply FILENAME
530
+** > fossil patch apply [DIRECTORY] FILENAME
483531
**
484
-** Apply the changes in FILENAME to the current check-out. Options:
532
+** Apply the changes in FILENAME to the check-out a DIRECTORY, or
533
+** in the current directory if DIRECTORY is omitted. Options:
485534
**
486535
** -f|--force Apply the patch even though there are unsaved
487536
** changes in the current check-out.
488537
** -n|--dryrun Do nothing, but print what would have happened.
489538
** -v|--verbose Extra output explaining what happens.
490539
**
491
-** > fossil patch diff [DIFF-FLAGS] FILENAME
492
-**
493
-** View the changes specified by the binary patch FILENAME in a
494
-** human-readable format. The usual diff flags apply.
495
-**
496540
** > fossil patch push REMOTE-CHECKOUT
497541
**
498542
** Create a patch for the current check-out, transfer that patch to
499543
** a remote machine (using ssh) and apply the patch there.
500544
**
@@ -518,30 +562,46 @@
518562
zCmd = g.argv[2];
519563
n = strlen(zCmd);
520564
if( strncmp(zCmd, "apply", n)==0 ){
521565
int forceFlag = find_option("force","f",0)!=0;
522566
unsigned flags = 0;
567
+ const char *zIn;
523568
if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN;
524569
if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
525
- db_must_be_within_tree();
526570
verify_all_options();
527
- if( g.argc!=4 ){
528
- usage("apply FILENAME");
571
+ if( g.argc!=4 && g.argc!=5 ){
572
+ usage("apply [DIRECTORY] FILENAME");
573
+ }
574
+ if( g.argc==5 ){
575
+ file_chdir(g.argv[3], 0);
576
+ zIn = g.argv[4];
577
+ }else{
578
+ zIn = g.argv[3];
529579
}
580
+ db_must_be_within_tree();
530581
if( !forceFlag && unsaved_changes(0) ){
531582
fossil_fatal("there are unsaved changes in the current checkout");
532583
}
533
- patch_attach(g.argv[3]);
584
+ if( fossil_strcmp(zIn,"-")==0 ) zIn = 0;
585
+ patch_attach(zIn);
534586
patch_apply(flags);
535587
}else
536588
if( strncmp(zCmd, "create", n)==0 ){
537
- db_must_be_within_tree();
589
+ const char *zOut;
538590
verify_all_options();
539
- if( g.argc!=4 ){
540
- usage("create FILENAME");
591
+ if( g.argc!=4 && g.argc!=5 ){
592
+ usage("create [DIRECTORY] FILENAME");
593
+ }
594
+ if( g.argc==5 ){
595
+ file_chdir(g.argv[3], 0);
596
+ zOut = g.argv[4];
597
+ }else{
598
+ zOut = g.argv[3];
541599
}
542
- patch_create(g.argv[3]);
600
+ if( fossil_strcmp(zOut, "-")==0 ) zOut = 0;
601
+ db_must_be_within_tree();
602
+ patch_create(zOut);
543603
}else
544604
if( strncmp(zCmd, "pull", n)==0 ){
545605
db_must_be_within_tree();
546606
verify_all_options();
547607
if( g.argc!=4 ){
@@ -556,17 +616,19 @@
556616
usage("push REMOTE-CHECKOUT");
557617
}
558618
fossil_print("TBD...\n");
559619
}else
560620
if( strncmp(zCmd, "view", n)==0 ){
561
- /* u64 diffFlags = diff_options(); */
621
+ const char *zIn;
562622
verify_all_options();
563623
if( g.argc!=4 ){
564624
usage("view FILENAME");
565625
}
566
- patch_attach(g.argv[3]);
626
+ zIn = g.argv[3];
627
+ if( fossil_strcmp(zIn, "-")==0 ) zIn = 0;
628
+ patch_attach(zIn);
567629
patch_view();
568630
}else
569631
{
570632
goto patch_usage;
571633
}
572634
}
573635
--- src/patch.c
+++ src/patch.c
@@ -116,20 +116,20 @@
116 ** named zOut.
117 */
118 void patch_create(const char *zOut){
119 int vid;
120
121 if( file_isdir(zOut, ExtFILE)!=0 ){
122 fossil_fatal("patch file already exists: %s", zOut);
123 }
124 add_content_sql_commands(g.db);
125 deltafunc_init(g.db);
126 sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0,
127 readfileFunc, 0, 0);
128 sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0,
129 mkdeltaFunc, 0, 0);
130 db_multi_exec("ATTACH %Q AS patch;", zOut);
131 db_multi_exec(
132 "PRAGMA patch.journal_mode=OFF;\n"
133 "PRAGMA patch.page_size=512;\n"
134 "CREATE TABLE patch.chng(\n"
135 " pathname TEXT,\n" /* Filename */
@@ -189,25 +189,67 @@
189 "INSERT INTO patch.patchmerge(type,mhash)"
190 " SELECT tmap.type,vmerge.mhash FROM vmerge, tmap"
191 " WHERE tmap.id=vmerge.id;"
192 );
193 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194 }
195
196 /*
197 ** Attempt to load and validate a patchfile identified by the first
198 ** argument.
199 */
200 void patch_attach(const char *zIn){
201 Stmt q;
202 if( !file_isfile(zIn, ExtFILE) ){
203 fossil_fatal("no such file: %s", zIn);
204 }
205 if( g.db==0 ){
206 sqlite3_open(":memory:", &g.db);
207 }
208 db_multi_exec("ATTACH %Q AS patch", zIn);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209 db_prepare(&q, "PRAGMA patch.quick_check");
210 while( db_step(&q)==SQLITE_ROW ){
211 if( fossil_strcmp(db_column_text(&q,0),"ok")!=0 ){
212 fossil_fatal("file %s is not a well-formed Fossil patchfile", zIn);
213 }
@@ -472,29 +514,31 @@
472 ** This command is used to creates, view, and apply Fossil binary patches.
473 ** A Fossil binary patch is a single (binary) file that captures all of the
474 ** uncommitted changes of a check-out. Use Fossil binary patches to transfer
475 ** proposed or incomplete changes between machines for testing or analysis.
476 **
477 ** > fossil patch create FILENAME
478 **
479 ** Create a new binary patch in FILENAME that captures all uncommitted
480 ** changes in the current check-out.
 
 
 
 
 
 
481 **
482 ** > fossil patch apply FILENAME
483 **
484 ** Apply the changes in FILENAME to the current check-out. Options:
 
485 **
486 ** -f|--force Apply the patch even though there are unsaved
487 ** changes in the current check-out.
488 ** -n|--dryrun Do nothing, but print what would have happened.
489 ** -v|--verbose Extra output explaining what happens.
490 **
491 ** > fossil patch diff [DIFF-FLAGS] FILENAME
492 **
493 ** View the changes specified by the binary patch FILENAME in a
494 ** human-readable format. The usual diff flags apply.
495 **
496 ** > fossil patch push REMOTE-CHECKOUT
497 **
498 ** Create a patch for the current check-out, transfer that patch to
499 ** a remote machine (using ssh) and apply the patch there.
500 **
@@ -518,30 +562,46 @@
518 zCmd = g.argv[2];
519 n = strlen(zCmd);
520 if( strncmp(zCmd, "apply", n)==0 ){
521 int forceFlag = find_option("force","f",0)!=0;
522 unsigned flags = 0;
 
523 if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN;
524 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
525 db_must_be_within_tree();
526 verify_all_options();
527 if( g.argc!=4 ){
528 usage("apply FILENAME");
 
 
 
 
 
 
529 }
 
530 if( !forceFlag && unsaved_changes(0) ){
531 fossil_fatal("there are unsaved changes in the current checkout");
532 }
533 patch_attach(g.argv[3]);
 
534 patch_apply(flags);
535 }else
536 if( strncmp(zCmd, "create", n)==0 ){
537 db_must_be_within_tree();
538 verify_all_options();
539 if( g.argc!=4 ){
540 usage("create FILENAME");
 
 
 
 
 
 
541 }
542 patch_create(g.argv[3]);
 
 
543 }else
544 if( strncmp(zCmd, "pull", n)==0 ){
545 db_must_be_within_tree();
546 verify_all_options();
547 if( g.argc!=4 ){
@@ -556,17 +616,19 @@
556 usage("push REMOTE-CHECKOUT");
557 }
558 fossil_print("TBD...\n");
559 }else
560 if( strncmp(zCmd, "view", n)==0 ){
561 /* u64 diffFlags = diff_options(); */
562 verify_all_options();
563 if( g.argc!=4 ){
564 usage("view FILENAME");
565 }
566 patch_attach(g.argv[3]);
 
 
567 patch_view();
568 }else
569 {
570 goto patch_usage;
571 }
572 }
573
--- src/patch.c
+++ src/patch.c
@@ -116,20 +116,20 @@
116 ** named zOut.
117 */
118 void patch_create(const char *zOut){
119 int vid;
120
121 if( zOut && file_isdir(zOut, ExtFILE)!=0 ){
122 fossil_fatal("patch file already exists: %s", zOut);
123 }
124 add_content_sql_commands(g.db);
125 deltafunc_init(g.db);
126 sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0,
127 readfileFunc, 0, 0);
128 sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0,
129 mkdeltaFunc, 0, 0);
130 db_multi_exec("ATTACH %Q AS patch;", zOut ? zOut : ":memory:");
131 db_multi_exec(
132 "PRAGMA patch.journal_mode=OFF;\n"
133 "PRAGMA patch.page_size=512;\n"
134 "CREATE TABLE patch.chng(\n"
135 " pathname TEXT,\n" /* Filename */
@@ -189,25 +189,67 @@
189 "INSERT INTO patch.patchmerge(type,mhash)"
190 " SELECT tmap.type,vmerge.mhash FROM vmerge, tmap"
191 " WHERE tmap.id=vmerge.id;"
192 );
193 }
194
195 /* Write the database to standard output if zOut==0 */
196 if( zOut==0 ){
197 sqlite3_int64 sz;
198 unsigned char *pData;
199 pData = sqlite3_serialize(g.db, "patch", &sz, 0);
200 if( pData==0 ){
201 fossil_fatal("out of memory");
202 }
203 fossil_print("%025lld\n", sz);
204 fwrite(pData, sz, 1, stdout);
205 sqlite3_free(pData);
206 fflush(stdout);
207 }
208 }
209
210 /*
211 ** Attempt to load and validate a patchfile identified by the first
212 ** argument.
213 */
214 void patch_attach(const char *zIn){
215 Stmt q;
 
 
 
216 if( g.db==0 ){
217 sqlite3_open(":memory:", &g.db);
218 }
219 if( zIn==0 ){
220 char zBuf[26];
221 size_t n;
222 sqlite3_int64 sz;
223 unsigned char *aIn;
224
225 n = fread(zBuf, sizeof(zBuf), 1, stdin);
226 if( n!=1 ){
227 fossil_fatal("unable to read size of input\n");
228 }
229 zBuf[sizeof(zBuf)-1] = 0;
230 sz = atoll(zBuf);
231 if( sz<512 || (sz%512)!=0 ){
232 fossil_fatal("bad size for input: %lld", sz);
233 }
234 aIn = sqlite3_malloc64( sz );
235 if( aIn==0 ){
236 fossil_fatal("out of memory");
237 }
238 n = fread(aIn, 1, sz, stdin);
239 if( n!=sz ){
240 fossil_fatal("got only %lld of %lld input bytes",
241 (sqlite3_int64)n, sz);
242 }
243 db_multi_exec("ATTACH ':memory:' AS patch");
244 sqlite3_deserialize(g.db, "patch", aIn, sz, sz,
245 SQLITE_DESERIALIZE_FREEONCLOSE);
246 }else if( !file_isfile(zIn, ExtFILE) ){
247 fossil_fatal("no such file: %s", zIn);
248 }else{
249 db_multi_exec("ATTACH %Q AS patch", zIn);
250 }
251 db_prepare(&q, "PRAGMA patch.quick_check");
252 while( db_step(&q)==SQLITE_ROW ){
253 if( fossil_strcmp(db_column_text(&q,0),"ok")!=0 ){
254 fossil_fatal("file %s is not a well-formed Fossil patchfile", zIn);
255 }
@@ -472,29 +514,31 @@
514 ** This command is used to creates, view, and apply Fossil binary patches.
515 ** A Fossil binary patch is a single (binary) file that captures all of the
516 ** uncommitted changes of a check-out. Use Fossil binary patches to transfer
517 ** proposed or incomplete changes between machines for testing or analysis.
518 **
519 ** > fossil patch create [DIRECTORY] FILENAME
520 **
521 ** Create a new binary patch in FILENAME that captures all uncommitted
522 ** changes in the check-out at DIRECTORY, or the current directory if
523 ** DIRECTORY is omitted.
524 **
525 ** If FILENAME is "-" then the binary patch is written to standard
526 ** output, preceeded by 26 bytes of header that is an ASCII
527 ** representation of the number of bytes in the patch followed by a
528 ** newline.
529 **
530 ** > fossil patch apply [DIRECTORY] FILENAME
531 **
532 ** Apply the changes in FILENAME to the check-out a DIRECTORY, or
533 ** in the current directory if DIRECTORY is omitted. Options:
534 **
535 ** -f|--force Apply the patch even though there are unsaved
536 ** changes in the current check-out.
537 ** -n|--dryrun Do nothing, but print what would have happened.
538 ** -v|--verbose Extra output explaining what happens.
539 **
 
 
 
 
 
540 ** > fossil patch push REMOTE-CHECKOUT
541 **
542 ** Create a patch for the current check-out, transfer that patch to
543 ** a remote machine (using ssh) and apply the patch there.
544 **
@@ -518,30 +562,46 @@
562 zCmd = g.argv[2];
563 n = strlen(zCmd);
564 if( strncmp(zCmd, "apply", n)==0 ){
565 int forceFlag = find_option("force","f",0)!=0;
566 unsigned flags = 0;
567 const char *zIn;
568 if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN;
569 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
 
570 verify_all_options();
571 if( g.argc!=4 && g.argc!=5 ){
572 usage("apply [DIRECTORY] FILENAME");
573 }
574 if( g.argc==5 ){
575 file_chdir(g.argv[3], 0);
576 zIn = g.argv[4];
577 }else{
578 zIn = g.argv[3];
579 }
580 db_must_be_within_tree();
581 if( !forceFlag && unsaved_changes(0) ){
582 fossil_fatal("there are unsaved changes in the current checkout");
583 }
584 if( fossil_strcmp(zIn,"-")==0 ) zIn = 0;
585 patch_attach(zIn);
586 patch_apply(flags);
587 }else
588 if( strncmp(zCmd, "create", n)==0 ){
589 const char *zOut;
590 verify_all_options();
591 if( g.argc!=4 && g.argc!=5 ){
592 usage("create [DIRECTORY] FILENAME");
593 }
594 if( g.argc==5 ){
595 file_chdir(g.argv[3], 0);
596 zOut = g.argv[4];
597 }else{
598 zOut = g.argv[3];
599 }
600 if( fossil_strcmp(zOut, "-")==0 ) zOut = 0;
601 db_must_be_within_tree();
602 patch_create(zOut);
603 }else
604 if( strncmp(zCmd, "pull", n)==0 ){
605 db_must_be_within_tree();
606 verify_all_options();
607 if( g.argc!=4 ){
@@ -556,17 +616,19 @@
616 usage("push REMOTE-CHECKOUT");
617 }
618 fossil_print("TBD...\n");
619 }else
620 if( strncmp(zCmd, "view", n)==0 ){
621 const char *zIn;
622 verify_all_options();
623 if( g.argc!=4 ){
624 usage("view FILENAME");
625 }
626 zIn = g.argv[3];
627 if( fossil_strcmp(zIn, "-")==0 ) zIn = 0;
628 patch_attach(zIn);
629 patch_view();
630 }else
631 {
632 goto patch_usage;
633 }
634 }
635

Keyboard Shortcuts

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