|
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
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file contains code used to implement the "patch" 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
|
*/ |
|
28
|
char *fossil_hostname(void){ |
|
29
|
FILE *in; |
|
30
|
char zBuf[200]; |
|
31
|
in = popen("hostname","r"); |
|
32
|
if( in ){ |
|
33
|
int 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
|
#define PATCH_RETRY 0x0008 /* Second attempt */ |
|
51
|
|
|
52
|
/* |
|
53
|
** Implementation of the "readfile(X)" SQL function. The entire content |
|
54
|
** of the check-out file named X is read and returned as a BLOB. |
|
55
|
*/ |
|
56
|
static void readfileFunc( |
|
57
|
sqlite3_context *context, |
|
58
|
int argc, |
|
59
|
sqlite3_value **argv |
|
60
|
){ |
|
61
|
const char *zName; |
|
62
|
Blob x; |
|
63
|
sqlite3_int64 sz; |
|
64
|
(void)(argc); /* Unused parameter */ |
|
65
|
zName = (const char*)sqlite3_value_text(argv[0]); |
|
66
|
if( zName==0 || (zName[0]=='-' && zName[1]==0) ) return; |
|
67
|
sz = blob_read_from_file(&x, zName, RepoFILE); |
|
68
|
sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT); |
|
69
|
blob_reset(&x); |
|
70
|
} |
|
71
|
|
|
72
|
/* |
|
73
|
** mkdelta(X,Y) |
|
74
|
** |
|
75
|
** X is an numeric artifact id. Y is a filename. |
|
76
|
** |
|
77
|
** Compute a compressed delta that carries X into Y. Or return |
|
78
|
** and zero-length blob if X is equal to Y. |
|
79
|
*/ |
|
80
|
static void mkdeltaFunc( |
|
81
|
sqlite3_context *context, |
|
82
|
int argc, |
|
83
|
sqlite3_value **argv |
|
84
|
){ |
|
85
|
const char *zFile; |
|
86
|
Blob x, y, out; |
|
87
|
int rid; |
|
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
|
blob_init(&out, 0, 0); |
|
109
|
blob_resize(&out, sz+70); |
|
110
|
if( blob_size(&x)==blob_size(&y) |
|
111
|
&& memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 |
|
112
|
){ |
|
113
|
blob_reset(&y); |
|
114
|
blob_reset(&x); |
|
115
|
sqlite3_result_blob64(context, "", 0, SQLITE_STATIC); |
|
116
|
return; |
|
117
|
} |
|
118
|
nOut = delta_create(blob_buffer(&x),blob_size(&x), |
|
119
|
blob_buffer(&y),blob_size(&y), blob_buffer(&out)); |
|
120
|
blob_resize(&out, nOut); |
|
121
|
blob_reset(&x); |
|
122
|
blob_reset(&y); |
|
123
|
blob_compress(&out, &out); |
|
124
|
sqlite3_result_blob64(context, blob_buffer(&out), blob_size(&out), |
|
125
|
SQLITE_TRANSIENT); |
|
126
|
blob_reset(&out); |
|
127
|
} |
|
128
|
|
|
129
|
|
|
130
|
/* |
|
131
|
** Generate a binary patch file and store it into the file |
|
132
|
** named zOut. Or if zOut is NULL, write it into out. |
|
133
|
** |
|
134
|
** Return the number of errors. |
|
135
|
*/ |
|
136
|
void patch_create(unsigned mFlags, const char *zOut, FILE *out){ |
|
137
|
int vid; |
|
138
|
char *z; |
|
139
|
|
|
140
|
if( zOut && file_isdir(zOut, ExtFILE)!=0 ){ |
|
141
|
if( mFlags & PATCH_FORCE ){ |
|
142
|
file_delete(zOut); |
|
143
|
} |
|
144
|
if( file_isdir(zOut, ExtFILE)!=0 ){ |
|
145
|
fossil_fatal("patch file already exists: %s", zOut); |
|
146
|
} |
|
147
|
} |
|
148
|
add_content_sql_commands(g.db); |
|
149
|
deltafunc_init(g.db); |
|
150
|
sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0, |
|
151
|
readfileFunc, 0, 0); |
|
152
|
sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0, |
|
153
|
mkdeltaFunc, 0, 0); |
|
154
|
db_multi_exec("ATTACH %Q AS patch;", zOut ? zOut : ":memory:"); |
|
155
|
db_multi_exec( |
|
156
|
"PRAGMA patch.journal_mode=OFF;\n" |
|
157
|
"PRAGMA patch.page_size=512;\n" |
|
158
|
"CREATE TABLE patch.chng(\n" |
|
159
|
" pathname TEXT,\n" /* Filename */ |
|
160
|
" origname TEXT,\n" /* Name before rename. NULL if not renamed */ |
|
161
|
" hash TEXT,\n" /* Baseline hash. NULL for new files. */ |
|
162
|
" isexe BOOL,\n" /* True if executable */ |
|
163
|
" islink BOOL,\n" /* True if is a symbolic link */ |
|
164
|
" delta BLOB\n" /* compressed delta. NULL if deleted. |
|
165
|
** length 0 if unchanged */ |
|
166
|
");" |
|
167
|
"CREATE TABLE patch.cfg(\n" |
|
168
|
" key TEXT,\n" |
|
169
|
" value ANY\n" |
|
170
|
");" |
|
171
|
); |
|
172
|
vid = db_lget_int("checkout", 0); |
|
173
|
vfile_check_signature(vid, CKSIG_ENOTFILE); |
|
174
|
user_select(); |
|
175
|
db_multi_exec( |
|
176
|
"INSERT INTO patch.cfg(key,value)" |
|
177
|
"SELECT 'baseline',uuid FROM blob WHERE rid=%d " |
|
178
|
"UNION ALL" |
|
179
|
" SELECT 'ckout',rtrim(%Q,'/')" |
|
180
|
"UNION ALL" |
|
181
|
" SELECT 'repo',%Q " |
|
182
|
"UNION ALL" |
|
183
|
" SELECT 'user',%Q " |
|
184
|
"UNION ALL" |
|
185
|
" SELECT 'date',julianday('now')" |
|
186
|
"UNION ALL" |
|
187
|
" SELECT name,value FROM repository.config" |
|
188
|
" WHERE name IN ('project-code','project-name') " |
|
189
|
"UNION ALL" |
|
190
|
" SELECT 'fossil-date',julianday('" MANIFEST_DATE "')" |
|
191
|
";", vid, g.zLocalRoot, g.zRepositoryName, g.zLogin); |
|
192
|
z = fossil_hostname(); |
|
193
|
if( z ){ |
|
194
|
db_multi_exec( |
|
195
|
"INSERT INTO patch.cfg(key,value)VALUES('hostname',%Q)", z); |
|
196
|
fossil_free(z); |
|
197
|
} |
|
198
|
|
|
199
|
/* New files */ |
|
200
|
db_multi_exec( |
|
201
|
"INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
|
202
|
" SELECT pathname, NULL, isexe, islink," |
|
203
|
" compress(read_co_file(%Q||pathname))" |
|
204
|
" FROM vfile WHERE rid==0;", |
|
205
|
g.zLocalRoot |
|
206
|
); |
|
207
|
|
|
208
|
/* Deleted files */ |
|
209
|
db_multi_exec( |
|
210
|
"INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
|
211
|
" SELECT pathname, NULL, 0, 0, NULL" |
|
212
|
" FROM vfile WHERE deleted;" |
|
213
|
); |
|
214
|
|
|
215
|
/* Changed files */ |
|
216
|
db_multi_exec( |
|
217
|
"INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" |
|
218
|
" SELECT pathname, nullif(origname,pathname), blob.uuid, isexe, islink," |
|
219
|
" mkdelta(blob.rid, %Q||pathname)" |
|
220
|
" FROM vfile, blob" |
|
221
|
" WHERE blob.rid=vfile.rid" |
|
222
|
" AND NOT deleted AND (chnged OR origname<>pathname);", |
|
223
|
g.zLocalRoot |
|
224
|
); |
|
225
|
|
|
226
|
/* Merges */ |
|
227
|
if( db_exists("SELECT 1 FROM localdb.vmerge WHERE id<=0") ){ |
|
228
|
db_multi_exec( |
|
229
|
"CREATE TABLE patch.patchmerge(type TEXT,mhash TEXT);\n" |
|
230
|
"WITH tmap(id,type) AS (VALUES(0,'merge'),(-1,'cherrypick')," |
|
231
|
"(-2,'backout'),(-4,'integrate'))" |
|
232
|
"INSERT INTO patch.patchmerge(type,mhash)" |
|
233
|
" SELECT tmap.type,vmerge.mhash FROM vmerge, tmap" |
|
234
|
" WHERE tmap.id=vmerge.id;" |
|
235
|
); |
|
236
|
} |
|
237
|
|
|
238
|
/* Write the database to standard output if zOut==0 */ |
|
239
|
if( zOut==0 ){ |
|
240
|
sqlite3_int64 sz; |
|
241
|
unsigned char *pData; |
|
242
|
pData = sqlite3_serialize(g.db, "patch", &sz, 0); |
|
243
|
if( pData==0 ){ |
|
244
|
fossil_fatal("out of memory"); |
|
245
|
} |
|
246
|
#ifdef _WIN32 |
|
247
|
fflush(out); |
|
248
|
_setmode(_fileno(out), _O_BINARY); |
|
249
|
#endif |
|
250
|
fwrite(pData, 1, sz, out); |
|
251
|
fflush(out); |
|
252
|
sqlite3_free(pData); |
|
253
|
} |
|
254
|
db_multi_exec("DETACH patch;"); |
|
255
|
} |
|
256
|
|
|
257
|
/* |
|
258
|
** Attempt to load and validate a patchfile identified by the first |
|
259
|
** argument. |
|
260
|
*/ |
|
261
|
void patch_attach(const char *zIn, FILE *in, int bIgnoreEmptyPatch){ |
|
262
|
Stmt q; |
|
263
|
if( g.db==0 ){ |
|
264
|
sqlite3_open(":memory:", &g.db); |
|
265
|
} |
|
266
|
if( zIn==0 ){ |
|
267
|
Blob buf; |
|
268
|
int rc; |
|
269
|
int sz; |
|
270
|
unsigned char *pData; |
|
271
|
blob_init(&buf, 0, 0); |
|
272
|
#ifdef _WIN32 |
|
273
|
_setmode(_fileno(in), _O_BINARY); |
|
274
|
#endif |
|
275
|
sz = blob_read_from_channel(&buf, in, -1); |
|
276
|
pData = (unsigned char*)blob_buffer(&buf); |
|
277
|
if( sz<512 ){ |
|
278
|
blob_reset(&buf); |
|
279
|
if( bIgnoreEmptyPatch ) return; |
|
280
|
fossil_fatal("input is too small to be a patch file"); |
|
281
|
} |
|
282
|
db_multi_exec("ATTACH ':memory:' AS patch"); |
|
283
|
if( g.fSqlTrace ){ |
|
284
|
fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz); |
|
285
|
} |
|
286
|
rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0); |
|
287
|
if( rc ){ |
|
288
|
fossil_fatal("cannot open patch database: %s", sqlite3_errmsg(g.db)); |
|
289
|
} |
|
290
|
}else if( !file_isfile(zIn, ExtFILE) ){ |
|
291
|
fossil_fatal("no such file: %s", zIn); |
|
292
|
}else{ |
|
293
|
db_multi_exec("ATTACH %Q AS patch", zIn); |
|
294
|
} |
|
295
|
db_prepare(&q, "PRAGMA patch.quick_check"); |
|
296
|
while( db_step(&q)==SQLITE_ROW ){ |
|
297
|
if( fossil_strcmp(db_column_text(&q,0),"ok")!=0 ){ |
|
298
|
fossil_fatal("file %s is not a well-formed Fossil patchfile", zIn); |
|
299
|
} |
|
300
|
} |
|
301
|
db_finalize(&q); |
|
302
|
} |
|
303
|
|
|
304
|
/* |
|
305
|
** Show a summary of the content of a patch on standard output |
|
306
|
*/ |
|
307
|
void patch_view(unsigned mFlags){ |
|
308
|
Stmt q; |
|
309
|
db_prepare(&q, |
|
310
|
"WITH nmap(nkey,nm) AS (VALUES" |
|
311
|
"('baseline','BASELINE')," |
|
312
|
"('project-name','PROJECT-NAME'))" |
|
313
|
"SELECT nm, value FROM nmap, patch.cfg WHERE nkey=key;" |
|
314
|
); |
|
315
|
while( db_step(&q)==SQLITE_ROW ){ |
|
316
|
fossil_print("%-12s %s\n", db_column_text(&q,0), db_column_text(&q,1)); |
|
317
|
} |
|
318
|
db_finalize(&q); |
|
319
|
if( mFlags & PATCH_VERBOSE ){ |
|
320
|
db_prepare(&q, |
|
321
|
"WITH nmap(nkey,nm,isDate) AS (VALUES" |
|
322
|
"('project-code','PROJECT-CODE',0)," |
|
323
|
"('date','TIMESTAMP',1)," |
|
324
|
"('user','USER',0)," |
|
325
|
"('hostname','HOSTNAME',0)," |
|
326
|
"('ckout','CHECKOUT',0)," |
|
327
|
"('repo','REPOSITORY',0))" |
|
328
|
"SELECT nm, CASE WHEN isDate THEN datetime(value) ELSE value END" |
|
329
|
" FROM nmap, patch.cfg WHERE nkey=key;" |
|
330
|
); |
|
331
|
while( db_step(&q)==SQLITE_ROW ){ |
|
332
|
fossil_print("%-12s %s\n", db_column_text(&q,0), db_column_text(&q,1)); |
|
333
|
} |
|
334
|
db_finalize(&q); |
|
335
|
} |
|
336
|
if( db_table_exists("patch","patchmerge") ){ |
|
337
|
db_prepare(&q, "SELECT upper(type),mhash FROM patchmerge"); |
|
338
|
while( db_step(&q)==SQLITE_ROW ){ |
|
339
|
fossil_print("%-12s %s\n", |
|
340
|
db_column_text(&q,0), |
|
341
|
db_column_text(&q,1)); |
|
342
|
} |
|
343
|
db_finalize(&q); |
|
344
|
} |
|
345
|
db_prepare(&q, |
|
346
|
"SELECT pathname," /* 0: new name */ |
|
347
|
" hash IS NULL AND delta IS NOT NULL," /* 1: isNew */ |
|
348
|
" delta IS NULL," /* 2: isDeleted */ |
|
349
|
" origname" /* 3: old name or NULL */ |
|
350
|
" FROM patch.chng ORDER BY 1"); |
|
351
|
while( db_step(&q)==SQLITE_ROW ){ |
|
352
|
const char *zClass = "EDIT"; |
|
353
|
const char *zName = db_column_text(&q,0); |
|
354
|
const char *zOrigName = db_column_text(&q, 3); |
|
355
|
if( db_column_int(&q, 1) && zOrigName==0 ){ |
|
356
|
zClass = "NEW"; |
|
357
|
}else if( db_column_int(&q, 2) ){ |
|
358
|
zClass = zOrigName==0 ? "DELETE" : 0; |
|
359
|
} |
|
360
|
if( zOrigName!=0 && zOrigName[0]!=0 ){ |
|
361
|
fossil_print("%-12s %s -> %s\n", "RENAME",zOrigName,zName); |
|
362
|
} |
|
363
|
if( zClass ){ |
|
364
|
fossil_print("%-12s %s\n", zClass, zName); |
|
365
|
} |
|
366
|
} |
|
367
|
db_finalize(&q); |
|
368
|
} |
|
369
|
|
|
370
|
/* |
|
371
|
** Apply the patch currently attached as database "patch". |
|
372
|
** |
|
373
|
** First update the check-out to be at "baseline". Then loop through |
|
374
|
** and update all files. |
|
375
|
*/ |
|
376
|
void patch_apply(unsigned mFlags){ |
|
377
|
Stmt q; |
|
378
|
Blob cmd; |
|
379
|
|
|
380
|
blob_init(&cmd, 0, 0); |
|
381
|
if( unsaved_changes(0) ){ |
|
382
|
if( (mFlags & PATCH_FORCE)==0 ){ |
|
383
|
fossil_fatal("Cannot apply patch: there are unsaved changes " |
|
384
|
"in the current check-out"); |
|
385
|
}else{ |
|
386
|
blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe); |
|
387
|
if( mFlags & PATCH_DRYRUN ){ |
|
388
|
fossil_print("%s\n", blob_str(&cmd)); |
|
389
|
}else{ |
|
390
|
int rc = fossil_system(blob_str(&cmd)); |
|
391
|
if( rc ){ |
|
392
|
fossil_fatal("unable to revert preexisting changes: %s", |
|
393
|
blob_str(&cmd)); |
|
394
|
} |
|
395
|
} |
|
396
|
blob_reset(&cmd); |
|
397
|
} |
|
398
|
} |
|
399
|
file_chdir(g.zLocalRoot, 0); |
|
400
|
db_prepare(&q, |
|
401
|
"SELECT patch.cfg.value" |
|
402
|
" FROM patch.cfg, localdb.vvar" |
|
403
|
" WHERE patch.cfg.key='baseline'" |
|
404
|
" AND localdb.vvar.name='checkout-hash'" |
|
405
|
" AND patch.cfg.key<>localdb.vvar.name" |
|
406
|
); |
|
407
|
if( db_step(&q)==SQLITE_ROW ){ |
|
408
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
409
|
blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); |
|
410
|
if( mFlags & PATCH_VERBOSE ){ |
|
411
|
fossil_print("%-10s %s\n", "BASELINE", db_column_text(&q,0)); |
|
412
|
} |
|
413
|
} |
|
414
|
db_finalize(&q); |
|
415
|
if( blob_size(&cmd)>0 ){ |
|
416
|
if( mFlags & PATCH_DRYRUN ){ |
|
417
|
fossil_print("%s\n", blob_str(&cmd)); |
|
418
|
}else{ |
|
419
|
int rc = fossil_system(blob_str(&cmd)); |
|
420
|
if( rc ){ |
|
421
|
fossil_fatal("unable to update to the baseline check-out: %s", |
|
422
|
blob_str(&cmd)); |
|
423
|
} |
|
424
|
} |
|
425
|
} |
|
426
|
blob_reset(&cmd); |
|
427
|
if( db_table_exists("patch","patchmerge") ){ |
|
428
|
int nMerge = 0; |
|
429
|
db_prepare(&q, |
|
430
|
"SELECT type, mhash, upper(type) FROM patch.patchmerge" |
|
431
|
" WHERE type IN ('merge','cherrypick','backout','integrate')" |
|
432
|
" AND mhash NOT GLOB '*[^a-fA-F0-9]*';" |
|
433
|
); |
|
434
|
while( db_step(&q)==SQLITE_ROW ){ |
|
435
|
const char *zType = db_column_text(&q,0); |
|
436
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
437
|
if( strcmp(zType,"merge")==0 ){ |
|
438
|
blob_appendf(&cmd, " merge --noundo --nosync %s\n", |
|
439
|
db_column_text(&q,1)); |
|
440
|
}else{ |
|
441
|
blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n", |
|
442
|
zType, db_column_text(&q,1)); |
|
443
|
} |
|
444
|
nMerge++; |
|
445
|
if( mFlags & PATCH_VERBOSE ){ |
|
446
|
fossil_print("%-10s %s\n", db_column_text(&q,2), |
|
447
|
db_column_text(&q,0)); |
|
448
|
} |
|
449
|
} |
|
450
|
db_finalize(&q); |
|
451
|
if( mFlags & PATCH_DRYRUN ){ |
|
452
|
fossil_print("%s", blob_str(&cmd)); |
|
453
|
}else{ |
|
454
|
int rc = fossil_unsafe_system(blob_str(&cmd)); |
|
455
|
if( rc ){ |
|
456
|
fossil_fatal("unable to do merges:\n%s", |
|
457
|
blob_str(&cmd)); |
|
458
|
} |
|
459
|
} |
|
460
|
blob_reset(&cmd); |
|
461
|
|
|
462
|
/* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054 |
|
463
|
** If one or more merge operations occurred in the patch and there are |
|
464
|
** files that are marked as "chnged' in the local VFILE but which |
|
465
|
** are not mentioned as having been modified in the patch, then |
|
466
|
** revert those files. |
|
467
|
*/ |
|
468
|
if( nMerge ){ |
|
469
|
int vid = db_lget_int("checkout", 0); |
|
470
|
int nRevert = 0; |
|
471
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
472
|
blob_appendf(&cmd, " revert --noundo "); |
|
473
|
db_prepare(&q, |
|
474
|
"SELECT pathname FROM vfile WHERE vid=%d AND chnged " |
|
475
|
"EXCEPT SELECT pathname FROM chng", |
|
476
|
vid |
|
477
|
); |
|
478
|
while( db_step(&q)==SQLITE_ROW ){ |
|
479
|
blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1); |
|
480
|
nRevert++; |
|
481
|
} |
|
482
|
db_finalize(&q); |
|
483
|
if( nRevert ){ |
|
484
|
if( mFlags & PATCH_DRYRUN ){ |
|
485
|
fossil_print("%s", blob_str(&cmd)); |
|
486
|
}else{ |
|
487
|
int rc = fossil_unsafe_system(blob_str(&cmd)); |
|
488
|
if( rc ){ |
|
489
|
fossil_fatal("unable to do reverts:\n%s", |
|
490
|
blob_str(&cmd)); |
|
491
|
} |
|
492
|
} |
|
493
|
} |
|
494
|
blob_reset(&cmd); |
|
495
|
} |
|
496
|
} |
|
497
|
|
|
498
|
/* Deletions */ |
|
499
|
db_prepare(&q, "SELECT pathname FROM patch.chng" |
|
500
|
" WHERE origname IS NULL AND delta IS NULL"); |
|
501
|
while( db_step(&q)==SQLITE_ROW ){ |
|
502
|
if( blob_size(&cmd)==0 ){ |
|
503
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
504
|
blob_appendf(&cmd, " rm --hard"); |
|
505
|
} |
|
506
|
blob_appendf(&cmd, " %$", db_column_text(&q,0)); |
|
507
|
if( mFlags & PATCH_VERBOSE ){ |
|
508
|
fossil_print("%-10s %s\n", "DELETE", db_column_text(&q,0)); |
|
509
|
} |
|
510
|
} |
|
511
|
db_finalize(&q); |
|
512
|
if( blob_size(&cmd)>0 ){ |
|
513
|
blob_appendf(&cmd, "\n"); |
|
514
|
if( mFlags & PATCH_DRYRUN ){ |
|
515
|
fossil_print("%s", blob_str(&cmd)); |
|
516
|
}else{ |
|
517
|
int rc = fossil_unsafe_system(blob_str(&cmd)); |
|
518
|
if( rc ){ |
|
519
|
fossil_fatal("unable to do merges:\n%s", |
|
520
|
blob_str(&cmd)); |
|
521
|
} |
|
522
|
} |
|
523
|
blob_reset(&cmd); |
|
524
|
} |
|
525
|
|
|
526
|
/* Renames */ |
|
527
|
db_prepare(&q, |
|
528
|
"SELECT origname, pathname FROM patch.chng" |
|
529
|
" WHERE origname IS NOT NULL" |
|
530
|
" AND origname<>pathname" |
|
531
|
); |
|
532
|
while( db_step(&q)==SQLITE_ROW ){ |
|
533
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
534
|
blob_appendf(&cmd, " mv --hard %$ %$\n", |
|
535
|
db_column_text(&q,0), db_column_text(&q,1)); |
|
536
|
if( mFlags & PATCH_VERBOSE ){ |
|
537
|
fossil_print("%-10s %s -> %s\n", "RENAME", |
|
538
|
db_column_text(&q,0), db_column_text(&q,1)); |
|
539
|
} |
|
540
|
} |
|
541
|
db_finalize(&q); |
|
542
|
if( blob_size(&cmd)>0 ){ |
|
543
|
if( mFlags & PATCH_DRYRUN ){ |
|
544
|
fossil_print("%s", blob_str(&cmd)); |
|
545
|
}else{ |
|
546
|
int rc = fossil_unsafe_system(blob_str(&cmd)); |
|
547
|
if( rc ){ |
|
548
|
fossil_print("%-10s unable to rename files:\n%s", "WARNING!", |
|
549
|
blob_str(&cmd)); |
|
550
|
} |
|
551
|
} |
|
552
|
blob_reset(&cmd); |
|
553
|
} |
|
554
|
|
|
555
|
/* Edits and new files */ |
|
556
|
db_prepare(&q, |
|
557
|
"SELECT pathname, hash, isexe, islink, delta FROM patch.chng" |
|
558
|
" WHERE delta IS NOT NULL" |
|
559
|
); |
|
560
|
while( db_step(&q)==SQLITE_ROW ){ |
|
561
|
const char *zPathname = db_column_text(&q,0); |
|
562
|
const char *zHash = db_column_text(&q,1); |
|
563
|
int isExe = db_column_int(&q,2); |
|
564
|
int isLink = db_column_int(&q,3); |
|
565
|
Blob data; |
|
566
|
|
|
567
|
blob_init(&data, 0, 0); |
|
568
|
db_ephemeral_blob(&q, 4, &data); |
|
569
|
if( blob_size(&data) ){ |
|
570
|
blob_uncompress(&data, &data); |
|
571
|
} |
|
572
|
if( blob_size(&data)==0 ){ |
|
573
|
/* No changes to the file */ |
|
574
|
continue; |
|
575
|
}else if( zHash ){ |
|
576
|
Blob basis; |
|
577
|
int rid = fast_uuid_to_rid(zHash); |
|
578
|
int outSize, sz; |
|
579
|
char *aOut; |
|
580
|
if( rid==0 ){ |
|
581
|
fossil_fatal("cannot locate basis artifact %s for %s", |
|
582
|
zHash, zPathname); |
|
583
|
} |
|
584
|
if( !content_get(rid, &basis) ){ |
|
585
|
fossil_fatal("cannot load basis artifact %d for %s", rid, zPathname); |
|
586
|
} |
|
587
|
outSize = delta_output_size(blob_buffer(&data),blob_size(&data)); |
|
588
|
if( outSize<=0 ){ |
|
589
|
fossil_fatal("malformed delta for %s", zPathname); |
|
590
|
} |
|
591
|
aOut = sqlite3_malloc64( outSize+1 ); |
|
592
|
if( aOut==0 ){ |
|
593
|
fossil_fatal("out of memory"); |
|
594
|
} |
|
595
|
sz = delta_apply(blob_buffer(&basis), blob_size(&basis), |
|
596
|
blob_buffer(&data), blob_size(&data), aOut); |
|
597
|
if( sz<0 ){ |
|
598
|
fossil_fatal("malformed delta for %s", zPathname); |
|
599
|
} |
|
600
|
blob_reset(&basis); |
|
601
|
blob_reset(&data); |
|
602
|
blob_append(&data, aOut, sz); |
|
603
|
sqlite3_free(aOut); |
|
604
|
if( mFlags & PATCH_VERBOSE ){ |
|
605
|
fossil_print("%-10s %s\n", "EDIT", zPathname); |
|
606
|
} |
|
607
|
}else{ |
|
608
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
609
|
blob_appendf(&cmd, " add %$\n", zPathname); |
|
610
|
if( mFlags & PATCH_VERBOSE ){ |
|
611
|
fossil_print("%-10s %s\n", "NEW", zPathname); |
|
612
|
} |
|
613
|
} |
|
614
|
if( (mFlags & PATCH_DRYRUN)==0 ){ |
|
615
|
if( isLink ){ |
|
616
|
symlink_create(blob_str(&data), zPathname); |
|
617
|
}else{ |
|
618
|
blob_write_to_file(&data, zPathname); |
|
619
|
} |
|
620
|
file_setexe(zPathname, isExe); |
|
621
|
blob_reset(&data); |
|
622
|
} |
|
623
|
} |
|
624
|
db_finalize(&q); |
|
625
|
if( blob_size(&cmd)>0 ){ |
|
626
|
if( mFlags & PATCH_DRYRUN ){ |
|
627
|
fossil_print("%s", blob_str(&cmd)); |
|
628
|
}else{ |
|
629
|
int rc = fossil_unsafe_system(blob_str(&cmd)); |
|
630
|
if( rc ){ |
|
631
|
fossil_fatal("unable to add new files:\n%s", |
|
632
|
blob_str(&cmd)); |
|
633
|
} |
|
634
|
} |
|
635
|
blob_reset(&cmd); |
|
636
|
} |
|
637
|
} |
|
638
|
|
|
639
|
/* |
|
640
|
** This routine processes the |
|
641
|
** |
|
642
|
** ... [--dir64 DIR64] [DIRECTORY] FILENAME |
|
643
|
** |
|
644
|
** part of various "fossil patch" subcommands. |
|
645
|
** |
|
646
|
** Find and return the filename of the patch file to be used by |
|
647
|
** "fossil patch apply" or "fossil patch create". Space to hold |
|
648
|
** the returned name is obtained from fossil_malloc() and should |
|
649
|
** be freed by the caller. |
|
650
|
** |
|
651
|
** If the name is "-" return NULL. The caller will interpret this |
|
652
|
** to mean the patch is coming in over stdin or going out over |
|
653
|
** stdout. |
|
654
|
** |
|
655
|
** If there is a prior DIRECTORY argument, or if |
|
656
|
** the --dir64 option is present, first chdir to the specified |
|
657
|
** directory, and adjust the path of FILENAME as appropriate so |
|
658
|
** that it still points to the same file. |
|
659
|
** |
|
660
|
** The --dir64 option is undocumented. The argument to --dir64 |
|
661
|
** is a base64-encoded directory name. The --dir64 option is used |
|
662
|
** to transmit the directory as part of the command argument to |
|
663
|
** a "ssh" command without having to worry about quoting |
|
664
|
** any special characters in the filename. |
|
665
|
** |
|
666
|
** The returned name is obtained from fossil_malloc() and should |
|
667
|
** be freed by the caller. |
|
668
|
*/ |
|
669
|
static char *patch_find_patch_filename(const char *zCmdName){ |
|
670
|
const char *zDir64 = find_option("dir64",0,1); |
|
671
|
const char *zDir = 0; |
|
672
|
const char *zBaseName; |
|
673
|
char *zToFree = 0; |
|
674
|
char *zPatchFile = 0; |
|
675
|
if( zDir64 ){ |
|
676
|
int n = 0; |
|
677
|
zToFree = decode64(zDir64, &n); |
|
678
|
zDir = zToFree; |
|
679
|
} |
|
680
|
verify_all_options(); |
|
681
|
if( g.argc!=4 && g.argc!=5 ){ |
|
682
|
usage(mprintf("%s [DIRECTORY] FILENAME", zCmdName)); |
|
683
|
} |
|
684
|
if( g.argc==5 ){ |
|
685
|
zDir = g.argv[3]; |
|
686
|
zBaseName = g.argv[4]; |
|
687
|
}else{ |
|
688
|
zBaseName = g.argv[3]; |
|
689
|
} |
|
690
|
if( fossil_strcmp(zBaseName, "-")==0 ){ |
|
691
|
zPatchFile = 0; |
|
692
|
}else if( zDir ){ |
|
693
|
zPatchFile = file_canonical_name_dup(g.argv[4]); |
|
694
|
}else{ |
|
695
|
zPatchFile = fossil_strdup(g.argv[3]); |
|
696
|
} |
|
697
|
if( zDir && file_chdir(zDir,0) ){ |
|
698
|
fossil_fatal("cannot change to directory \"%s\"", zDir); |
|
699
|
} |
|
700
|
fossil_free(zToFree); |
|
701
|
return zPatchFile; |
|
702
|
} |
|
703
|
|
|
704
|
/* |
|
705
|
** Resolves a patch-command remote system name, accounting for patch |
|
706
|
** aliases. |
|
707
|
** |
|
708
|
** If a CONFIG table entry matching name='patch-alias:$zKey' is found, |
|
709
|
** the corresponding value is returned, else a fossil_strdup() of zKey |
|
710
|
** is returned. The caller is responsible for passing the resulting |
|
711
|
** string to fossil_free(). |
|
712
|
*/ |
|
713
|
static char *patch_resolve_remote(const char *zKey){ |
|
714
|
char *zAlias = db_text(0, "SELECT value FROM config " |
|
715
|
"WHERE name = 'patch-alias:%q'", |
|
716
|
zKey); |
|
717
|
return zAlias ? zAlias : fossil_strdup(zKey); |
|
718
|
} |
|
719
|
|
|
720
|
/* |
|
721
|
** Create a FILE* that will execute the remote side of a push or pull |
|
722
|
** using ssh (probably) or fossil for local pushes and pulls. Return |
|
723
|
** a FILE* obtained from popen() into which we write the patch, or from |
|
724
|
** which we read the patch, depending on whether this is a push or pull. |
|
725
|
*/ |
|
726
|
static FILE *patch_remote_command( |
|
727
|
unsigned mFlags, /* flags */ |
|
728
|
const char *zThisCmd, /* "push" or "pull" */ |
|
729
|
const char *zRemoteCmd, /* "apply" or "create" */ |
|
730
|
const char *zFossilCmd, /* Name of "fossil" on remote system */ |
|
731
|
const char *zRW /* "w" or "r" */ |
|
732
|
){ |
|
733
|
char *zRemote = 0; |
|
734
|
char *zDir = 0; |
|
735
|
Blob cmd; |
|
736
|
FILE *f = 0; |
|
737
|
Blob flgs; |
|
738
|
char *zForce = 0; |
|
739
|
int isRetry = (mFlags & PATCH_RETRY)!=0; |
|
740
|
|
|
741
|
blob_init(&flgs, 0, 0); |
|
742
|
blob_init(&cmd, 0, 0); |
|
743
|
if( mFlags & PATCH_FORCE ) blob_appendf(&flgs, " -f"); |
|
744
|
if( mFlags & PATCH_VERBOSE ) blob_appendf(&flgs, " -v"); |
|
745
|
if( mFlags & PATCH_DRYRUN ) blob_appendf(&flgs, " -n"); |
|
746
|
zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : ""; |
|
747
|
if( g.argc!=4 ){ |
|
748
|
usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd)); |
|
749
|
} |
|
750
|
zRemote = patch_resolve_remote(g.argv[3]); |
|
751
|
zDir = (char*)file_skip_userhost(zRemote); |
|
752
|
if( zDir==0 ){ |
|
753
|
if( isRetry ) goto remote_command_error; |
|
754
|
zDir = zRemote; |
|
755
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
756
|
blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir); |
|
757
|
}else{ |
|
758
|
Blob remote; |
|
759
|
*(char*)(zDir-1) = 0; |
|
760
|
transport_ssh_command(&cmd); |
|
761
|
blob_appendf(&cmd, " -T"); |
|
762
|
blob_append_escaped_arg(&cmd, zRemote, 0); |
|
763
|
blob_init(&remote, 0, 0); |
|
764
|
if( zFossilCmd==0 ){ |
|
765
|
if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){ |
|
766
|
ssh_add_path_argument(&cmd); |
|
767
|
} |
|
768
|
zFossilCmd = "fossil"; |
|
769
|
}else if( mFlags & PATCH_RETRY ){ |
|
770
|
goto remote_command_error; |
|
771
|
} |
|
772
|
blob_appendf(&remote, "%$ patch %s%s --dir64 %z -", |
|
773
|
zFossilCmd, zRemoteCmd, zForce, encode64(zDir, -1)); |
|
774
|
blob_append_escaped_arg(&cmd, blob_str(&remote), 0); |
|
775
|
blob_reset(&remote); |
|
776
|
} |
|
777
|
if( isRetry ){ |
|
778
|
fossil_print("First attempt to run \"fossil\" on %s failed\n" |
|
779
|
"Retry: ", zRemote); |
|
780
|
} |
|
781
|
fossil_print("%s\n", blob_str(&cmd)); |
|
782
|
fflush(stdout); |
|
783
|
f = popen(blob_str(&cmd), zRW); |
|
784
|
if( f==0 ){ |
|
785
|
fossil_fatal("cannot run command: %s", blob_str(&cmd)); |
|
786
|
} |
|
787
|
remote_command_error: |
|
788
|
fossil_free(zRemote); |
|
789
|
blob_reset(&cmd); |
|
790
|
blob_reset(&flgs); |
|
791
|
return f; |
|
792
|
} |
|
793
|
|
|
794
|
/* |
|
795
|
** Toggle the use-path-for-ssh setting for the remote host defined |
|
796
|
** by g.argv[3]. |
|
797
|
*/ |
|
798
|
static void patch_toggle_ssh_needs_path(void){ |
|
799
|
char *zRemote = patch_resolve_remote(g.argv[3]); |
|
800
|
char *zDir = (char*)file_skip_userhost(zRemote); |
|
801
|
if( zDir ){ |
|
802
|
*(char*)(zDir - 1) = 0; |
|
803
|
ssh_needs_path_argument(zRemote, 99); |
|
804
|
} |
|
805
|
fossil_free(zRemote); |
|
806
|
} |
|
807
|
|
|
808
|
/* |
|
809
|
** Show a diff for the patch currently loaded into database "patch". |
|
810
|
*/ |
|
811
|
static void patch_diff( |
|
812
|
unsigned mFlags, /* Patch flags. only -f is allowed */ |
|
813
|
DiffConfig *pCfg /* Diff options */ |
|
814
|
){ |
|
815
|
int nErr = 0; |
|
816
|
Stmt q; |
|
817
|
int bWebpage = (pCfg->diffFlags & DIFF_WEBPAGE)!=0; |
|
818
|
Blob empty; |
|
819
|
blob_zero(&empty); |
|
820
|
|
|
821
|
if( (mFlags & PATCH_FORCE)==0 ){ |
|
822
|
/* Check to ensure that the patch is against the repository that |
|
823
|
** we have opened. |
|
824
|
** |
|
825
|
** To do: If there is a mismatch, should we scan all of the repositories |
|
826
|
** listed in the global_config table looking for a match? |
|
827
|
*/ |
|
828
|
if( db_exists( |
|
829
|
"SELECT 1 FROM patch.cfg" |
|
830
|
" WHERE cfg.key='baseline'" |
|
831
|
" AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=cfg.value)" |
|
832
|
)){ |
|
833
|
char *zBaseline; |
|
834
|
db_prepare(&q, |
|
835
|
"SELECT config.value, cfg.value FROM config, cfg" |
|
836
|
" WHERE config.name='project-name'" |
|
837
|
" AND cfg.key='project-name'" |
|
838
|
" AND config.value<>cfg.value" |
|
839
|
); |
|
840
|
if( db_step(&q)==SQLITE_ROW ){ |
|
841
|
char *zRepo = fossil_strdup(db_column_text(&q,0)); |
|
842
|
char *zPatch = fossil_strdup(db_column_text(&q,1)); |
|
843
|
db_finalize(&q); |
|
844
|
fossil_fatal("the patch is against project \"%z\" but you are using " |
|
845
|
"project \"%z\"", zPatch, zRepo); |
|
846
|
} |
|
847
|
db_finalize(&q); |
|
848
|
zBaseline = db_text(0, "SELECT value FROM patch.cfg" |
|
849
|
" WHERE key='baseline'"); |
|
850
|
if( zBaseline ){ |
|
851
|
fossil_fatal("the baseline of the patch (check-in %S) is not found " |
|
852
|
"in the %s repository", zBaseline, g.zRepositoryName); |
|
853
|
} |
|
854
|
} |
|
855
|
} |
|
856
|
|
|
857
|
diff_begin(pCfg); |
|
858
|
db_prepare(&q, |
|
859
|
"SELECT" |
|
860
|
" (SELECT blob.rid FROM blob WHERE blob.uuid=chng.hash)," |
|
861
|
" pathname," /* 1: new pathname */ |
|
862
|
" origname," /* 2: original pathname. Null if not renamed */ |
|
863
|
" delta," /* 3: delta. NULL if deleted. empty is no change */ |
|
864
|
" hash" /* 4: baseline hash */ |
|
865
|
" FROM patch.chng" |
|
866
|
" ORDER BY pathname" |
|
867
|
); |
|
868
|
while( db_step(&q)==SQLITE_ROW ){ |
|
869
|
int rid; |
|
870
|
const char *zName; |
|
871
|
Blob a, b; |
|
872
|
|
|
873
|
if( db_column_type(&q,0)!=SQLITE_INTEGER |
|
874
|
&& db_column_type(&q,4)==SQLITE_TEXT |
|
875
|
){ |
|
876
|
char *zUuid = fossil_strdup(db_column_text(&q,4)); |
|
877
|
char *zName = fossil_strdup(db_column_text(&q,1)); |
|
878
|
if( mFlags & PATCH_FORCE ){ |
|
879
|
fossil_print("ERROR cannot find base artifact %S for file \"%s\"\n", |
|
880
|
zUuid, zName); |
|
881
|
nErr++; |
|
882
|
fossil_free(zUuid); |
|
883
|
fossil_free(zName); |
|
884
|
continue; |
|
885
|
}else{ |
|
886
|
db_finalize(&q); |
|
887
|
fossil_fatal("base artifact %S for file \"%s\" not found", |
|
888
|
zUuid, zName); |
|
889
|
} |
|
890
|
} |
|
891
|
zName = db_column_text(&q, 1); |
|
892
|
rid = db_column_int(&q, 0); |
|
893
|
|
|
894
|
pCfg->diffFlags &= (~DIFF_FILE_MASK); |
|
895
|
if( db_column_type(&q,3)==SQLITE_NULL ){ |
|
896
|
if( !bWebpage ) fossil_print("DELETE %s\n", zName); |
|
897
|
pCfg->diffFlags |= DIFF_FILE_DELETED; |
|
898
|
diff_print_index(zName, pCfg, 0); |
|
899
|
content_get(rid, &a); |
|
900
|
diff_file_mem(&a, &empty, zName, pCfg); |
|
901
|
}else if( rid==0 ){ |
|
902
|
db_ephemeral_blob(&q, 3, &a); |
|
903
|
blob_uncompress(&a, &a); |
|
904
|
if( !bWebpage ) fossil_print("ADDED %s\n", zName); |
|
905
|
pCfg->diffFlags |= DIFF_FILE_ADDED; |
|
906
|
diff_print_index(zName, pCfg, 0); |
|
907
|
diff_file_mem(&empty, &a, zName, pCfg); |
|
908
|
blob_reset(&a); |
|
909
|
}else if( db_column_bytes(&q, 3)>0 ){ |
|
910
|
Blob delta; |
|
911
|
db_ephemeral_blob(&q, 3, &delta); |
|
912
|
blob_uncompress(&delta, &delta); |
|
913
|
content_get(rid, &a); |
|
914
|
blob_delta_apply(&a, &delta, &b); |
|
915
|
diff_file_mem(&a, &b, zName, pCfg); |
|
916
|
blob_reset(&a); |
|
917
|
blob_reset(&b); |
|
918
|
blob_reset(&delta); |
|
919
|
} |
|
920
|
} |
|
921
|
db_finalize(&q); |
|
922
|
diff_end(pCfg, nErr); |
|
923
|
if( nErr ) fossil_fatal("abort due to prior errors"); |
|
924
|
} |
|
925
|
|
|
926
|
/* |
|
927
|
** COMMAND: patch |
|
928
|
** |
|
929
|
** Usage: %fossil patch SUBCOMMAND ?ARGS ..? |
|
930
|
** |
|
931
|
** This command is used to create, view, and apply Fossil binary patches. |
|
932
|
** A Fossil binary patch is a single (binary) file that captures all of the |
|
933
|
** uncommitted changes of a check-out. Use Fossil binary patches to transfer |
|
934
|
** proposed or incomplete changes between machines for testing or analysis. |
|
935
|
** |
|
936
|
** > fossil patch alias add|rm|ls|list ?ARGS? |
|
937
|
** |
|
938
|
** Manage remote-name aliases, which act as short-form |
|
939
|
** equivalents to REMOTE-CHECKOUT strings. Aliases are local to |
|
940
|
** a given repository and do not sync. Subcommands: |
|
941
|
** |
|
942
|
** ... add ALIAS REMOTE-CHECKOUT Add ALIAS as an alias |
|
943
|
** for REMOTE-CHECKOUT. |
|
944
|
** ... ls|list List all local aliases. |
|
945
|
** ... rm ALIAS [ALIAS...] Remove named aliases |
|
946
|
** ... rm --all Remove all aliases |
|
947
|
** |
|
948
|
** > fossil patch create [DIRECTORY] PATCHFILE |
|
949
|
** |
|
950
|
** Create a new binary patch in PATCHFILE that captures all uncommitted |
|
951
|
** changes in the check-out at DIRECTORY, or the current directory if |
|
952
|
** DIRECTORY is omitted. If PATCHFILE is "-" then the binary patch |
|
953
|
** is written to standard output. |
|
954
|
** |
|
955
|
** Options: |
|
956
|
** -f|--force Overwrite an existing patch with the same name |
|
957
|
** |
|
958
|
** > fossil patch apply [DIRECTORY] PATCHFILE |
|
959
|
** |
|
960
|
** Apply the changes in PATCHFILE to the check-out at DIRECTORY, or |
|
961
|
** in the current directory if DIRECTORY is omitted. |
|
962
|
** |
|
963
|
** Options: |
|
964
|
** -f|--force Apply the patch even though there are unsaved |
|
965
|
** changes in the current check-out. Unsaved changes |
|
966
|
** are reverted and permanently lost. |
|
967
|
** -n|--dry-run Do nothing, but print what would have happened |
|
968
|
** -v|--verbose Extra output explaining what happens |
|
969
|
** |
|
970
|
** > fossil patch diff [DIRECTORY] PATCHFILE |
|
971
|
** > fossil patch gdiff [DIRECTORY] PATCHFILE |
|
972
|
** |
|
973
|
** Show a human-readable diff for the patch in PATCHFILE and associated |
|
974
|
** with the repository checked out in DIRECTORY. The current |
|
975
|
** directory is used if DIRECTORY is omitted. All the usual |
|
976
|
** diff flags described at "fossil help diff" apply. With gdiff, |
|
977
|
** gdiff-command is used instead of internal diff logic. In addition: |
|
978
|
** |
|
979
|
** -f|--force Continue trying to perform the diff even if |
|
980
|
** baseline information is missing from the current |
|
981
|
** repository |
|
982
|
** |
|
983
|
** > fossil patch push REMOTE-CHECKOUT |
|
984
|
** |
|
985
|
** Create a patch for the current check-out, transfer that patch to |
|
986
|
** a remote machine (using ssh) and apply the patch there. The |
|
987
|
** REMOTE-CHECKOUT is in one of the following formats: |
|
988
|
** |
|
989
|
** * DIRECTORY |
|
990
|
** * HOST:DIRECTORY |
|
991
|
** * USER@HOST:DIRECTORY |
|
992
|
** |
|
993
|
** The name of the fossil executable on the remote host is specified |
|
994
|
** by the --fossilcmd option, or if there is no --fossilcmd, it first |
|
995
|
** tries "$HOME/bin/fossil" and if not found there it searches for any |
|
996
|
** executable named "fossil" on the default $PATH set by SSH on the |
|
997
|
** remote. |
|
998
|
** |
|
999
|
** Command-line options: |
|
1000
|
** |
|
1001
|
** -f|--force Apply the patch even though there are unsaved |
|
1002
|
** changes in the current check-out. Unsaved |
|
1003
|
** changes will be reverted and then the patch is |
|
1004
|
** applied. |
|
1005
|
** --fossilcmd EXE Name of the "fossil" executable on the remote |
|
1006
|
** -n|--dry-run Do nothing, but print what would have happened |
|
1007
|
** -v|--verbose Extra output explaining what happens |
|
1008
|
** |
|
1009
|
** |
|
1010
|
** > fossil patch pull REMOTE-CHECKOUT |
|
1011
|
** |
|
1012
|
** Like "fossil patch push" except that the transfer is from remote |
|
1013
|
** to local. All the same command-line options apply. |
|
1014
|
** |
|
1015
|
** > fossil patch view PATCHFILE |
|
1016
|
** |
|
1017
|
** View a summary of the changes in the binary patch in PATCHFILE. |
|
1018
|
** Use "fossil patch diff" for detailed patch content. |
|
1019
|
** |
|
1020
|
** -v|--verbose Show extra detail about the patch |
|
1021
|
** |
|
1022
|
*/ |
|
1023
|
void patch_cmd(void){ |
|
1024
|
const char *zCmd; |
|
1025
|
size_t n; |
|
1026
|
if( g.argc<3 ){ |
|
1027
|
patch_usage: |
|
1028
|
usage("alias|apply|create|diff|gdiff|pull|push|view"); |
|
1029
|
} |
|
1030
|
zCmd = g.argv[2]; |
|
1031
|
n = strlen(zCmd); |
|
1032
|
if( strncmp(zCmd, "alias", n)==0 ){ |
|
1033
|
const char * zArg = g.argc>3 ? g.argv[3] : 0; |
|
1034
|
db_must_be_within_tree(); |
|
1035
|
if( 0==zArg ){ |
|
1036
|
goto usage_patch_alias; |
|
1037
|
}else if( 0==strcmp("ls",zArg) || 0==strcmp("list",zArg) ){ |
|
1038
|
/* alias ls|list */ |
|
1039
|
Stmt q; |
|
1040
|
int nAlias = 0; |
|
1041
|
|
|
1042
|
verify_all_options(); |
|
1043
|
db_prepare(&q, "SELECT substr(name,13), value FROM config " |
|
1044
|
"WHERE name GLOB 'patch-alias:*' ORDER BY name"); |
|
1045
|
while( SQLITE_ROW==db_step(&q) ){ |
|
1046
|
const char *zName = db_column_text(&q, 0); |
|
1047
|
const char *zVal = db_column_text(&q, 1); |
|
1048
|
++nAlias; |
|
1049
|
fossil_print("%s = %s\n", zName, zVal); |
|
1050
|
} |
|
1051
|
db_finalize(&q); |
|
1052
|
if( 0==nAlias ){ |
|
1053
|
fossil_print("No patch aliases defined\n"); |
|
1054
|
} |
|
1055
|
}else if( 0==strcmp("add", zArg) ){ |
|
1056
|
/* alias add localName remote */ |
|
1057
|
verify_all_options(); |
|
1058
|
if( 6!=g.argc ){ |
|
1059
|
usage("alias add localName remote"); |
|
1060
|
} |
|
1061
|
db_unprotect(PROTECT_CONFIG); |
|
1062
|
db_multi_exec("REPLACE INTO config (name, value, mtime) " |
|
1063
|
"VALUES ('patch-alias:%q', %Q, unixepoch())", |
|
1064
|
g.argv[4], g.argv[5]); |
|
1065
|
db_protect_pop(); |
|
1066
|
}else if( 0==strcmp("rm", zArg) ){ |
|
1067
|
/* alias rm */ |
|
1068
|
const int fAll = 0!=find_option("all", 0, 0); |
|
1069
|
if( fAll ? g.argc<4 : g.argc<5 ){ |
|
1070
|
usage("alias rm [-all] [aliasGlob [...aliasGlobN]]"); |
|
1071
|
} |
|
1072
|
verify_all_options(); |
|
1073
|
db_unprotect(PROTECT_CONFIG); |
|
1074
|
if( 0!=fAll ){ |
|
1075
|
db_multi_exec("DELETE FROM config WHERE name GLOB 'patch-alias:*'"); |
|
1076
|
}else{ |
|
1077
|
Stmt q; |
|
1078
|
int i; |
|
1079
|
db_prepare(&q, "DELETE FROM config WHERE name " |
|
1080
|
"GLOB 'patch-alias:' || :pattern"); |
|
1081
|
for(i = 4; i < g.argc; ++i){ |
|
1082
|
db_bind_text(&q, ":pattern", g.argv[i]); |
|
1083
|
db_step(&q); |
|
1084
|
db_reset(&q); |
|
1085
|
} |
|
1086
|
db_finalize(&q); |
|
1087
|
} |
|
1088
|
db_protect_pop(); |
|
1089
|
}else{ |
|
1090
|
usage_patch_alias: |
|
1091
|
usage("alias ls|list|add|rm ..."); |
|
1092
|
} |
|
1093
|
}else |
|
1094
|
if( strncmp(zCmd, "apply", n)==0 ){ |
|
1095
|
char *zIn; |
|
1096
|
unsigned flags = 0; |
|
1097
|
if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN; |
|
1098
|
if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; |
|
1099
|
if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
|
1100
|
zIn = patch_find_patch_filename("apply"); |
|
1101
|
db_must_be_within_tree(); |
|
1102
|
patch_attach(zIn, stdin, 0); |
|
1103
|
patch_apply(flags); |
|
1104
|
fossil_free(zIn); |
|
1105
|
}else |
|
1106
|
if( strncmp(zCmd, "create", n)==0 ){ |
|
1107
|
char *zOut; |
|
1108
|
unsigned flags = 0; |
|
1109
|
if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
|
1110
|
zOut = patch_find_patch_filename("create"); |
|
1111
|
verify_all_options(); |
|
1112
|
db_must_be_within_tree(); |
|
1113
|
patch_create(flags, zOut, stdout); |
|
1114
|
fossil_free(zOut); |
|
1115
|
}else |
|
1116
|
if( (strncmp(zCmd, "diff", n)==0) || (strncmp(zCmd, "gdiff", n)==0) ){ |
|
1117
|
char *zIn; |
|
1118
|
unsigned flags = 0; |
|
1119
|
DiffConfig DCfg; |
|
1120
|
|
|
1121
|
if( find_option("tk",0,0)!=0 ){ |
|
1122
|
db_close(0); |
|
1123
|
diff_tk("patch diff", 3); |
|
1124
|
return; |
|
1125
|
} |
|
1126
|
db_find_and_open_repository(0, 0); |
|
1127
|
if( gdiff_using_tk(zCmd[0]=='g') ){ |
|
1128
|
diff_tk("patch diff", 3); |
|
1129
|
return; |
|
1130
|
} |
|
1131
|
if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
|
1132
|
diff_options(&DCfg, zCmd[0]=='g', 0); |
|
1133
|
verify_all_options(); |
|
1134
|
zIn = patch_find_patch_filename("diff"); |
|
1135
|
patch_attach(zIn, stdin, 0); |
|
1136
|
patch_diff(flags, &DCfg); |
|
1137
|
fossil_free(zIn); |
|
1138
|
}else |
|
1139
|
if( strncmp(zCmd, "pull", n)==0 ){ |
|
1140
|
FILE *pIn = 0; |
|
1141
|
unsigned flags = 0; |
|
1142
|
const char *zFossilCmd = find_option("fossilcmd",0,1); |
|
1143
|
if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN; |
|
1144
|
if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; |
|
1145
|
if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
|
1146
|
db_must_be_within_tree(); |
|
1147
|
verify_all_options(); |
|
1148
|
pIn = patch_remote_command(flags & (~PATCH_FORCE), |
|
1149
|
"pull", "create", zFossilCmd, "r"); |
|
1150
|
if( pIn ){ |
|
1151
|
patch_attach(0, pIn, 1); |
|
1152
|
if( pclose(pIn) ){ |
|
1153
|
flags |= PATCH_RETRY; |
|
1154
|
pIn = patch_remote_command(flags & (~PATCH_FORCE), |
|
1155
|
"pull", "create", zFossilCmd, "r"); |
|
1156
|
if( pIn ){ |
|
1157
|
patch_attach(0, pIn, 0); |
|
1158
|
if( pclose(pIn)==0 ){ |
|
1159
|
patch_toggle_ssh_needs_path(); |
|
1160
|
} |
|
1161
|
} |
|
1162
|
} |
|
1163
|
patch_apply(flags); |
|
1164
|
} |
|
1165
|
}else |
|
1166
|
if( strncmp(zCmd, "push", n)==0 ){ |
|
1167
|
FILE *pOut = 0; |
|
1168
|
unsigned flags = 0; |
|
1169
|
const char *zFossilCmd = find_option("fossilcmd",0,1); |
|
1170
|
if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN; |
|
1171
|
if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; |
|
1172
|
if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
|
1173
|
db_must_be_within_tree(); |
|
1174
|
verify_all_options(); |
|
1175
|
pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w"); |
|
1176
|
if( pOut ){ |
|
1177
|
patch_create(0, 0, pOut); |
|
1178
|
if( pclose(pOut)!=0 ){ |
|
1179
|
flags |= PATCH_RETRY; |
|
1180
|
pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w"); |
|
1181
|
if( pOut ){ |
|
1182
|
patch_create(0, 0, pOut); |
|
1183
|
if( pclose(pOut)==0 ){ |
|
1184
|
patch_toggle_ssh_needs_path(); |
|
1185
|
} |
|
1186
|
} |
|
1187
|
} |
|
1188
|
} |
|
1189
|
}else |
|
1190
|
if( strncmp(zCmd, "view", n)==0 ){ |
|
1191
|
const char *zIn; |
|
1192
|
unsigned int flags = 0; |
|
1193
|
if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; |
|
1194
|
verify_all_options(); |
|
1195
|
if( g.argc!=4 ){ |
|
1196
|
usage("view FILENAME"); |
|
1197
|
} |
|
1198
|
zIn = g.argv[3]; |
|
1199
|
if( fossil_strcmp(zIn, "-")==0 ) zIn = 0; |
|
1200
|
patch_attach(zIn, stdin, 0); |
|
1201
|
patch_view(flags); |
|
1202
|
}else |
|
1203
|
{ |
|
1204
|
goto patch_usage; |
|
1205
|
} |
|
1206
|
} |
|
1207
|
|