Fossil SCM
First attempt to get "fossil patch apply" working. There are probably still bugs. Certainly much more testing is needed.
Commit
5865739195db36684e6a7b8d01e8879e9803b1581ca734155a71e4bd5e07f0e2
Parent
03dca8fca9bb629…
1 file changed
+189
-9
+189
-9
| --- src/patch.c | ||
| +++ src/patch.c | ||
| @@ -19,10 +19,17 @@ | ||
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include "patch.h" |
| 22 | 22 | #include <assert.h> |
| 23 | 23 | |
| 24 | +/* | |
| 25 | +** Flags passed from the main patch_cmd() routine into subfunctions used | |
| 26 | +** to implement the various subcommands. | |
| 27 | +*/ | |
| 28 | +#define PATCH_DRYRUN 0x0001 | |
| 29 | +#define PATCH_VERBOSE 0x0002 | |
| 30 | + | |
| 24 | 31 | /* |
| 25 | 32 | ** Implementation of the "readfile(X)" SQL function. The entire content |
| 26 | 33 | ** of the checkout file named X is read and returned as a BLOB. |
| 27 | 34 | */ |
| 28 | 35 | static void readfileFunc( |
| @@ -152,16 +159,18 @@ | ||
| 152 | 159 | " SELECT pathname, NULL, isexe, islink," |
| 153 | 160 | " compress(read_co_file(%Q||pathname))" |
| 154 | 161 | " FROM vfile WHERE rid==0;", |
| 155 | 162 | g.zLocalRoot |
| 156 | 163 | ); |
| 164 | + | |
| 157 | 165 | /* Deleted files */ |
| 158 | 166 | db_multi_exec( |
| 159 | 167 | "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
| 160 | 168 | " SELECT pathname, NULL, 0, 0, NULL" |
| 161 | 169 | " FROM vfile WHERE deleted;" |
| 162 | 170 | ); |
| 171 | + | |
| 163 | 172 | /* Changed files */ |
| 164 | 173 | db_multi_exec( |
| 165 | 174 | "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" |
| 166 | 175 | " SELECT pathname, nullif(origname,pathname), blob.uuid, isexe, islink," |
| 167 | 176 | " mkdelta(blob.rid, %Q||pathname)" |
| @@ -169,10 +178,11 @@ | ||
| 169 | 178 | " WHERE blob.rid=vfile.rid" |
| 170 | 179 | " AND NOT deleted AND (chnged OR origname<>pathname);", |
| 171 | 180 | g.zLocalRoot |
| 172 | 181 | ); |
| 173 | 182 | |
| 183 | + /* Merges */ | |
| 174 | 184 | if( db_exists("SELECT 1 FROM localdb.vmerge WHERE id<=0") ){ |
| 175 | 185 | db_multi_exec( |
| 176 | 186 | "CREATE TABLE patch.patchmerge(type TEXT,mhash TEXT);\n" |
| 177 | 187 | "WITH tmap(id,type) AS (VALUES(0,'merge'),(-1,'cherrypick')," |
| 178 | 188 | "(-2,'backout'),(-4,'integrate'))" |
| @@ -226,12 +236,12 @@ | ||
| 226 | 236 | } |
| 227 | 237 | db_finalize(&q); |
| 228 | 238 | } |
| 229 | 239 | db_prepare(&q, |
| 230 | 240 | "SELECT pathname," |
| 231 | - " hash IS NULL AND delta IS NOT NULL," | |
| 232 | - " delta IS NULL," | |
| 241 | + " hash IS NULL AND delta IS NOT NULL," /* isNew */ | |
| 242 | + " delta IS NULL," /* delete if origname NULL */ | |
| 233 | 243 | " origname" |
| 234 | 244 | " FROM patch.chng ORDER BY 1"); |
| 235 | 245 | while( db_step(&q)==SQLITE_ROW ){ |
| 236 | 246 | const char *zClass = "EDIT"; |
| 237 | 247 | const char *zName = db_column_text(&q,0); |
| @@ -255,14 +265,15 @@ | ||
| 255 | 265 | ** Apply the patch currently attached as database "patch". |
| 256 | 266 | ** |
| 257 | 267 | ** First update the check-out to be at "baseline". Then loop through |
| 258 | 268 | ** and update all files. |
| 259 | 269 | */ |
| 260 | -void patch_apply(void){ | |
| 270 | +void patch_apply(unsigned mFlags){ | |
| 261 | 271 | Stmt q; |
| 262 | 272 | Blob cmd; |
| 263 | 273 | blob_init(&cmd, 0, 0); |
| 274 | + file_chdir(g.zLocalRoot, 0); | |
| 264 | 275 | db_prepare(&q, |
| 265 | 276 | "SELECT patch.cfg.value" |
| 266 | 277 | " FROM patch.cfg, localdb.vvar" |
| 267 | 278 | " WHERE patch.cfg.key='baseline'" |
| 268 | 279 | " AND localdb.vvar.name='checkout-hash'" |
| @@ -269,21 +280,185 @@ | ||
| 269 | 280 | " AND patch.cfg.key<>localdb.vvar.name" |
| 270 | 281 | ); |
| 271 | 282 | if( db_step(&q)==SQLITE_ROW ){ |
| 272 | 283 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 273 | 284 | blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); |
| 285 | + if( mFlags & PATCH_VERBOSE ){ | |
| 286 | + fossil_print("%-10s %s\n", "BASELINE", db_column_text(&q,0)); | |
| 287 | + } | |
| 274 | 288 | } |
| 275 | 289 | db_finalize(&q); |
| 276 | 290 | if( blob_size(&cmd)>0 ){ |
| 277 | - int rc = fossil_system(blob_str(&cmd)); | |
| 278 | - if( rc ){ | |
| 279 | - fossil_fatal("unable to update to the baseline check-out: %s", | |
| 280 | - blob_str(&cmd)); | |
| 291 | + if( mFlags & PATCH_DRYRUN ){ | |
| 292 | + fossil_print("%s\n", blob_str(&cmd)); | |
| 293 | + }else{ | |
| 294 | + int rc = fossil_system(blob_str(&cmd)); | |
| 295 | + if( rc ){ | |
| 296 | + fossil_fatal("unable to update to the baseline check-out: %s", | |
| 297 | + blob_str(&cmd)); | |
| 298 | + } | |
| 281 | 299 | } |
| 282 | 300 | } |
| 283 | 301 | blob_reset(&cmd); |
| 284 | - | |
| 302 | + if( db_table_exists("patch","patchmerge") ){ | |
| 303 | + db_prepare(&q, | |
| 304 | + "SELECT type, mhash, upper(type) FROM patch.patchmerge" | |
| 305 | + " WHERE type IN ('merge','cherrypick','backout','integrate')" | |
| 306 | + " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" | |
| 307 | + ); | |
| 308 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 309 | + blob_append_escaped_arg(&cmd, g.nameOfExe); | |
| 310 | + blob_appendf(&cmd, " --%s %s\n", db_column_text(&q,0), | |
| 311 | + db_column_text(&q,1)); | |
| 312 | + if( mFlags & PATCH_VERBOSE ){ | |
| 313 | + fossil_print("%-10s %s\n", db_column_text(&q,2), | |
| 314 | + db_column_text(&q,0)); | |
| 315 | + } | |
| 316 | + } | |
| 317 | + db_finalize(&q); | |
| 318 | + if( mFlags & PATCH_DRYRUN ){ | |
| 319 | + fossil_print("%s", blob_str(&cmd)); | |
| 320 | + }else{ | |
| 321 | + int rc = fossil_system(blob_str(&cmd)); | |
| 322 | + if( rc ){ | |
| 323 | + fossil_fatal("unable to do merges:\n%s", | |
| 324 | + blob_str(&cmd)); | |
| 325 | + } | |
| 326 | + } | |
| 327 | + blob_reset(&cmd); | |
| 328 | + } | |
| 329 | + | |
| 330 | + /* Deletions */ | |
| 331 | + db_prepare(&q, "SELECT pathname FROM patch.chng" | |
| 332 | + " WHERE origname IS NULL AND delta IS NULL"); | |
| 333 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 334 | + blob_append_escaped_arg(&cmd, g.nameOfExe); | |
| 335 | + blob_appendf(&cmd, " rm --hard %$\n", db_column_text(&q,0)); | |
| 336 | + if( mFlags & PATCH_VERBOSE ){ | |
| 337 | + fossil_print("%-10s %s\n", "DELETE", db_column_text(&q,0)); | |
| 338 | + } | |
| 339 | + } | |
| 340 | + db_finalize(&q); | |
| 341 | + if( blob_size(&cmd)>0 ){ | |
| 342 | + if( mFlags & PATCH_DRYRUN ){ | |
| 343 | + fossil_print("%s", blob_str(&cmd)); | |
| 344 | + }else{ | |
| 345 | + int rc = fossil_system(blob_str(&cmd)); | |
| 346 | + if( rc ){ | |
| 347 | + fossil_fatal("unable to do merges:\n%s", | |
| 348 | + blob_str(&cmd)); | |
| 349 | + } | |
| 350 | + } | |
| 351 | + blob_reset(&cmd); | |
| 352 | + } | |
| 353 | + | |
| 354 | + /* Renames */ | |
| 355 | + db_prepare(&q, | |
| 356 | + "SELECT origname, pathname FROM patch.chng" | |
| 357 | + " WHERE origname IS NOT NULL" | |
| 358 | + " AND origname<>pathname" | |
| 359 | + ); | |
| 360 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 361 | + blob_append_escaped_arg(&cmd, g.nameOfExe); | |
| 362 | + blob_appendf(&cmd, " mv --hard %$ %$\n", | |
| 363 | + db_column_text(&q,0), db_column_text(&q,1)); | |
| 364 | + if( mFlags & PATCH_VERBOSE ){ | |
| 365 | + fossil_print("%-10s %s -> %s\n", "RENAME", | |
| 366 | + db_column_text(&q,0), db_column_text(&q,1)); | |
| 367 | + } | |
| 368 | + } | |
| 369 | + db_finalize(&q); | |
| 370 | + if( blob_size(&cmd)>0 ){ | |
| 371 | + if( mFlags & PATCH_DRYRUN ){ | |
| 372 | + fossil_print("%s", blob_str(&cmd)); | |
| 373 | + }else{ | |
| 374 | + int rc = fossil_system(blob_str(&cmd)); | |
| 375 | + if( rc ){ | |
| 376 | + fossil_fatal("unable to rename files:\n%s", | |
| 377 | + blob_str(&cmd)); | |
| 378 | + } | |
| 379 | + } | |
| 380 | + blob_reset(&cmd); | |
| 381 | + } | |
| 382 | + | |
| 383 | + /* Edits and new files */ | |
| 384 | + db_prepare(&q, | |
| 385 | + "SELECT pathname, hash, isexe, islink, delta FROM patch.chng" | |
| 386 | + " WHERE delta IS NOT NULL" | |
| 387 | + ); | |
| 388 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 389 | + const char *zPathname = db_column_text(&q,0); | |
| 390 | + const char *zHash = db_column_text(&q,1); | |
| 391 | + int isExe = db_column_int(&q,2); | |
| 392 | + int isLink = db_column_int(&q,3); | |
| 393 | + Blob data; | |
| 394 | + | |
| 395 | + blob_init(&data, 0, 0); | |
| 396 | + db_column_blob(&q, 4, &data); | |
| 397 | + blob_uncompress(&data, &data); | |
| 398 | + if( zHash ){ | |
| 399 | + Blob basis; | |
| 400 | + int rid = fast_uuid_to_rid(zHash); | |
| 401 | + int outSize, sz; | |
| 402 | + char *aOut; | |
| 403 | + if( rid==0 ){ | |
| 404 | + fossil_fatal("cannot locate basis artifact %s for %s", | |
| 405 | + zHash, zPathname); | |
| 406 | + } | |
| 407 | + if( !content_get(rid, &basis) ){ | |
| 408 | + fossil_fatal("cannot load basis artifact %d for %s", rid, zPathname); | |
| 409 | + } | |
| 410 | + outSize = delta_output_size(blob_buffer(&data),blob_size(&data)); | |
| 411 | + if( outSize<=0 ){ | |
| 412 | + fossil_fatal("malformed delta for %s", zPathname); | |
| 413 | + } | |
| 414 | + aOut = sqlite3_malloc64( outSize+1 ); | |
| 415 | + if( aOut==0 ){ | |
| 416 | + fossil_fatal("out of memory"); | |
| 417 | + } | |
| 418 | + sz = delta_apply(blob_buffer(&basis), blob_size(&basis), | |
| 419 | + blob_buffer(&data), blob_size(&data), aOut); | |
| 420 | + if( sz<0 ){ | |
| 421 | + fossil_fatal("malformed delta for %s", zPathname); | |
| 422 | + } | |
| 423 | + blob_reset(&basis); | |
| 424 | + blob_reset(&data); | |
| 425 | + blob_append(&data, aOut, sz); | |
| 426 | + sqlite3_free(aOut); | |
| 427 | + if( mFlags & PATCH_VERBOSE ){ | |
| 428 | + fossil_print("%-10s %s\n", "EDIT", zPathname); | |
| 429 | + } | |
| 430 | + }else{ | |
| 431 | + blob_append_escaped_arg(&cmd, g.nameOfExe); | |
| 432 | + blob_appendf(&cmd, " add %$\n", zPathname); | |
| 433 | + if( mFlags & PATCH_VERBOSE ){ | |
| 434 | + fossil_print("%-10s %s\n", "NEW", zPathname); | |
| 435 | + } | |
| 436 | + } | |
| 437 | + if( (mFlags & PATCH_DRYRUN)==0 ){ | |
| 438 | + if( isLink ){ | |
| 439 | + symlink_create(blob_str(&data), zPathname); | |
| 440 | + }else{ | |
| 441 | + blob_write_to_file(&data, zPathname); | |
| 442 | + } | |
| 443 | + file_setexe(zPathname, isExe); | |
| 444 | + blob_reset(&data); | |
| 445 | + } | |
| 446 | + } | |
| 447 | + db_finalize(&q); | |
| 448 | + if( blob_size(&cmd)>0 ){ | |
| 449 | + if( mFlags & PATCH_DRYRUN ){ | |
| 450 | + fossil_print("%s", blob_str(&cmd)); | |
| 451 | + }else{ | |
| 452 | + int rc = fossil_system(blob_str(&cmd)); | |
| 453 | + if( rc ){ | |
| 454 | + fossil_fatal("unable to add new files:\n%s", | |
| 455 | + blob_str(&cmd)); | |
| 456 | + } | |
| 457 | + } | |
| 458 | + blob_reset(&cmd); | |
| 459 | + } | |
| 285 | 460 | } |
| 286 | 461 | |
| 287 | 462 | |
| 288 | 463 | /* |
| 289 | 464 | ** COMMAND: patch |
| @@ -304,10 +479,12 @@ | ||
| 304 | 479 | ** |
| 305 | 480 | ** Apply the changes in FILENAME to the current check-out. Options: |
| 306 | 481 | ** |
| 307 | 482 | ** -f|--force Apply the patch even though there are unsaved |
| 308 | 483 | ** changes in the current check-out. |
| 484 | +** -n|--dryrun Do nothing, but print what would have happened. | |
| 485 | +** -v|--verbose Extra output explaining what happens. | |
| 309 | 486 | ** |
| 310 | 487 | ** > fossil patch diff [DIFF-FLAGS] FILENAME |
| 311 | 488 | ** |
| 312 | 489 | ** View the changes specified by the binary patch FILENAME in a |
| 313 | 490 | ** human-readable format. The usual diff flags apply. |
| @@ -336,20 +513,23 @@ | ||
| 336 | 513 | } |
| 337 | 514 | zCmd = g.argv[2]; |
| 338 | 515 | n = strlen(zCmd); |
| 339 | 516 | if( strncmp(zCmd, "apply", n)==0 ){ |
| 340 | 517 | int forceFlag = find_option("force","f",0)!=0; |
| 518 | + unsigned flags = 0; | |
| 519 | + if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN; | |
| 520 | + if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; | |
| 341 | 521 | db_must_be_within_tree(); |
| 342 | 522 | verify_all_options(); |
| 343 | 523 | if( g.argc!=4 ){ |
| 344 | 524 | usage("apply FILENAME"); |
| 345 | 525 | } |
| 346 | 526 | if( !forceFlag && unsaved_changes(0) ){ |
| 347 | 527 | fossil_fatal("there are unsaved changes in the current checkout"); |
| 348 | 528 | } |
| 349 | 529 | patch_attach(g.argv[3]); |
| 350 | - patch_apply(); | |
| 530 | + patch_apply(flags); | |
| 351 | 531 | }else |
| 352 | 532 | if( strncmp(zCmd, "create", n)==0 ){ |
| 353 | 533 | db_must_be_within_tree(); |
| 354 | 534 | verify_all_options(); |
| 355 | 535 | if( g.argc!=4 ){ |
| 356 | 536 |
| --- src/patch.c | |
| +++ src/patch.c | |
| @@ -19,10 +19,17 @@ | |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include "patch.h" |
| 22 | #include <assert.h> |
| 23 | |
| 24 | /* |
| 25 | ** Implementation of the "readfile(X)" SQL function. The entire content |
| 26 | ** of the checkout file named X is read and returned as a BLOB. |
| 27 | */ |
| 28 | static void readfileFunc( |
| @@ -152,16 +159,18 @@ | |
| 152 | " SELECT pathname, NULL, isexe, islink," |
| 153 | " compress(read_co_file(%Q||pathname))" |
| 154 | " FROM vfile WHERE rid==0;", |
| 155 | g.zLocalRoot |
| 156 | ); |
| 157 | /* Deleted files */ |
| 158 | db_multi_exec( |
| 159 | "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
| 160 | " SELECT pathname, NULL, 0, 0, NULL" |
| 161 | " FROM vfile WHERE deleted;" |
| 162 | ); |
| 163 | /* Changed files */ |
| 164 | db_multi_exec( |
| 165 | "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" |
| 166 | " SELECT pathname, nullif(origname,pathname), blob.uuid, isexe, islink," |
| 167 | " mkdelta(blob.rid, %Q||pathname)" |
| @@ -169,10 +178,11 @@ | |
| 169 | " WHERE blob.rid=vfile.rid" |
| 170 | " AND NOT deleted AND (chnged OR origname<>pathname);", |
| 171 | g.zLocalRoot |
| 172 | ); |
| 173 | |
| 174 | if( db_exists("SELECT 1 FROM localdb.vmerge WHERE id<=0") ){ |
| 175 | db_multi_exec( |
| 176 | "CREATE TABLE patch.patchmerge(type TEXT,mhash TEXT);\n" |
| 177 | "WITH tmap(id,type) AS (VALUES(0,'merge'),(-1,'cherrypick')," |
| 178 | "(-2,'backout'),(-4,'integrate'))" |
| @@ -226,12 +236,12 @@ | |
| 226 | } |
| 227 | db_finalize(&q); |
| 228 | } |
| 229 | db_prepare(&q, |
| 230 | "SELECT pathname," |
| 231 | " hash IS NULL AND delta IS NOT NULL," |
| 232 | " delta IS NULL," |
| 233 | " origname" |
| 234 | " FROM patch.chng ORDER BY 1"); |
| 235 | while( db_step(&q)==SQLITE_ROW ){ |
| 236 | const char *zClass = "EDIT"; |
| 237 | const char *zName = db_column_text(&q,0); |
| @@ -255,14 +265,15 @@ | |
| 255 | ** Apply the patch currently attached as database "patch". |
| 256 | ** |
| 257 | ** First update the check-out to be at "baseline". Then loop through |
| 258 | ** and update all files. |
| 259 | */ |
| 260 | void patch_apply(void){ |
| 261 | Stmt q; |
| 262 | Blob cmd; |
| 263 | blob_init(&cmd, 0, 0); |
| 264 | db_prepare(&q, |
| 265 | "SELECT patch.cfg.value" |
| 266 | " FROM patch.cfg, localdb.vvar" |
| 267 | " WHERE patch.cfg.key='baseline'" |
| 268 | " AND localdb.vvar.name='checkout-hash'" |
| @@ -269,21 +280,185 @@ | |
| 269 | " AND patch.cfg.key<>localdb.vvar.name" |
| 270 | ); |
| 271 | if( db_step(&q)==SQLITE_ROW ){ |
| 272 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 273 | blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); |
| 274 | } |
| 275 | db_finalize(&q); |
| 276 | if( blob_size(&cmd)>0 ){ |
| 277 | int rc = fossil_system(blob_str(&cmd)); |
| 278 | if( rc ){ |
| 279 | fossil_fatal("unable to update to the baseline check-out: %s", |
| 280 | blob_str(&cmd)); |
| 281 | } |
| 282 | } |
| 283 | blob_reset(&cmd); |
| 284 | |
| 285 | } |
| 286 | |
| 287 | |
| 288 | /* |
| 289 | ** COMMAND: patch |
| @@ -304,10 +479,12 @@ | |
| 304 | ** |
| 305 | ** Apply the changes in FILENAME to the current check-out. Options: |
| 306 | ** |
| 307 | ** -f|--force Apply the patch even though there are unsaved |
| 308 | ** changes in the current check-out. |
| 309 | ** |
| 310 | ** > fossil patch diff [DIFF-FLAGS] FILENAME |
| 311 | ** |
| 312 | ** View the changes specified by the binary patch FILENAME in a |
| 313 | ** human-readable format. The usual diff flags apply. |
| @@ -336,20 +513,23 @@ | |
| 336 | } |
| 337 | zCmd = g.argv[2]; |
| 338 | n = strlen(zCmd); |
| 339 | if( strncmp(zCmd, "apply", n)==0 ){ |
| 340 | int forceFlag = find_option("force","f",0)!=0; |
| 341 | db_must_be_within_tree(); |
| 342 | verify_all_options(); |
| 343 | if( g.argc!=4 ){ |
| 344 | usage("apply FILENAME"); |
| 345 | } |
| 346 | if( !forceFlag && unsaved_changes(0) ){ |
| 347 | fossil_fatal("there are unsaved changes in the current checkout"); |
| 348 | } |
| 349 | patch_attach(g.argv[3]); |
| 350 | patch_apply(); |
| 351 | }else |
| 352 | if( strncmp(zCmd, "create", n)==0 ){ |
| 353 | db_must_be_within_tree(); |
| 354 | verify_all_options(); |
| 355 | if( g.argc!=4 ){ |
| 356 |
| --- src/patch.c | |
| +++ src/patch.c | |
| @@ -19,10 +19,17 @@ | |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include "patch.h" |
| 22 | #include <assert.h> |
| 23 | |
| 24 | /* |
| 25 | ** Flags passed from the main patch_cmd() routine into subfunctions used |
| 26 | ** to implement the various subcommands. |
| 27 | */ |
| 28 | #define PATCH_DRYRUN 0x0001 |
| 29 | #define PATCH_VERBOSE 0x0002 |
| 30 | |
| 31 | /* |
| 32 | ** Implementation of the "readfile(X)" SQL function. The entire content |
| 33 | ** of the checkout file named X is read and returned as a BLOB. |
| 34 | */ |
| 35 | static void readfileFunc( |
| @@ -152,16 +159,18 @@ | |
| 159 | " SELECT pathname, NULL, isexe, islink," |
| 160 | " compress(read_co_file(%Q||pathname))" |
| 161 | " FROM vfile WHERE rid==0;", |
| 162 | g.zLocalRoot |
| 163 | ); |
| 164 | |
| 165 | /* Deleted files */ |
| 166 | db_multi_exec( |
| 167 | "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
| 168 | " SELECT pathname, NULL, 0, 0, NULL" |
| 169 | " FROM vfile WHERE deleted;" |
| 170 | ); |
| 171 | |
| 172 | /* Changed files */ |
| 173 | db_multi_exec( |
| 174 | "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" |
| 175 | " SELECT pathname, nullif(origname,pathname), blob.uuid, isexe, islink," |
| 176 | " mkdelta(blob.rid, %Q||pathname)" |
| @@ -169,10 +178,11 @@ | |
| 178 | " WHERE blob.rid=vfile.rid" |
| 179 | " AND NOT deleted AND (chnged OR origname<>pathname);", |
| 180 | g.zLocalRoot |
| 181 | ); |
| 182 | |
| 183 | /* Merges */ |
| 184 | if( db_exists("SELECT 1 FROM localdb.vmerge WHERE id<=0") ){ |
| 185 | db_multi_exec( |
| 186 | "CREATE TABLE patch.patchmerge(type TEXT,mhash TEXT);\n" |
| 187 | "WITH tmap(id,type) AS (VALUES(0,'merge'),(-1,'cherrypick')," |
| 188 | "(-2,'backout'),(-4,'integrate'))" |
| @@ -226,12 +236,12 @@ | |
| 236 | } |
| 237 | db_finalize(&q); |
| 238 | } |
| 239 | db_prepare(&q, |
| 240 | "SELECT pathname," |
| 241 | " hash IS NULL AND delta IS NOT NULL," /* isNew */ |
| 242 | " delta IS NULL," /* delete if origname NULL */ |
| 243 | " origname" |
| 244 | " FROM patch.chng ORDER BY 1"); |
| 245 | while( db_step(&q)==SQLITE_ROW ){ |
| 246 | const char *zClass = "EDIT"; |
| 247 | const char *zName = db_column_text(&q,0); |
| @@ -255,14 +265,15 @@ | |
| 265 | ** Apply the patch currently attached as database "patch". |
| 266 | ** |
| 267 | ** First update the check-out to be at "baseline". Then loop through |
| 268 | ** and update all files. |
| 269 | */ |
| 270 | void patch_apply(unsigned mFlags){ |
| 271 | Stmt q; |
| 272 | Blob cmd; |
| 273 | blob_init(&cmd, 0, 0); |
| 274 | file_chdir(g.zLocalRoot, 0); |
| 275 | db_prepare(&q, |
| 276 | "SELECT patch.cfg.value" |
| 277 | " FROM patch.cfg, localdb.vvar" |
| 278 | " WHERE patch.cfg.key='baseline'" |
| 279 | " AND localdb.vvar.name='checkout-hash'" |
| @@ -269,21 +280,185 @@ | |
| 280 | " AND patch.cfg.key<>localdb.vvar.name" |
| 281 | ); |
| 282 | if( db_step(&q)==SQLITE_ROW ){ |
| 283 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 284 | blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); |
| 285 | if( mFlags & PATCH_VERBOSE ){ |
| 286 | fossil_print("%-10s %s\n", "BASELINE", db_column_text(&q,0)); |
| 287 | } |
| 288 | } |
| 289 | db_finalize(&q); |
| 290 | if( blob_size(&cmd)>0 ){ |
| 291 | if( mFlags & PATCH_DRYRUN ){ |
| 292 | fossil_print("%s\n", blob_str(&cmd)); |
| 293 | }else{ |
| 294 | int rc = fossil_system(blob_str(&cmd)); |
| 295 | if( rc ){ |
| 296 | fossil_fatal("unable to update to the baseline check-out: %s", |
| 297 | blob_str(&cmd)); |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | blob_reset(&cmd); |
| 302 | if( db_table_exists("patch","patchmerge") ){ |
| 303 | db_prepare(&q, |
| 304 | "SELECT type, mhash, upper(type) FROM patch.patchmerge" |
| 305 | " WHERE type IN ('merge','cherrypick','backout','integrate')" |
| 306 | " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" |
| 307 | ); |
| 308 | while( db_step(&q)==SQLITE_ROW ){ |
| 309 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 310 | blob_appendf(&cmd, " --%s %s\n", db_column_text(&q,0), |
| 311 | db_column_text(&q,1)); |
| 312 | if( mFlags & PATCH_VERBOSE ){ |
| 313 | fossil_print("%-10s %s\n", db_column_text(&q,2), |
| 314 | db_column_text(&q,0)); |
| 315 | } |
| 316 | } |
| 317 | db_finalize(&q); |
| 318 | if( mFlags & PATCH_DRYRUN ){ |
| 319 | fossil_print("%s", blob_str(&cmd)); |
| 320 | }else{ |
| 321 | int rc = fossil_system(blob_str(&cmd)); |
| 322 | if( rc ){ |
| 323 | fossil_fatal("unable to do merges:\n%s", |
| 324 | blob_str(&cmd)); |
| 325 | } |
| 326 | } |
| 327 | blob_reset(&cmd); |
| 328 | } |
| 329 | |
| 330 | /* Deletions */ |
| 331 | db_prepare(&q, "SELECT pathname FROM patch.chng" |
| 332 | " WHERE origname IS NULL AND delta IS NULL"); |
| 333 | while( db_step(&q)==SQLITE_ROW ){ |
| 334 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 335 | blob_appendf(&cmd, " rm --hard %$\n", db_column_text(&q,0)); |
| 336 | if( mFlags & PATCH_VERBOSE ){ |
| 337 | fossil_print("%-10s %s\n", "DELETE", db_column_text(&q,0)); |
| 338 | } |
| 339 | } |
| 340 | db_finalize(&q); |
| 341 | if( blob_size(&cmd)>0 ){ |
| 342 | if( mFlags & PATCH_DRYRUN ){ |
| 343 | fossil_print("%s", blob_str(&cmd)); |
| 344 | }else{ |
| 345 | int rc = fossil_system(blob_str(&cmd)); |
| 346 | if( rc ){ |
| 347 | fossil_fatal("unable to do merges:\n%s", |
| 348 | blob_str(&cmd)); |
| 349 | } |
| 350 | } |
| 351 | blob_reset(&cmd); |
| 352 | } |
| 353 | |
| 354 | /* Renames */ |
| 355 | db_prepare(&q, |
| 356 | "SELECT origname, pathname FROM patch.chng" |
| 357 | " WHERE origname IS NOT NULL" |
| 358 | " AND origname<>pathname" |
| 359 | ); |
| 360 | while( db_step(&q)==SQLITE_ROW ){ |
| 361 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 362 | blob_appendf(&cmd, " mv --hard %$ %$\n", |
| 363 | db_column_text(&q,0), db_column_text(&q,1)); |
| 364 | if( mFlags & PATCH_VERBOSE ){ |
| 365 | fossil_print("%-10s %s -> %s\n", "RENAME", |
| 366 | db_column_text(&q,0), db_column_text(&q,1)); |
| 367 | } |
| 368 | } |
| 369 | db_finalize(&q); |
| 370 | if( blob_size(&cmd)>0 ){ |
| 371 | if( mFlags & PATCH_DRYRUN ){ |
| 372 | fossil_print("%s", blob_str(&cmd)); |
| 373 | }else{ |
| 374 | int rc = fossil_system(blob_str(&cmd)); |
| 375 | if( rc ){ |
| 376 | fossil_fatal("unable to rename files:\n%s", |
| 377 | blob_str(&cmd)); |
| 378 | } |
| 379 | } |
| 380 | blob_reset(&cmd); |
| 381 | } |
| 382 | |
| 383 | /* Edits and new files */ |
| 384 | db_prepare(&q, |
| 385 | "SELECT pathname, hash, isexe, islink, delta FROM patch.chng" |
| 386 | " WHERE delta IS NOT NULL" |
| 387 | ); |
| 388 | while( db_step(&q)==SQLITE_ROW ){ |
| 389 | const char *zPathname = db_column_text(&q,0); |
| 390 | const char *zHash = db_column_text(&q,1); |
| 391 | int isExe = db_column_int(&q,2); |
| 392 | int isLink = db_column_int(&q,3); |
| 393 | Blob data; |
| 394 | |
| 395 | blob_init(&data, 0, 0); |
| 396 | db_column_blob(&q, 4, &data); |
| 397 | blob_uncompress(&data, &data); |
| 398 | if( zHash ){ |
| 399 | Blob basis; |
| 400 | int rid = fast_uuid_to_rid(zHash); |
| 401 | int outSize, sz; |
| 402 | char *aOut; |
| 403 | if( rid==0 ){ |
| 404 | fossil_fatal("cannot locate basis artifact %s for %s", |
| 405 | zHash, zPathname); |
| 406 | } |
| 407 | if( !content_get(rid, &basis) ){ |
| 408 | fossil_fatal("cannot load basis artifact %d for %s", rid, zPathname); |
| 409 | } |
| 410 | outSize = delta_output_size(blob_buffer(&data),blob_size(&data)); |
| 411 | if( outSize<=0 ){ |
| 412 | fossil_fatal("malformed delta for %s", zPathname); |
| 413 | } |
| 414 | aOut = sqlite3_malloc64( outSize+1 ); |
| 415 | if( aOut==0 ){ |
| 416 | fossil_fatal("out of memory"); |
| 417 | } |
| 418 | sz = delta_apply(blob_buffer(&basis), blob_size(&basis), |
| 419 | blob_buffer(&data), blob_size(&data), aOut); |
| 420 | if( sz<0 ){ |
| 421 | fossil_fatal("malformed delta for %s", zPathname); |
| 422 | } |
| 423 | blob_reset(&basis); |
| 424 | blob_reset(&data); |
| 425 | blob_append(&data, aOut, sz); |
| 426 | sqlite3_free(aOut); |
| 427 | if( mFlags & PATCH_VERBOSE ){ |
| 428 | fossil_print("%-10s %s\n", "EDIT", zPathname); |
| 429 | } |
| 430 | }else{ |
| 431 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 432 | blob_appendf(&cmd, " add %$\n", zPathname); |
| 433 | if( mFlags & PATCH_VERBOSE ){ |
| 434 | fossil_print("%-10s %s\n", "NEW", zPathname); |
| 435 | } |
| 436 | } |
| 437 | if( (mFlags & PATCH_DRYRUN)==0 ){ |
| 438 | if( isLink ){ |
| 439 | symlink_create(blob_str(&data), zPathname); |
| 440 | }else{ |
| 441 | blob_write_to_file(&data, zPathname); |
| 442 | } |
| 443 | file_setexe(zPathname, isExe); |
| 444 | blob_reset(&data); |
| 445 | } |
| 446 | } |
| 447 | db_finalize(&q); |
| 448 | if( blob_size(&cmd)>0 ){ |
| 449 | if( mFlags & PATCH_DRYRUN ){ |
| 450 | fossil_print("%s", blob_str(&cmd)); |
| 451 | }else{ |
| 452 | int rc = fossil_system(blob_str(&cmd)); |
| 453 | if( rc ){ |
| 454 | fossil_fatal("unable to add new files:\n%s", |
| 455 | blob_str(&cmd)); |
| 456 | } |
| 457 | } |
| 458 | blob_reset(&cmd); |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | |
| 463 | /* |
| 464 | ** COMMAND: patch |
| @@ -304,10 +479,12 @@ | |
| 479 | ** |
| 480 | ** Apply the changes in FILENAME to the current check-out. Options: |
| 481 | ** |
| 482 | ** -f|--force Apply the patch even though there are unsaved |
| 483 | ** changes in the current check-out. |
| 484 | ** -n|--dryrun Do nothing, but print what would have happened. |
| 485 | ** -v|--verbose Extra output explaining what happens. |
| 486 | ** |
| 487 | ** > fossil patch diff [DIFF-FLAGS] FILENAME |
| 488 | ** |
| 489 | ** View the changes specified by the binary patch FILENAME in a |
| 490 | ** human-readable format. The usual diff flags apply. |
| @@ -336,20 +513,23 @@ | |
| 513 | } |
| 514 | zCmd = g.argv[2]; |
| 515 | n = strlen(zCmd); |
| 516 | if( strncmp(zCmd, "apply", n)==0 ){ |
| 517 | int forceFlag = find_option("force","f",0)!=0; |
| 518 | unsigned flags = 0; |
| 519 | if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN; |
| 520 | if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; |
| 521 | db_must_be_within_tree(); |
| 522 | verify_all_options(); |
| 523 | if( g.argc!=4 ){ |
| 524 | usage("apply FILENAME"); |
| 525 | } |
| 526 | if( !forceFlag && unsaved_changes(0) ){ |
| 527 | fossil_fatal("there are unsaved changes in the current checkout"); |
| 528 | } |
| 529 | patch_attach(g.argv[3]); |
| 530 | patch_apply(flags); |
| 531 | }else |
| 532 | if( strncmp(zCmd, "create", n)==0 ){ |
| 533 | db_must_be_within_tree(); |
| 534 | verify_all_options(); |
| 535 | if( g.argc!=4 ){ |
| 536 |