1 /*
2 ** Copyright (c) 2021 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** drh@hwaci.com
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code used to implement the "diff" command
19 */
20 #include "config.h"
21 #include "patch.h"
22 #include <assert.h>
23
24 /*
25 ** Try to compute the name of the computer on which this process
26 ** is running.
27 */
fossil_hostname(void)28 char *fossil_hostname(void){
29 FILE *in;
30 char zBuf[200];
31 in = popen("hostname","r");
32 if( in ){
33 size_t n = fread(zBuf, 1, sizeof(zBuf)-1, in);
34 while( n>0 && fossil_isspace(zBuf[n-1]) ){ n--; }
35 if( n<0 ) n = 0;
36 zBuf[n] = 0;
37 pclose(in);
38 return fossil_strdup(zBuf);
39 }
40 return 0;
41 }
42
43 /*
44 ** Flags passed from the main patch_cmd() routine into subfunctions used
45 ** to implement the various subcommands.
46 */
47 #define PATCH_DRYRUN 0x0001
48 #define PATCH_VERBOSE 0x0002
49 #define PATCH_FORCE 0x0004
50
51 /*
52 ** Implementation of the "readfile(X)" SQL function. The entire content
53 ** of the checkout file named X is read and returned as a BLOB.
54 */
readfileFunc(sqlite3_context * context,int argc,sqlite3_value ** argv)55 static void readfileFunc(
56 sqlite3_context *context,
57 int argc,
58 sqlite3_value **argv
59 ){
60 const char *zName;
61 Blob x;
62 sqlite3_int64 sz;
63 (void)(argc); /* Unused parameter */
64 zName = (const char*)sqlite3_value_text(argv[0]);
65 if( zName==0 || (zName[0]=='-' && zName[1]==0) ) return;
66 sz = blob_read_from_file(&x, zName, RepoFILE);
67 sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT);
68 blob_reset(&x);
69 }
70
71 /*
72 ** mkdelta(X,Y)
73 **
74 ** X is an numeric artifact id. Y is a filename.
75 **
76 ** Compute a compressed delta that carries X into Y. Or return
77 ** and zero-length blob if X is equal to Y.
78 */
mkdeltaFunc(sqlite3_context * context,int argc,sqlite3_value ** argv)79 static void mkdeltaFunc(
80 sqlite3_context *context,
81 int argc,
82 sqlite3_value **argv
83 ){
84 const char *zFile;
85 Blob x, y;
86 int rid;
87 char *aOut;
88 int nOut;
89 sqlite3_int64 sz;
90
91 rid = sqlite3_value_int(argv[0]);
92 if( !content_get(rid, &x) ){
93 sqlite3_result_error(context, "mkdelta(X,Y): no content for X", -1);
94 return;
95 }
96 zFile = (const char*)sqlite3_value_text(argv[1]);
97 if( zFile==0 ){
98 sqlite3_result_error(context, "mkdelta(X,Y): NULL Y argument", -1);
99 blob_reset(&x);
100 return;
101 }
102 sz = blob_read_from_file(&y, zFile, RepoFILE);
103 if( sz<0 ){
104 sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1);
105 blob_reset(&x);
106 return;
107 }
108 aOut = sqlite3_malloc64(sz+70);
109 if( aOut==0 ){
110 sqlite3_result_error_nomem(context);
111 blob_reset(&y);
112 blob_reset(&x);
113 return;
114 }
115 if( blob_size(&x)==blob_size(&y)
116 && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0
117 ){
118 blob_reset(&y);
119 blob_reset(&x);
120 sqlite3_result_blob64(context, "", 0, SQLITE_STATIC);
121 return;
122 }
123 nOut = delta_create(blob_buffer(&x),blob_size(&x),
124 blob_buffer(&y),blob_size(&y), aOut);
125 blob_reset(&x);
126 blob_reset(&y);
127 blob_init(&x, aOut, nOut);
128 blob_compress(&x, &x);
129 sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x),
130 SQLITE_TRANSIENT);
131 blob_reset(&x);
132 }
133
134
135 /*
136 ** Generate a binary patch file and store it into the file
137 ** named zOut.
138 */
patch_create(unsigned mFlags,const char * zOut,FILE * out)139 void patch_create(unsigned mFlags, const char *zOut, FILE *out){
140 int vid;
141 char *z;
142
143 if( zOut && file_isdir(zOut, ExtFILE)!=0 ){
144 if( mFlags & PATCH_FORCE ){
145 file_delete(zOut);
146 }
147 if( file_isdir(zOut, ExtFILE)!=0 ){
148 fossil_fatal("patch file already exists: %s", zOut);
149 }
150 }
151 add_content_sql_commands(g.db);
152 deltafunc_init(g.db);
153 sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0,
154 readfileFunc, 0, 0);
155 sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0,
156 mkdeltaFunc, 0, 0);
157 db_multi_exec("ATTACH %Q AS patch;", zOut ? zOut : ":memory:");
158 db_multi_exec(
159 "PRAGMA patch.journal_mode=OFF;\n"
160 "PRAGMA patch.page_size=512;\n"
161 "CREATE TABLE patch.chng(\n"
162 " pathname TEXT,\n" /* Filename */
163 " origname TEXT,\n" /* Name before rename. NULL if not renamed */
164 " hash TEXT,\n" /* Baseline hash. NULL for new files. */
165 " isexe BOOL,\n" /* True if executable */
166 " islink BOOL,\n" /* True if is a symbolic link */
167 " delta BLOB\n" /* compressed delta. NULL if deleted.
168 ** length 0 if unchanged */
169 ");"
170 "CREATE TABLE patch.cfg(\n"
171 " key TEXT,\n"
172 " value ANY\n"
173 ");"
174 );
175 vid = db_lget_int("checkout", 0);
176 vfile_check_signature(vid, CKSIG_ENOTFILE);
177 user_select();
178 db_multi_exec(
179 "INSERT INTO patch.cfg(key,value)"
180 "SELECT 'baseline',uuid FROM blob WHERE rid=%d "
181 "UNION ALL"
182 " SELECT 'ckout',rtrim(%Q,'/')"
183 "UNION ALL"
184 " SELECT 'repo',%Q "
185 "UNION ALL"
186 " SELECT 'user',%Q "
187 "UNION ALL"
188 " SELECT 'date',julianday('now')"
189 "UNION ALL"
190 " SELECT name,value FROM repository.config"
191 " WHERE name IN ('project-code','project-name') "
192 "UNION ALL"
193 " SELECT 'fossil-date',julianday('" MANIFEST_DATE "')"
194 ";", vid, g.zLocalRoot, g.zRepositoryName, g.zLogin);
195 z = fossil_hostname();
196 if( z ){
197 db_multi_exec(
198 "INSERT INTO patch.cfg(key,value)VALUES('hostname',%Q)", z);
199 fossil_free(z);
200 }
201
202 /* New files */
203 db_multi_exec(
204 "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)"
205 " SELECT pathname, NULL, isexe, islink,"
206 " compress(read_co_file(%Q||pathname))"
207 " FROM vfile WHERE rid==0;",
208 g.zLocalRoot
209 );
210
211 /* Deleted files */
212 db_multi_exec(
213 "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)"
214 " SELECT pathname, NULL, 0, 0, NULL"
215 " FROM vfile WHERE deleted;"
216 );
217
218 /* Changed files */
219 db_multi_exec(
220 "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)"
221 " SELECT pathname, nullif(origname,pathname), blob.uuid, isexe, islink,"
222 " mkdelta(blob.rid, %Q||pathname)"
223 " FROM vfile, blob"
224 " WHERE blob.rid=vfile.rid"
225 " AND NOT deleted AND (chnged OR origname<>pathname);",
226 g.zLocalRoot
227 );
228
229 /* Merges */
230 if( db_exists("SELECT 1 FROM localdb.vmerge WHERE id<=0") ){
231 db_multi_exec(
232 "CREATE TABLE patch.patchmerge(type TEXT,mhash TEXT);\n"
233 "WITH tmap(id,type) AS (VALUES(0,'merge'),(-1,'cherrypick'),"
234 "(-2,'backout'),(-4,'integrate'))"
235 "INSERT INTO patch.patchmerge(type,mhash)"
236 " SELECT tmap.type,vmerge.mhash FROM vmerge, tmap"
237 " WHERE tmap.id=vmerge.id;"
238 );
239 }
240
241 /* Write the database to standard output if zOut==0 */
242 if( zOut==0 ){
243 sqlite3_int64 sz;
244 unsigned char *pData;
245 pData = sqlite3_serialize(g.db, "patch", &sz, 0);
246 if( pData==0 ){
247 fossil_fatal("out of memory");
248 }
249 #ifdef _WIN32
250 fflush(out);
251 _setmode(_fileno(out), _O_BINARY);
252 #endif
253 fwrite(pData, sz, 1, out);
254 sqlite3_free(pData);
255 fflush(out);
256 }
257 }
258
259 /*
260 ** Attempt to load and validate a patchfile identified by the first
261 ** argument.
262 */
patch_attach(const char * zIn,FILE * in)263 void patch_attach(const char *zIn, FILE *in){
264 Stmt q;
265 if( g.db==0 ){
266 sqlite3_open(":memory:", &g.db);
267 }
268 if( zIn==0 ){
269 Blob buf;
270 int rc;
271 int sz;
272 unsigned char *pData;
273 blob_init(&buf, 0, 0);
274 #ifdef _WIN32
275 _setmode(_fileno(in), _O_BINARY);
276 #endif
277 sz = blob_read_from_channel(&buf, in, -1);
278 pData = (unsigned char*)blob_buffer(&buf);
279 db_multi_exec("ATTACH ':memory:' AS patch");
280 if( g.fSqlTrace ){
281 fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz);
282 }
283 rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0);
284 if( rc ){
285 fossil_fatal("cannot open patch database: %s", sqlite3_errmsg(g.db));
286 }
287 }else if( !file_isfile(zIn, ExtFILE) ){
288 fossil_fatal("no such file: %s", zIn);
289 }else{
290 db_multi_exec("ATTACH %Q AS patch", zIn);
291 }
292 db_prepare(&q, "PRAGMA patch.quick_check");
293 while( db_step(&q)==SQLITE_ROW ){
294 if( fossil_strcmp(db_column_text(&q,0),"ok")!=0 ){
295 fossil_fatal("file %s is not a well-formed Fossil patchfile", zIn);
296 }
297 }
298 db_finalize(&q);
299 }
300
301 /*
302 ** Show a summary of the content of a patch on standard output
303 */
patch_view(unsigned mFlags)304 void patch_view(unsigned mFlags){
305 Stmt q;
306 db_prepare(&q,
307 "WITH nmap(nkey,nm) AS (VALUES"
308 "('baseline','BASELINE'),"
309 "('project-name','PROJECT-NAME'))"
310 "SELECT nm, value FROM nmap, patch.cfg WHERE nkey=key;"
311 );
312 while( db_step(&q)==SQLITE_ROW ){
313 fossil_print("%-12s %s\n", db_column_text(&q,0), db_column_text(&q,1));
314 }
315 db_finalize(&q);
316 if( mFlags & PATCH_VERBOSE ){
317 db_prepare(&q,
318 "WITH nmap(nkey,nm,isDate) AS (VALUES"
319 "('project-code','PROJECT-CODE',0),"
320 "('date','TIMESTAMP',1),"
321 "('user','USER',0),"
322 "('hostname','HOSTNAME',0),"
323 "('ckout','CHECKOUT',0),"
324 "('repo','REPOSITORY',0))"
325 "SELECT nm, CASE WHEN isDate THEN datetime(value) ELSE value END"
326 " FROM nmap, patch.cfg WHERE nkey=key;"
327 );
328 while( db_step(&q)==SQLITE_ROW ){
329 fossil_print("%-12s %s\n", db_column_text(&q,0), db_column_text(&q,1));
330 }
331 db_finalize(&q);
332 }
333 if( db_table_exists("patch","patchmerge") ){
334 db_prepare(&q, "SELECT upper(type),mhash FROM patchmerge");
335 while( db_step(&q)==SQLITE_ROW ){
336 fossil_print("%-12s %s\n",
337 db_column_text(&q,0),
338 db_column_text(&q,1));
339 }
340 db_finalize(&q);
341 }
342 db_prepare(&q,
343 "SELECT pathname," /* 0: new name */
344 " hash IS NULL AND delta IS NOT NULL," /* 1: isNew */
345 " delta IS NULL," /* 2: isDeleted */
346 " origname" /* 3: old name or NULL */
347 " FROM patch.chng ORDER BY 1");
348 while( db_step(&q)==SQLITE_ROW ){
349 const char *zClass = "EDIT";
350 const char *zName = db_column_text(&q,0);
351 const char *zOrigName = db_column_text(&q, 3);
352 if( db_column_int(&q, 1) && zOrigName==0 ){
353 zClass = "NEW";
354 }else if( db_column_int(&q, 2) ){
355 zClass = zOrigName==0 ? "DELETE" : 0;
356 }
357 if( zOrigName!=0 && zOrigName[0]!=0 ){
358 fossil_print("%-12s %s -> %s\n", "RENAME",zOrigName,zName);
359 }
360 if( zClass ){
361 fossil_print("%-12s %s\n", zClass, zName);
362 }
363 }
364 db_finalize(&q);
365 }
366
367 /*
368 ** Apply the patch currently attached as database "patch".
369 **
370 ** First update the check-out to be at "baseline". Then loop through
371 ** and update all files.
372 */
patch_apply(unsigned mFlags)373 void patch_apply(unsigned mFlags){
374 Stmt q;
375 Blob cmd;
376
377 blob_init(&cmd, 0, 0);
378 if( unsaved_changes(0) ){
379 if( (mFlags & PATCH_FORCE)==0 ){
380 fossil_fatal("there are unsaved changes in the current checkout");
381 }else{
382 blob_appendf(&cmd, "%$ revert", g.nameOfExe);
383 if( mFlags & PATCH_DRYRUN ){
384 fossil_print("%s\n", blob_str(&cmd));
385 }else{
386 int rc = fossil_system(blob_str(&cmd));
387 if( rc ){
388 fossil_fatal("unable to revert preexisting changes: %s",
389 blob_str(&cmd));
390 }
391 }
392 blob_reset(&cmd);
393 }
394 }
395 file_chdir(g.zLocalRoot, 0);
396 db_prepare(&q,
397 "SELECT patch.cfg.value"
398 " FROM patch.cfg, localdb.vvar"
399 " WHERE patch.cfg.key='baseline'"
400 " AND localdb.vvar.name='checkout-hash'"
401 " AND patch.cfg.key<>localdb.vvar.name"
402 );
403 if( db_step(&q)==SQLITE_ROW ){
404 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
405 blob_appendf(&cmd, " update %s", db_column_text(&q, 0));
406 if( mFlags & PATCH_VERBOSE ){
407 fossil_print("%-10s %s\n", "BASELINE", db_column_text(&q,0));
408 }
409 }
410 db_finalize(&q);
411 if( blob_size(&cmd)>0 ){
412 if( mFlags & PATCH_DRYRUN ){
413 fossil_print("%s\n", blob_str(&cmd));
414 }else{
415 int rc = fossil_system(blob_str(&cmd));
416 if( rc ){
417 fossil_fatal("unable to update to the baseline check-out: %s",
418 blob_str(&cmd));
419 }
420 }
421 }
422 blob_reset(&cmd);
423 if( db_table_exists("patch","patchmerge") ){
424 db_prepare(&q,
425 "SELECT type, mhash, upper(type) FROM patch.patchmerge"
426 " WHERE type IN ('merge','cherrypick','backout','integrate')"
427 " AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
428 );
429 while( db_step(&q)==SQLITE_ROW ){
430 const char *zType = db_column_text(&q,0);
431 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
432 if( strcmp(zType,"merge")==0 ){
433 blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));
434 }else{
435 blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));
436 }
437 if( mFlags & PATCH_VERBOSE ){
438 fossil_print("%-10s %s\n", db_column_text(&q,2),
439 db_column_text(&q,0));
440 }
441 }
442 db_finalize(&q);
443 if( mFlags & PATCH_DRYRUN ){
444 fossil_print("%s", blob_str(&cmd));
445 }else{
446 int rc = fossil_unsafe_system(blob_str(&cmd));
447 if( rc ){
448 fossil_fatal("unable to do merges:\n%s",
449 blob_str(&cmd));
450 }
451 }
452 blob_reset(&cmd);
453 }
454
455 /* Deletions */
456 db_prepare(&q, "SELECT pathname FROM patch.chng"
457 " WHERE origname IS NULL AND delta IS NULL");
458 while( db_step(&q)==SQLITE_ROW ){
459 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
460 blob_appendf(&cmd, " rm --hard %$\n", db_column_text(&q,0));
461 if( mFlags & PATCH_VERBOSE ){
462 fossil_print("%-10s %s\n", "DELETE", db_column_text(&q,0));
463 }
464 }
465 db_finalize(&q);
466 if( blob_size(&cmd)>0 ){
467 if( mFlags & PATCH_DRYRUN ){
468 fossil_print("%s", blob_str(&cmd));
469 }else{
470 int rc = fossil_unsafe_system(blob_str(&cmd));
471 if( rc ){
472 fossil_fatal("unable to do merges:\n%s",
473 blob_str(&cmd));
474 }
475 }
476 blob_reset(&cmd);
477 }
478
479 /* Renames */
480 db_prepare(&q,
481 "SELECT origname, pathname FROM patch.chng"
482 " WHERE origname IS NOT NULL"
483 " AND origname<>pathname"
484 );
485 while( db_step(&q)==SQLITE_ROW ){
486 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
487 blob_appendf(&cmd, " mv --hard %$ %$\n",
488 db_column_text(&q,0), db_column_text(&q,1));
489 if( mFlags & PATCH_VERBOSE ){
490 fossil_print("%-10s %s -> %s\n", "RENAME",
491 db_column_text(&q,0), db_column_text(&q,1));
492 }
493 }
494 db_finalize(&q);
495 if( blob_size(&cmd)>0 ){
496 if( mFlags & PATCH_DRYRUN ){
497 fossil_print("%s", blob_str(&cmd));
498 }else{
499 int rc = fossil_unsafe_system(blob_str(&cmd));
500 if( rc ){
501 fossil_fatal("unable to rename files:\n%s",
502 blob_str(&cmd));
503 }
504 }
505 blob_reset(&cmd);
506 }
507
508 /* Edits and new files */
509 db_prepare(&q,
510 "SELECT pathname, hash, isexe, islink, delta FROM patch.chng"
511 " WHERE delta IS NOT NULL"
512 );
513 while( db_step(&q)==SQLITE_ROW ){
514 const char *zPathname = db_column_text(&q,0);
515 const char *zHash = db_column_text(&q,1);
516 int isExe = db_column_int(&q,2);
517 int isLink = db_column_int(&q,3);
518 Blob data;
519
520 blob_init(&data, 0, 0);
521 db_ephemeral_blob(&q, 4, &data);
522 if( blob_size(&data) ){
523 blob_uncompress(&data, &data);
524 }
525 if( blob_size(&data)==0 ){
526 /* No changes to the file */
527 }else if( zHash ){
528 Blob basis;
529 int rid = fast_uuid_to_rid(zHash);
530 int outSize, sz;
531 char *aOut;
532 if( rid==0 ){
533 fossil_fatal("cannot locate basis artifact %s for %s",
534 zHash, zPathname);
535 }
536 if( !content_get(rid, &basis) ){
537 fossil_fatal("cannot load basis artifact %d for %s", rid, zPathname);
538 }
539 outSize = delta_output_size(blob_buffer(&data),blob_size(&data));
540 if( outSize<=0 ){
541 fossil_fatal("malformed delta for %s", zPathname);
542 }
543 aOut = sqlite3_malloc64( outSize+1 );
544 if( aOut==0 ){
545 fossil_fatal("out of memory");
546 }
547 sz = delta_apply(blob_buffer(&basis), blob_size(&basis),
548 blob_buffer(&data), blob_size(&data), aOut);
549 if( sz<0 ){
550 fossil_fatal("malformed delta for %s", zPathname);
551 }
552 blob_reset(&basis);
553 blob_reset(&data);
554 blob_append(&data, aOut, sz);
555 sqlite3_free(aOut);
556 if( mFlags & PATCH_VERBOSE ){
557 fossil_print("%-10s %s\n", "EDIT", zPathname);
558 }
559 }else{
560 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
561 blob_appendf(&cmd, " add %$\n", zPathname);
562 if( mFlags & PATCH_VERBOSE ){
563 fossil_print("%-10s %s\n", "NEW", zPathname);
564 }
565 }
566 if( (mFlags & PATCH_DRYRUN)==0 ){
567 if( isLink ){
568 symlink_create(blob_str(&data), zPathname);
569 }else{
570 blob_write_to_file(&data, zPathname);
571 }
572 file_setexe(zPathname, isExe);
573 blob_reset(&data);
574 }
575 }
576 db_finalize(&q);
577 if( blob_size(&cmd)>0 ){
578 if( mFlags & PATCH_DRYRUN ){
579 fossil_print("%s", blob_str(&cmd));
580 }else{
581 int rc = fossil_unsafe_system(blob_str(&cmd));
582 if( rc ){
583 fossil_fatal("unable to add new files:\n%s",
584 blob_str(&cmd));
585 }
586 }
587 blob_reset(&cmd);
588 }
589 }
590
591 /*
592 ** This routine processes the
593 **
594 ** ... [--dir64 DIR64] [DIRECTORY] FILENAME
595 **
596 ** part of various "fossil patch" subcommands.
597 **
598 ** Find and return the filename of the patch file to be used by
599 ** "fossil patch apply" or "fossil patch create". Space to hold
600 ** the returned name is obtained from fossil_malloc() and should
601 ** be freed by the caller.
602 **
603 ** If the name is "-" return NULL. The caller will interpret this
604 ** to mean the patch is coming in over stdin or going out over
605 ** stdout.
606 **
607 ** If there is a prior DIRECTORY argument, or if
608 ** the --dir64 option is present, first chdir to the specified
609 ** directory, and adjust the path of FILENAME as appropriate so
610 ** that it still points to the same file.
611 **
612 ** The --dir64 option is undocumented. The argument to --dir64
613 ** is a base64-encoded directory name. The --dir64 option is used
614 ** to transmit the directory as part of the command argument to
615 ** a "ssh" command without having to worry about quoting
616 ** any special characters in the filename.
617 **
618 ** The returned name is obtained from fossil_malloc() and should
619 ** be freed by the caller.
620 */
patch_find_patch_filename(const char * zCmdName)621 static char *patch_find_patch_filename(const char *zCmdName){
622 const char *zDir64 = find_option("dir64",0,1);
623 const char *zDir = 0;
624 const char *zBaseName;
625 char *zToFree = 0;
626 char *zPatchFile = 0;
627 if( zDir64 ){
628 int n = 0;
629 zToFree = decode64(zDir64, &n);
630 zDir = zToFree;
631 }
632 verify_all_options();
633 if( g.argc!=4 && g.argc!=5 ){
634 usage(mprintf("%s [DIRECTORY] FILENAME", zCmdName));
635 }
636 if( g.argc==5 ){
637 zDir = g.argv[3];
638 zBaseName = g.argv[4];
639 }else{
640 zBaseName = g.argv[3];
641 }
642 if( fossil_strcmp(zBaseName, "-")==0 ){
643 zPatchFile = 0;
644 }else if( zDir ){
645 zPatchFile = file_canonical_name_dup(g.argv[4]);
646 }else{
647 zPatchFile = fossil_strdup(g.argv[3]);
648 }
649 if( zDir && file_chdir(zDir,0) ){
650 fossil_fatal("cannot change to directory \"%s\"", zDir);
651 }
652 fossil_free(zToFree);
653 return zPatchFile;
654 }
655
656 /*
657 ** Create a FILE* that will execute the remote side of a push or pull
658 ** using ssh (probably) or fossil for local pushes and pulls. Return
659 ** a FILE* obtained from popen() into which we write the patch, or from
660 ** which we read the patch, depending on whether this is a push or pull.
661 */
patch_remote_command(unsigned mFlags,const char * zThisCmd,const char * zRemoteCmd,const char * zFossilCmd,const char * zRW)662 static FILE *patch_remote_command(
663 unsigned mFlags, /* flags */
664 const char *zThisCmd, /* "push" or "pull" */
665 const char *zRemoteCmd, /* "apply" or "create" */
666 const char *zFossilCmd, /* Name of "fossil" on remote system */
667 const char *zRW /* "w" or "r" */
668 ){
669 char *zRemote;
670 char *zDir;
671 Blob cmd;
672 FILE *f;
673 Blob flgs;
674 char *zForce;
675
676 blob_init(&flgs, 0, 0);
677 if( mFlags & PATCH_FORCE ) blob_appendf(&flgs, " -f");
678 if( mFlags & PATCH_VERBOSE ) blob_appendf(&flgs, " -v");
679 if( mFlags & PATCH_DRYRUN ) blob_appendf(&flgs, " -n");
680 zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : "";
681 if( g.argc!=4 ){
682 usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd));
683 }
684 zRemote = fossil_strdup(g.argv[3]);
685 zDir = (char*)file_skip_userhost(zRemote);
686 if( zDir==0 ){
687 zDir = zRemote;
688 blob_init(&cmd, 0, 0);
689 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
690 blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir);
691 }else{
692 Blob remote;
693 *(char*)(zDir-1) = 0;
694 transport_ssh_command(&cmd);
695 blob_appendf(&cmd, " -T");
696 blob_append_escaped_arg(&cmd, zRemote, 0);
697 blob_init(&remote, 0, 0);
698 if( zFossilCmd==0 ) zFossilCmd = "fossil";
699 blob_appendf(&remote, "%$ patch %s%s --dir64 %z -",
700 zFossilCmd, zRemoteCmd, zForce, encode64(zDir, -1));
701 blob_append_escaped_arg(&cmd, blob_str(&remote), 0);
702 blob_reset(&remote);
703 }
704 if( mFlags & PATCH_VERBOSE ){
705 fossil_print("# %s\n", blob_str(&cmd));
706 fflush(stdout);
707 }
708 f = popen(blob_str(&cmd), zRW);
709 if( f==0 ){
710 fossil_fatal("cannot run command: %s", blob_str(&cmd));
711 }
712 blob_reset(&cmd);
713 blob_reset(&flgs);
714 return f;
715 }
716
717 /*
718 ** Show a diff for the patch currently loaded into database "patch".
719 */
patch_diff(unsigned mFlags,DiffConfig * pCfg)720 static void patch_diff(
721 unsigned mFlags, /* Patch flags. only -f is allowed */
722 DiffConfig *pCfg /* Diff options */
723 ){
724 int nErr = 0;
725 Stmt q;
726 int bWebpage = (pCfg->diffFlags & DIFF_WEBPAGE)!=0;
727 Blob empty;
728 blob_zero(&empty);
729
730 if( (mFlags & PATCH_FORCE)==0 ){
731 /* Check to ensure that the patch is against the repository that
732 ** we have opened.
733 **
734 ** To do: If there is a mismatch, should we scan all of the repositories
735 ** listed in the global_config table looking for a match?
736 */
737 if( db_exists(
738 "SELECT 1 FROM patch.cfg"
739 " WHERE cfg.key='baseline'"
740 " AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=cfg.value)"
741 )){
742 char *zBaseline;
743 db_prepare(&q,
744 "SELECT config.value, cfg.value FROM config, cfg"
745 " WHERE config.name='project-name'"
746 " AND cfg.key='project-name'"
747 " AND config.value<>cfg.value"
748 );
749 if( db_step(&q)==SQLITE_ROW ){
750 char *zRepo = fossil_strdup(db_column_text(&q,0));
751 char *zPatch = fossil_strdup(db_column_text(&q,1));
752 db_finalize(&q);
753 fossil_fatal("the patch is against project \"%z\" but you are using "
754 "project \"%z\"", zPatch, zRepo);
755 }
756 db_finalize(&q);
757 zBaseline = db_text(0, "SELECT value FROM patch.cfg"
758 " WHERE key='baseline'");
759 if( zBaseline ){
760 fossil_fatal("the baseline of the patch (check-in %S) is not found "
761 "in the %s repository", zBaseline, g.zRepositoryName);
762 }
763 }
764 }
765
766 diff_begin(pCfg);
767 db_prepare(&q,
768 "SELECT"
769 " (SELECT blob.rid FROM blob WHERE blob.uuid=chng.hash),"
770 " pathname," /* 1: new pathname */
771 " origname," /* 2: original pathname. Null if not renamed */
772 " delta," /* 3: delta. NULL if deleted. empty is no change */
773 " hash" /* 4: baseline hash */
774 " FROM patch.chng"
775 " ORDER BY pathname"
776 );
777 while( db_step(&q)==SQLITE_ROW ){
778 int rid;
779 const char *zName;
780 Blob a, b;
781
782 if( db_column_type(&q,0)!=SQLITE_INTEGER
783 && db_column_type(&q,4)==SQLITE_TEXT
784 ){
785 char *zUuid = fossil_strdup(db_column_text(&q,4));
786 char *zName = fossil_strdup(db_column_text(&q,1));
787 if( mFlags & PATCH_FORCE ){
788 fossil_print("ERROR cannot find base artifact %S for file \"%s\"\n",
789 zUuid, zName);
790 nErr++;
791 fossil_free(zUuid);
792 fossil_free(zName);
793 continue;
794 }else{
795 db_finalize(&q);
796 fossil_fatal("base artifact %S for file \"%s\" not found",
797 zUuid, zName);
798 }
799 }
800 zName = db_column_text(&q, 1);
801 rid = db_column_int(&q, 0);
802
803 if( db_column_type(&q,3)==SQLITE_NULL ){
804 if( !bWebpage ) fossil_print("DELETE %s\n", zName);
805 diff_print_index(zName, pCfg, 0);
806 content_get(rid, &a);
807 diff_file_mem(&a, &empty, zName, pCfg);
808 }else if( rid==0 ){
809 db_ephemeral_blob(&q, 3, &a);
810 blob_uncompress(&a, &a);
811 if( !bWebpage ) fossil_print("ADDED %s\n", zName);
812 diff_print_index(zName, pCfg, 0);
813 diff_file_mem(&empty, &a, zName, pCfg);
814 blob_reset(&a);
815 }else if( db_column_bytes(&q, 3)>0 ){
816 Blob delta;
817 db_ephemeral_blob(&q, 3, &delta);
818 blob_uncompress(&delta, &delta);
819 content_get(rid, &a);
820 blob_delta_apply(&a, &delta, &b);
821 diff_file_mem(&a, &b, zName, pCfg);
822 blob_reset(&a);
823 blob_reset(&b);
824 blob_reset(&delta);
825 }
826 }
827 db_finalize(&q);
828 diff_end(pCfg, nErr);
829 if( nErr ) fossil_fatal("abort due to prior errors");
830 }
831
832
833 /*
834 ** COMMAND: patch
835 **
836 ** Usage: %fossil patch SUBCOMMAND ?ARGS ..?
837 **
838 ** This command is used to create, view, and apply Fossil binary patches.
839 ** A Fossil binary patch is a single (binary) file that captures all of the
840 ** uncommitted changes of a check-out. Use Fossil binary patches to transfer
841 ** proposed or incomplete changes between machines for testing or analysis.
842 **
843 ** > fossil patch create [DIRECTORY] FILENAME
844 **
845 ** Create a new binary patch in FILENAME that captures all uncommitted
846 ** changes in the check-out at DIRECTORY, or the current directory if
847 ** DIRECTORY is omitted. If FILENAME is "-" then the binary patch
848 ** is written to standard output.
849 **
850 ** -f|--force Overwrite an existing patch with the same name.
851 **
852 ** > fossil patch apply [DIRECTORY] FILENAME
853 **
854 ** Apply the changes in FILENAME to the check-out at DIRECTORY, or
855 ** in the current directory if DIRECTORY is omitted. Options:
856 **
857 ** -f|--force Apply the patch even though there are unsaved
858 ** changes in the current check-out. Unsaved changes
859 ** are reverted and permanently lost.
860 ** -n|--dryrun Do nothing, but print what would have happened.
861 ** -v|--verbose Extra output explaining what happens.
862 **
863 ** > fossil patch diff [DIRECTORY] FILENAME
864 **
865 ** Show a human-readable diff for the patch. All the usual
866 ** diff flags described at "fossil help diff" apply. In addition:
867 **
868 ** -f|--force Continue trying to perform the diff even if
869 ** baseline information is missing from the current
870 ** repository
871 **
872 ** > fossil patch push REMOTE-CHECKOUT
873 **
874 ** Create a patch for the current check-out, transfer that patch to
875 ** a remote machine (using ssh) and apply the patch there. The
876 ** REMOTE-CHECKOUT is in one of the following formats:
877 **
878 ** * DIRECTORY
879 ** * HOST:DIRECTORY
880 ** * USER@HOST:DIRECTORY
881 **
882 ** Command-line options:
883 **
884 ** -f|--force Apply the patch even though there are unsaved
885 ** changes in the current check-out. Unsaved
886 ** changes will be reverted and then the patch is
887 ** applied.
888 ** --fossilcmd EXE Name of the "fossil" executable on the remote
889 ** -n|--dryrun Do nothing, but print what would have happened.
890 ** -v|--verbose Extra output explaining what happens.
891 **
892 **
893 ** > fossil patch pull REMOTE-CHECKOUT
894 **
895 ** Like "fossil patch push" except that the transfer is from remote
896 ** to local. All the same command-line options apply.
897 **
898 ** > fossil patch view FILENAME
899 **
900 ** View a summary of the changes in the binary patch FILENAME.
901 ** Use "fossil patch diff" for detailed patch content.
902 **
903 ** -v|--verbose Show extra detail about the patch.
904 **
905 */
patch_cmd(void)906 void patch_cmd(void){
907 const char *zCmd;
908 size_t n;
909 if( g.argc<3 ){
910 patch_usage:
911 usage("apply|create|diff|pull|push|view");
912 }
913 zCmd = g.argv[2];
914 n = strlen(zCmd);
915 if( strncmp(zCmd, "apply", n)==0 ){
916 char *zIn;
917 unsigned flags = 0;
918 if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN;
919 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
920 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
921 zIn = patch_find_patch_filename("apply");
922 db_must_be_within_tree();
923 patch_attach(zIn, stdin);
924 patch_apply(flags);
925 fossil_free(zIn);
926 }else
927 if( strncmp(zCmd, "create", n)==0 ){
928 char *zOut;
929 unsigned flags = 0;
930 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
931 zOut = patch_find_patch_filename("create");
932 verify_all_options();
933 db_must_be_within_tree();
934 patch_create(flags, zOut, stdout);
935 fossil_free(zOut);
936 }else
937 if( strncmp(zCmd, "diff", n)==0 ){
938 char *zIn;
939 unsigned flags = 0;
940 DiffConfig DCfg;
941
942 if( find_option("tk",0,0)!=0 ){
943 db_close(0);
944 diff_tk("patch diff", 3);
945 return;
946 }
947 diff_options(&DCfg, zCmd[0]=='g', 0);
948 db_find_and_open_repository(0, 0);
949 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
950 verify_all_options();
951 zIn = patch_find_patch_filename("apply");
952 patch_attach(zIn, stdin);
953 patch_diff(flags, &DCfg);
954 fossil_free(zIn);
955 }else
956 if( strncmp(zCmd, "pull", n)==0 ){
957 FILE *pIn = 0;
958 unsigned flags = 0;
959 const char *zFossilCmd = find_option("fossilcmd",0,1);
960 if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN;
961 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
962 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
963 db_must_be_within_tree();
964 verify_all_options();
965 pIn = patch_remote_command(flags & (~PATCH_FORCE),
966 "pull", "create", zFossilCmd, "r");
967 if( pIn ){
968 patch_attach(0, pIn);
969 pclose(pIn);
970 patch_apply(flags);
971 }
972 }else
973 if( strncmp(zCmd, "push", n)==0 ){
974 FILE *pOut = 0;
975 unsigned flags = 0;
976 const char *zFossilCmd = find_option("fossilcmd",0,1);
977 if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN;
978 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
979 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
980 db_must_be_within_tree();
981 verify_all_options();
982 pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w");
983 if( pOut ){
984 patch_create(0, 0, pOut);
985 pclose(pOut);
986 }
987 }else
988 if( strncmp(zCmd, "view", n)==0 ){
989 const char *zIn;
990 unsigned int flags = 0;
991 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
992 verify_all_options();
993 if( g.argc!=4 ){
994 usage("view FILENAME");
995 }
996 zIn = g.argv[3];
997 if( fossil_strcmp(zIn, "-")==0 ) zIn = 0;
998 patch_attach(zIn, stdin);
999 patch_view(flags);
1000 }else
1001 {
1002 goto patch_usage;
1003 }
1004 }
1005