Fossil SCM
Enhance patchfiles to deal with renames.
Commit
4d82a8d1d01e8ccba42e9940398201fb1a159912952b1b656a6f7d3aa7e9b12f
Parent
b08e8651e4990b3…
1 file changed
+142
-19
+142
-19
| --- src/patch.c | ||
| +++ src/patch.c | ||
| @@ -39,10 +39,72 @@ | ||
| 39 | 39 | sz = blob_read_from_file(&x, zName, RepoFILE); |
| 40 | 40 | sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT); |
| 41 | 41 | blob_reset(&x); |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | +/* | |
| 45 | +** mkdelta(X,Y) | |
| 46 | +** | |
| 47 | +** X is an numeric artifact id. Y is a filename. | |
| 48 | +** | |
| 49 | +** Compute a compressed delta that carries X into Y. Or return NULL | |
| 50 | +** if X is equal to Y. | |
| 51 | +*/ | |
| 52 | +static void mkdeltaFunc( | |
| 53 | + sqlite3_context *context, | |
| 54 | + int argc, | |
| 55 | + sqlite3_value **argv | |
| 56 | +){ | |
| 57 | + const char *zFile; | |
| 58 | + Blob x, y; | |
| 59 | + int rid; | |
| 60 | + char *aOut; | |
| 61 | + int nOut; | |
| 62 | + sqlite3_int64 sz; | |
| 63 | + | |
| 64 | + rid = sqlite3_value_int(argv[0]); | |
| 65 | + if( !content_get(rid, &x) ){ | |
| 66 | + sqlite3_result_error(context, "mkdelta(X,Y): no content for X", -1); | |
| 67 | + return; | |
| 68 | + } | |
| 69 | + zFile = (const char*)sqlite3_value_text(argv[1]); | |
| 70 | + if( zFile==0 ){ | |
| 71 | + sqlite3_result_error(context, "mkdelta(X,Y): NULL Y argument", -1); | |
| 72 | + blob_reset(&x); | |
| 73 | + return; | |
| 74 | + } | |
| 75 | + sz = blob_read_from_file(&y, zFile, RepoFILE); | |
| 76 | + if( sz<0 ){ | |
| 77 | + sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1); | |
| 78 | + blob_reset(&x); | |
| 79 | + return; | |
| 80 | + } | |
| 81 | + aOut = sqlite3_malloc64(sz+70); | |
| 82 | + if( aOut==0 ){ | |
| 83 | + sqlite3_result_error_nomem(context); | |
| 84 | + blob_reset(&y); | |
| 85 | + blob_reset(&x); | |
| 86 | + return; | |
| 87 | + } | |
| 88 | + if( blob_size(&x)==blob_size(&y) | |
| 89 | + && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 | |
| 90 | + ){ | |
| 91 | + blob_reset(&y); | |
| 92 | + blob_reset(&x); | |
| 93 | + return; | |
| 94 | + } | |
| 95 | + nOut = delta_create(blob_buffer(&x),blob_size(&x), | |
| 96 | + blob_buffer(&y),blob_size(&y), aOut); | |
| 97 | + blob_reset(&x); | |
| 98 | + blob_reset(&y); | |
| 99 | + blob_init(&x, aOut, nOut); | |
| 100 | + blob_compress(&x, &x); | |
| 101 | + sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x), | |
| 102 | + SQLITE_TRANSIENT); | |
| 103 | + blob_reset(&x); | |
| 104 | +} | |
| 105 | + | |
| 44 | 106 | |
| 45 | 107 | /* |
| 46 | 108 | ** Generate a binary patch file and store it into the file |
| 47 | 109 | ** named zOut. |
| 48 | 110 | */ |
| @@ -53,20 +115,23 @@ | ||
| 53 | 115 | } |
| 54 | 116 | add_content_sql_commands(g.db); |
| 55 | 117 | deltafunc_init(g.db); |
| 56 | 118 | sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0, |
| 57 | 119 | readfileFunc, 0, 0); |
| 120 | + sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0, | |
| 121 | + mkdeltaFunc, 0, 0); | |
| 58 | 122 | db_multi_exec("ATTACH %Q AS patch;", zOut); |
| 59 | 123 | db_multi_exec( |
| 60 | 124 | "PRAGMA patch.journal_mode=OFF;\n" |
| 61 | 125 | "PRAGMA patch.page_size=512;\n" |
| 62 | 126 | "CREATE TABLE patch.chng(\n" |
| 63 | - " fname TEXT,\n" /* Filename */ | |
| 64 | - " hash TEXT,\n" /* Baseline hash. NULL for new files. */ | |
| 65 | - " isexe BOOL,\n" /* True if executable */ | |
| 66 | - " islink BOOL,\n" /* True if is a symbolic link */ | |
| 67 | - " delta BLOB\n" /* Delta. NULL if file deleted */ | |
| 127 | + " pathname TEXT,\n" /* Filename */ | |
| 128 | + " origname TEXT,\n" /* Name before rename. NULL if not renamed */ | |
| 129 | + " hash TEXT,\n" /* Baseline hash. NULL for new files. */ | |
| 130 | + " isexe BOOL,\n" /* True if executable */ | |
| 131 | + " islink BOOL,\n" /* True if is a symbolic link */ | |
| 132 | + " delta BLOB\n" /* Delta. NULL if file deleted or unchanged */ | |
| 68 | 133 | ");" |
| 69 | 134 | "CREATE TABLE patch.cfg(\n" |
| 70 | 135 | " key TEXT,\n" |
| 71 | 136 | " value ANY\n" |
| 72 | 137 | ");" |
| @@ -80,30 +145,30 @@ | ||
| 80 | 145 | db_multi_exec("INSERT INTO patch.cfg(key,value)VALUES('merged',1);"); |
| 81 | 146 | } |
| 82 | 147 | |
| 83 | 148 | /* New files */ |
| 84 | 149 | db_multi_exec( |
| 85 | - "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)" | |
| 150 | + "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" | |
| 86 | 151 | " SELECT pathname, NULL, isexe, islink," |
| 87 | 152 | " compress(read_co_file(%Q||pathname))" |
| 88 | 153 | " FROM vfile WHERE rid==0;", |
| 89 | 154 | g.zLocalRoot |
| 90 | 155 | ); |
| 91 | 156 | /* Deleted files */ |
| 92 | 157 | db_multi_exec( |
| 93 | - "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)" | |
| 158 | + "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" | |
| 94 | 159 | " SELECT pathname, NULL, 0, 0, NULL" |
| 95 | 160 | " FROM vfile WHERE deleted;" |
| 96 | 161 | ); |
| 97 | 162 | /* Changed files */ |
| 98 | 163 | db_multi_exec( |
| 99 | - "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)" | |
| 100 | - " SELECT pathname, blob.uuid, isexe, islink," | |
| 101 | - " compress(delta_create(content(blob.uuid)," | |
| 102 | - "read_co_file(%Q||pathname)))" | |
| 164 | + "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" | |
| 165 | + " SELECT pathname, origname, blob.uuid, isexe, islink," | |
| 166 | + " mkdelta(blob.rid, %Q||pathname)" | |
| 103 | 167 | " FROM vfile, blob" |
| 104 | - " WHERE blob.rid=vfile.rid AND NOT deleted AND chnged;", | |
| 168 | + " WHERE blob.rid=vfile.rid" | |
| 169 | + " AND NOT deleted AND (chnged OR origname<>pathname);", | |
| 105 | 170 | g.zLocalRoot |
| 106 | 171 | ); |
| 107 | 172 | } |
| 108 | 173 | |
| 109 | 174 | /* |
| @@ -110,11 +175,11 @@ | ||
| 110 | 175 | ** Attempt to load and validate a patchfile identified by the first |
| 111 | 176 | ** argument. |
| 112 | 177 | */ |
| 113 | 178 | void patch_attach(const char *zIn){ |
| 114 | 179 | Stmt q; |
| 115 | - if( !file_isfile(zIn, zIn) ){ | |
| 180 | + if( !file_isfile(zIn, ExtFILE) ){ | |
| 116 | 181 | fossil_fatal("no such file: %s", zIn); |
| 117 | 182 | } |
| 118 | 183 | if( g.db==0 ){ |
| 119 | 184 | sqlite3_open(":memory:", &g.db); |
| 120 | 185 | } |
| @@ -138,22 +203,66 @@ | ||
| 138 | 203 | fossil_print("Patch against check-in %S\n", db_column_text(&q,0)); |
| 139 | 204 | }else{ |
| 140 | 205 | fossil_fatal("ERROR: Missing patch baseline"); |
| 141 | 206 | } |
| 142 | 207 | db_finalize(&q); |
| 143 | - db_prepare(&q, "SELECT fname, hash IS NULL AS isnew, delta IS NULL AS isdel" | |
| 144 | - " FROM patch.chng ORDER BY 1"); | |
| 208 | + db_prepare(&q, | |
| 209 | + "SELECT pathname," | |
| 210 | + " hash IS NULL AND delta IS NOT NULL," | |
| 211 | + " delta IS NULL," | |
| 212 | + " origname" | |
| 213 | + " FROM patch.chng ORDER BY 1"); | |
| 145 | 214 | while( db_step(&q)==SQLITE_ROW ){ |
| 146 | 215 | const char *zClass = "CHANGED"; |
| 147 | - if( db_column_int(&q, 1) ){ | |
| 216 | + const char *zName = db_column_text(&q,0); | |
| 217 | + const char *zOrigName = db_column_text(&q, 3); | |
| 218 | + if( db_column_int(&q, 1) && zOrigName==0 ){ | |
| 148 | 219 | zClass = "NEW"; |
| 149 | 220 | }else if( db_column_int(&q, 2) ){ |
| 150 | - zClass = "DELETED"; | |
| 221 | + zClass = zOrigName==0 ? "DELETED" : 0; | |
| 222 | + } | |
| 223 | + if( zOrigName!=0 && zOrigName[0]!=0 ){ | |
| 224 | + fossil_print("%-10s %s -> %s\n", "RENAME",zOrigName,zName); | |
| 225 | + } | |
| 226 | + if( zClass ){ | |
| 227 | + fossil_print("%-10s %s\n", zClass, zName); | |
| 151 | 228 | } |
| 152 | - fossil_print("%-10s %s\n", zClass, db_column_text(&q,0)); | |
| 229 | + } | |
| 230 | + db_finalize(&q); | |
| 231 | +} | |
| 232 | + | |
| 233 | +/* | |
| 234 | +** Apply the patch currently attached as database "patch". | |
| 235 | +** | |
| 236 | +** First update the check-out to be at "baseline". Then loop through | |
| 237 | +** and update all files. | |
| 238 | +*/ | |
| 239 | +void patch_apply(void){ | |
| 240 | + Stmt q; | |
| 241 | + Blob cmd; | |
| 242 | + blob_init(&cmd, 0, 0); | |
| 243 | + db_prepare(&q, | |
| 244 | + "SELECT patch.cfg.value" | |
| 245 | + " FROM patch.cfg, localdb.vvar" | |
| 246 | + " WHERE patch.cfg.key='baseline'" | |
| 247 | + " AND localdb.vvar.name='checkout-hash'" | |
| 248 | + " AND patch.cfg.key<>localdb.vvar.name" | |
| 249 | + ); | |
| 250 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 251 | + blob_append_escaped_arg(&cmd, g.nameOfExe); | |
| 252 | + blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); | |
| 153 | 253 | } |
| 154 | 254 | db_finalize(&q); |
| 255 | + if( blob_size(&cmd)>0 ){ | |
| 256 | + int rc = fossil_system(blob_str(&cmd)); | |
| 257 | + if( rc ){ | |
| 258 | + fossil_fatal("unable to update to the baseline check-out: %s", | |
| 259 | + blob_str(&cmd)); | |
| 260 | + } | |
| 261 | + } | |
| 262 | + blob_reset(&cmd); | |
| 263 | + | |
| 155 | 264 | } |
| 156 | 265 | |
| 157 | 266 | |
| 158 | 267 | /* |
| 159 | 268 | ** COMMAND: patch |
| @@ -170,11 +279,14 @@ | ||
| 170 | 279 | ** Create a new binary patch in FILENAME that captures all uncommitted |
| 171 | 280 | ** changes in the current check-out. |
| 172 | 281 | ** |
| 173 | 282 | ** > fossil patch apply FILENAME |
| 174 | 283 | ** |
| 175 | -** Apply the changes in FILENAME to the current check-out. | |
| 284 | +** Apply the changes in FILENAME to the current check-out. Options: | |
| 285 | +** | |
| 286 | +** -f|--force Apply the patch even though there are unsaved | |
| 287 | +** changes in the current check-out. | |
| 176 | 288 | ** |
| 177 | 289 | ** > fossil patch diff [DIFF-FLAGS] FILENAME |
| 178 | 290 | ** |
| 179 | 291 | ** View the changes specified by the binary patch FILENAME in a |
| 180 | 292 | ** human-readable format. The usual diff flags apply. |
| @@ -186,10 +298,15 @@ | ||
| 186 | 298 | ** |
| 187 | 299 | ** > fossil patch pull REMOTE-CHECKOUT |
| 188 | 300 | ** |
| 189 | 301 | ** Create a patch on a remote check-out, transfer that patch to the |
| 190 | 302 | ** local machine (using ssh) and apply the patch in the local checkout. |
| 303 | +** | |
| 304 | +** > fossil patch view FILENAME | |
| 305 | +** | |
| 306 | +** View a summary of the the changes in the binary patch FILENAME. | |
| 307 | +** | |
| 191 | 308 | */ |
| 192 | 309 | void patch_cmd(void){ |
| 193 | 310 | const char *zCmd; |
| 194 | 311 | size_t n; |
| 195 | 312 | if( g.argc<3 ){ |
| @@ -197,15 +314,21 @@ | ||
| 197 | 314 | usage("apply|create|pull|push|view"); |
| 198 | 315 | } |
| 199 | 316 | zCmd = g.argv[2]; |
| 200 | 317 | n = strlen(zCmd); |
| 201 | 318 | if( strncmp(zCmd, "apply", n)==0 ){ |
| 319 | + int forceFlag = find_option("force","f",0)!=0; | |
| 202 | 320 | db_must_be_within_tree(); |
| 203 | 321 | verify_all_options(); |
| 204 | 322 | if( g.argc!=4 ){ |
| 205 | 323 | usage("apply FILENAME"); |
| 206 | 324 | } |
| 325 | + if( !forceFlag && unsaved_changes(0) ){ | |
| 326 | + fossil_fatal("there are unsaved changes in the current checkout"); | |
| 327 | + } | |
| 328 | + patch_attach(g.argv[3]); | |
| 329 | + patch_apply(); | |
| 207 | 330 | }else |
| 208 | 331 | if( strncmp(zCmd, "create", n)==0 ){ |
| 209 | 332 | db_must_be_within_tree(); |
| 210 | 333 | verify_all_options(); |
| 211 | 334 | if( g.argc!=4 ){ |
| 212 | 335 |
| --- src/patch.c | |
| +++ src/patch.c | |
| @@ -39,10 +39,72 @@ | |
| 39 | sz = blob_read_from_file(&x, zName, RepoFILE); |
| 40 | sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT); |
| 41 | blob_reset(&x); |
| 42 | } |
| 43 | |
| 44 | |
| 45 | /* |
| 46 | ** Generate a binary patch file and store it into the file |
| 47 | ** named zOut. |
| 48 | */ |
| @@ -53,20 +115,23 @@ | |
| 53 | } |
| 54 | add_content_sql_commands(g.db); |
| 55 | deltafunc_init(g.db); |
| 56 | sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0, |
| 57 | readfileFunc, 0, 0); |
| 58 | db_multi_exec("ATTACH %Q AS patch;", zOut); |
| 59 | db_multi_exec( |
| 60 | "PRAGMA patch.journal_mode=OFF;\n" |
| 61 | "PRAGMA patch.page_size=512;\n" |
| 62 | "CREATE TABLE patch.chng(\n" |
| 63 | " fname TEXT,\n" /* Filename */ |
| 64 | " hash TEXT,\n" /* Baseline hash. NULL for new files. */ |
| 65 | " isexe BOOL,\n" /* True if executable */ |
| 66 | " islink BOOL,\n" /* True if is a symbolic link */ |
| 67 | " delta BLOB\n" /* Delta. NULL if file deleted */ |
| 68 | ");" |
| 69 | "CREATE TABLE patch.cfg(\n" |
| 70 | " key TEXT,\n" |
| 71 | " value ANY\n" |
| 72 | ");" |
| @@ -80,30 +145,30 @@ | |
| 80 | db_multi_exec("INSERT INTO patch.cfg(key,value)VALUES('merged',1);"); |
| 81 | } |
| 82 | |
| 83 | /* New files */ |
| 84 | db_multi_exec( |
| 85 | "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)" |
| 86 | " SELECT pathname, NULL, isexe, islink," |
| 87 | " compress(read_co_file(%Q||pathname))" |
| 88 | " FROM vfile WHERE rid==0;", |
| 89 | g.zLocalRoot |
| 90 | ); |
| 91 | /* Deleted files */ |
| 92 | db_multi_exec( |
| 93 | "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)" |
| 94 | " SELECT pathname, NULL, 0, 0, NULL" |
| 95 | " FROM vfile WHERE deleted;" |
| 96 | ); |
| 97 | /* Changed files */ |
| 98 | db_multi_exec( |
| 99 | "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)" |
| 100 | " SELECT pathname, blob.uuid, isexe, islink," |
| 101 | " compress(delta_create(content(blob.uuid)," |
| 102 | "read_co_file(%Q||pathname)))" |
| 103 | " FROM vfile, blob" |
| 104 | " WHERE blob.rid=vfile.rid AND NOT deleted AND chnged;", |
| 105 | g.zLocalRoot |
| 106 | ); |
| 107 | } |
| 108 | |
| 109 | /* |
| @@ -110,11 +175,11 @@ | |
| 110 | ** Attempt to load and validate a patchfile identified by the first |
| 111 | ** argument. |
| 112 | */ |
| 113 | void patch_attach(const char *zIn){ |
| 114 | Stmt q; |
| 115 | if( !file_isfile(zIn, zIn) ){ |
| 116 | fossil_fatal("no such file: %s", zIn); |
| 117 | } |
| 118 | if( g.db==0 ){ |
| 119 | sqlite3_open(":memory:", &g.db); |
| 120 | } |
| @@ -138,22 +203,66 @@ | |
| 138 | fossil_print("Patch against check-in %S\n", db_column_text(&q,0)); |
| 139 | }else{ |
| 140 | fossil_fatal("ERROR: Missing patch baseline"); |
| 141 | } |
| 142 | db_finalize(&q); |
| 143 | db_prepare(&q, "SELECT fname, hash IS NULL AS isnew, delta IS NULL AS isdel" |
| 144 | " FROM patch.chng ORDER BY 1"); |
| 145 | while( db_step(&q)==SQLITE_ROW ){ |
| 146 | const char *zClass = "CHANGED"; |
| 147 | if( db_column_int(&q, 1) ){ |
| 148 | zClass = "NEW"; |
| 149 | }else if( db_column_int(&q, 2) ){ |
| 150 | zClass = "DELETED"; |
| 151 | } |
| 152 | fossil_print("%-10s %s\n", zClass, db_column_text(&q,0)); |
| 153 | } |
| 154 | db_finalize(&q); |
| 155 | } |
| 156 | |
| 157 | |
| 158 | /* |
| 159 | ** COMMAND: patch |
| @@ -170,11 +279,14 @@ | |
| 170 | ** Create a new binary patch in FILENAME that captures all uncommitted |
| 171 | ** changes in the current check-out. |
| 172 | ** |
| 173 | ** > fossil patch apply FILENAME |
| 174 | ** |
| 175 | ** Apply the changes in FILENAME to the current check-out. |
| 176 | ** |
| 177 | ** > fossil patch diff [DIFF-FLAGS] FILENAME |
| 178 | ** |
| 179 | ** View the changes specified by the binary patch FILENAME in a |
| 180 | ** human-readable format. The usual diff flags apply. |
| @@ -186,10 +298,15 @@ | |
| 186 | ** |
| 187 | ** > fossil patch pull REMOTE-CHECKOUT |
| 188 | ** |
| 189 | ** Create a patch on a remote check-out, transfer that patch to the |
| 190 | ** local machine (using ssh) and apply the patch in the local checkout. |
| 191 | */ |
| 192 | void patch_cmd(void){ |
| 193 | const char *zCmd; |
| 194 | size_t n; |
| 195 | if( g.argc<3 ){ |
| @@ -197,15 +314,21 @@ | |
| 197 | usage("apply|create|pull|push|view"); |
| 198 | } |
| 199 | zCmd = g.argv[2]; |
| 200 | n = strlen(zCmd); |
| 201 | if( strncmp(zCmd, "apply", n)==0 ){ |
| 202 | db_must_be_within_tree(); |
| 203 | verify_all_options(); |
| 204 | if( g.argc!=4 ){ |
| 205 | usage("apply FILENAME"); |
| 206 | } |
| 207 | }else |
| 208 | if( strncmp(zCmd, "create", n)==0 ){ |
| 209 | db_must_be_within_tree(); |
| 210 | verify_all_options(); |
| 211 | if( g.argc!=4 ){ |
| 212 |
| --- src/patch.c | |
| +++ src/patch.c | |
| @@ -39,10 +39,72 @@ | |
| 39 | sz = blob_read_from_file(&x, zName, RepoFILE); |
| 40 | sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT); |
| 41 | blob_reset(&x); |
| 42 | } |
| 43 | |
| 44 | /* |
| 45 | ** mkdelta(X,Y) |
| 46 | ** |
| 47 | ** X is an numeric artifact id. Y is a filename. |
| 48 | ** |
| 49 | ** Compute a compressed delta that carries X into Y. Or return NULL |
| 50 | ** if X is equal to Y. |
| 51 | */ |
| 52 | static void mkdeltaFunc( |
| 53 | sqlite3_context *context, |
| 54 | int argc, |
| 55 | sqlite3_value **argv |
| 56 | ){ |
| 57 | const char *zFile; |
| 58 | Blob x, y; |
| 59 | int rid; |
| 60 | char *aOut; |
| 61 | int nOut; |
| 62 | sqlite3_int64 sz; |
| 63 | |
| 64 | rid = sqlite3_value_int(argv[0]); |
| 65 | if( !content_get(rid, &x) ){ |
| 66 | sqlite3_result_error(context, "mkdelta(X,Y): no content for X", -1); |
| 67 | return; |
| 68 | } |
| 69 | zFile = (const char*)sqlite3_value_text(argv[1]); |
| 70 | if( zFile==0 ){ |
| 71 | sqlite3_result_error(context, "mkdelta(X,Y): NULL Y argument", -1); |
| 72 | blob_reset(&x); |
| 73 | return; |
| 74 | } |
| 75 | sz = blob_read_from_file(&y, zFile, RepoFILE); |
| 76 | if( sz<0 ){ |
| 77 | sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1); |
| 78 | blob_reset(&x); |
| 79 | return; |
| 80 | } |
| 81 | aOut = sqlite3_malloc64(sz+70); |
| 82 | if( aOut==0 ){ |
| 83 | sqlite3_result_error_nomem(context); |
| 84 | blob_reset(&y); |
| 85 | blob_reset(&x); |
| 86 | return; |
| 87 | } |
| 88 | if( blob_size(&x)==blob_size(&y) |
| 89 | && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 |
| 90 | ){ |
| 91 | blob_reset(&y); |
| 92 | blob_reset(&x); |
| 93 | return; |
| 94 | } |
| 95 | nOut = delta_create(blob_buffer(&x),blob_size(&x), |
| 96 | blob_buffer(&y),blob_size(&y), aOut); |
| 97 | blob_reset(&x); |
| 98 | blob_reset(&y); |
| 99 | blob_init(&x, aOut, nOut); |
| 100 | blob_compress(&x, &x); |
| 101 | sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x), |
| 102 | SQLITE_TRANSIENT); |
| 103 | blob_reset(&x); |
| 104 | } |
| 105 | |
| 106 | |
| 107 | /* |
| 108 | ** Generate a binary patch file and store it into the file |
| 109 | ** named zOut. |
| 110 | */ |
| @@ -53,20 +115,23 @@ | |
| 115 | } |
| 116 | add_content_sql_commands(g.db); |
| 117 | deltafunc_init(g.db); |
| 118 | sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0, |
| 119 | readfileFunc, 0, 0); |
| 120 | sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0, |
| 121 | mkdeltaFunc, 0, 0); |
| 122 | db_multi_exec("ATTACH %Q AS patch;", zOut); |
| 123 | db_multi_exec( |
| 124 | "PRAGMA patch.journal_mode=OFF;\n" |
| 125 | "PRAGMA patch.page_size=512;\n" |
| 126 | "CREATE TABLE patch.chng(\n" |
| 127 | " pathname TEXT,\n" /* Filename */ |
| 128 | " origname TEXT,\n" /* Name before rename. NULL if not renamed */ |
| 129 | " hash TEXT,\n" /* Baseline hash. NULL for new files. */ |
| 130 | " isexe BOOL,\n" /* True if executable */ |
| 131 | " islink BOOL,\n" /* True if is a symbolic link */ |
| 132 | " delta BLOB\n" /* Delta. NULL if file deleted or unchanged */ |
| 133 | ");" |
| 134 | "CREATE TABLE patch.cfg(\n" |
| 135 | " key TEXT,\n" |
| 136 | " value ANY\n" |
| 137 | ");" |
| @@ -80,30 +145,30 @@ | |
| 145 | db_multi_exec("INSERT INTO patch.cfg(key,value)VALUES('merged',1);"); |
| 146 | } |
| 147 | |
| 148 | /* New files */ |
| 149 | db_multi_exec( |
| 150 | "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
| 151 | " SELECT pathname, NULL, isexe, islink," |
| 152 | " compress(read_co_file(%Q||pathname))" |
| 153 | " FROM vfile WHERE rid==0;", |
| 154 | g.zLocalRoot |
| 155 | ); |
| 156 | /* Deleted files */ |
| 157 | db_multi_exec( |
| 158 | "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" |
| 159 | " SELECT pathname, NULL, 0, 0, NULL" |
| 160 | " FROM vfile WHERE deleted;" |
| 161 | ); |
| 162 | /* Changed files */ |
| 163 | db_multi_exec( |
| 164 | "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" |
| 165 | " SELECT pathname, origname, blob.uuid, isexe, islink," |
| 166 | " mkdelta(blob.rid, %Q||pathname)" |
| 167 | " FROM vfile, blob" |
| 168 | " WHERE blob.rid=vfile.rid" |
| 169 | " AND NOT deleted AND (chnged OR origname<>pathname);", |
| 170 | g.zLocalRoot |
| 171 | ); |
| 172 | } |
| 173 | |
| 174 | /* |
| @@ -110,11 +175,11 @@ | |
| 175 | ** Attempt to load and validate a patchfile identified by the first |
| 176 | ** argument. |
| 177 | */ |
| 178 | void patch_attach(const char *zIn){ |
| 179 | Stmt q; |
| 180 | if( !file_isfile(zIn, ExtFILE) ){ |
| 181 | fossil_fatal("no such file: %s", zIn); |
| 182 | } |
| 183 | if( g.db==0 ){ |
| 184 | sqlite3_open(":memory:", &g.db); |
| 185 | } |
| @@ -138,22 +203,66 @@ | |
| 203 | fossil_print("Patch against check-in %S\n", db_column_text(&q,0)); |
| 204 | }else{ |
| 205 | fossil_fatal("ERROR: Missing patch baseline"); |
| 206 | } |
| 207 | db_finalize(&q); |
| 208 | db_prepare(&q, |
| 209 | "SELECT pathname," |
| 210 | " hash IS NULL AND delta IS NOT NULL," |
| 211 | " delta IS NULL," |
| 212 | " origname" |
| 213 | " FROM patch.chng ORDER BY 1"); |
| 214 | while( db_step(&q)==SQLITE_ROW ){ |
| 215 | const char *zClass = "CHANGED"; |
| 216 | const char *zName = db_column_text(&q,0); |
| 217 | const char *zOrigName = db_column_text(&q, 3); |
| 218 | if( db_column_int(&q, 1) && zOrigName==0 ){ |
| 219 | zClass = "NEW"; |
| 220 | }else if( db_column_int(&q, 2) ){ |
| 221 | zClass = zOrigName==0 ? "DELETED" : 0; |
| 222 | } |
| 223 | if( zOrigName!=0 && zOrigName[0]!=0 ){ |
| 224 | fossil_print("%-10s %s -> %s\n", "RENAME",zOrigName,zName); |
| 225 | } |
| 226 | if( zClass ){ |
| 227 | fossil_print("%-10s %s\n", zClass, zName); |
| 228 | } |
| 229 | } |
| 230 | db_finalize(&q); |
| 231 | } |
| 232 | |
| 233 | /* |
| 234 | ** Apply the patch currently attached as database "patch". |
| 235 | ** |
| 236 | ** First update the check-out to be at "baseline". Then loop through |
| 237 | ** and update all files. |
| 238 | */ |
| 239 | void patch_apply(void){ |
| 240 | Stmt q; |
| 241 | Blob cmd; |
| 242 | blob_init(&cmd, 0, 0); |
| 243 | db_prepare(&q, |
| 244 | "SELECT patch.cfg.value" |
| 245 | " FROM patch.cfg, localdb.vvar" |
| 246 | " WHERE patch.cfg.key='baseline'" |
| 247 | " AND localdb.vvar.name='checkout-hash'" |
| 248 | " AND patch.cfg.key<>localdb.vvar.name" |
| 249 | ); |
| 250 | if( db_step(&q)==SQLITE_ROW ){ |
| 251 | blob_append_escaped_arg(&cmd, g.nameOfExe); |
| 252 | blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); |
| 253 | } |
| 254 | db_finalize(&q); |
| 255 | if( blob_size(&cmd)>0 ){ |
| 256 | int rc = fossil_system(blob_str(&cmd)); |
| 257 | if( rc ){ |
| 258 | fossil_fatal("unable to update to the baseline check-out: %s", |
| 259 | blob_str(&cmd)); |
| 260 | } |
| 261 | } |
| 262 | blob_reset(&cmd); |
| 263 | |
| 264 | } |
| 265 | |
| 266 | |
| 267 | /* |
| 268 | ** COMMAND: patch |
| @@ -170,11 +279,14 @@ | |
| 279 | ** Create a new binary patch in FILENAME that captures all uncommitted |
| 280 | ** changes in the current check-out. |
| 281 | ** |
| 282 | ** > fossil patch apply FILENAME |
| 283 | ** |
| 284 | ** Apply the changes in FILENAME to the current check-out. Options: |
| 285 | ** |
| 286 | ** -f|--force Apply the patch even though there are unsaved |
| 287 | ** changes in the current check-out. |
| 288 | ** |
| 289 | ** > fossil patch diff [DIFF-FLAGS] FILENAME |
| 290 | ** |
| 291 | ** View the changes specified by the binary patch FILENAME in a |
| 292 | ** human-readable format. The usual diff flags apply. |
| @@ -186,10 +298,15 @@ | |
| 298 | ** |
| 299 | ** > fossil patch pull REMOTE-CHECKOUT |
| 300 | ** |
| 301 | ** Create a patch on a remote check-out, transfer that patch to the |
| 302 | ** local machine (using ssh) and apply the patch in the local checkout. |
| 303 | ** |
| 304 | ** > fossil patch view FILENAME |
| 305 | ** |
| 306 | ** View a summary of the the changes in the binary patch FILENAME. |
| 307 | ** |
| 308 | */ |
| 309 | void patch_cmd(void){ |
| 310 | const char *zCmd; |
| 311 | size_t n; |
| 312 | if( g.argc<3 ){ |
| @@ -197,15 +314,21 @@ | |
| 314 | usage("apply|create|pull|push|view"); |
| 315 | } |
| 316 | zCmd = g.argv[2]; |
| 317 | n = strlen(zCmd); |
| 318 | if( strncmp(zCmd, "apply", n)==0 ){ |
| 319 | int forceFlag = find_option("force","f",0)!=0; |
| 320 | db_must_be_within_tree(); |
| 321 | verify_all_options(); |
| 322 | if( g.argc!=4 ){ |
| 323 | usage("apply FILENAME"); |
| 324 | } |
| 325 | if( !forceFlag && unsaved_changes(0) ){ |
| 326 | fossil_fatal("there are unsaved changes in the current checkout"); |
| 327 | } |
| 328 | patch_attach(g.argv[3]); |
| 329 | patch_apply(); |
| 330 | }else |
| 331 | if( strncmp(zCmd, "create", n)==0 ){ |
| 332 | db_must_be_within_tree(); |
| 333 | verify_all_options(); |
| 334 | if( g.argc!=4 ){ |
| 335 |