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