| | @@ -50,23 +50,37 @@ |
| 50 | 50 | ** |
| 51 | 51 | ** --nochange | -n Dryrun: do not actually make any changes; just |
| 52 | 52 | ** show what would have happened. |
| 53 | 53 | */ |
| 54 | 54 | void merge_cmd(void){ |
| 55 | | - int vid; /* Current version */ |
| 56 | | - int mid; /* Version we are merging from */ |
| 57 | | - int pid; /* The pivot version - most recent common ancestor */ |
| 55 | + int vid; /* Current version "V" */ |
| 56 | + int mid; /* Version we are merging from "M" */ |
| 57 | + int pid; /* The pivot version - most recent common ancestor P */ |
| 58 | 58 | int detailFlag; /* True if the --detail option is present */ |
| 59 | 59 | int pickFlag; /* True if the --cherrypick option is present */ |
| 60 | 60 | int backoutFlag; /* True if the --backout option is present */ |
| 61 | 61 | int nochangeFlag; /* True if the --nochange or -n option is present */ |
| 62 | 62 | const char *zBinGlob; /* The value of --binary */ |
| 63 | + int debugFlag; /* True if --debug is present */ |
| 64 | + int nChng; /* Number of file name changes */ |
| 65 | + int *aChng; /* An array of file name changes */ |
| 66 | + int i; /* Loop counter */ |
| 67 | + int nConflict = 0; /* Number of conflicts seen */ |
| 63 | 68 | Stmt q; |
| 64 | 69 | |
| 70 | + |
| 71 | + /* Notation: |
| 72 | + ** |
| 73 | + ** V The current checkout |
| 74 | + ** M The version being merged in |
| 75 | + ** P The "pivot" - the most recent common ancestor of V and M. |
| 76 | + */ |
| 77 | + |
| 65 | 78 | detailFlag = find_option("detail",0,0)!=0; |
| 66 | 79 | pickFlag = find_option("cherrypick",0,0)!=0; |
| 67 | 80 | backoutFlag = find_option("backout",0,0)!=0; |
| 81 | + debugFlag = find_option("debug",0,0)!=0; |
| 68 | 82 | zBinGlob = find_option("binary",0,1); |
| 69 | 83 | nochangeFlag = find_option("nochange","n",0)!=0; |
| 70 | 84 | if( g.argc!=3 ){ |
| 71 | 85 | usage("VERSION"); |
| 72 | 86 | } |
| | @@ -129,53 +143,134 @@ |
| 129 | 143 | " idp INTEGER," /* VFILE entry for the pivot */ |
| 130 | 144 | " idm INTEGER," /* VFILE entry for version merging in */ |
| 131 | 145 | " chnged BOOLEAN," /* True if current version has been edited */ |
| 132 | 146 | " ridv INTEGER," /* Record ID for current version */ |
| 133 | 147 | " ridp INTEGER," /* Record ID for pivot */ |
| 134 | | - " ridm INTEGER" /* Record ID for merge */ |
| 148 | + " ridm INTEGER," /* Record ID for merge */ |
| 149 | + " fnp TEXT," /* The filename in the pivot */ |
| 150 | + " fnm TEXT" /* the filename in the merged version */ |
| 135 | 151 | ");" |
| 136 | 152 | ); |
| 153 | + |
| 154 | + /* Add files found in V |
| 155 | + */ |
| 156 | + db_multi_exec( |
| 157 | + "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)" |
| 158 | + " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 " |
| 159 | + " FROM vfile WHERE vid=%d", |
| 160 | + vid |
| 161 | + ); |
| 162 | + |
| 163 | + /* |
| 164 | + ** Compute name changes from P->V |
| 165 | + */ |
| 166 | + find_filename_changes(vid, pid, &nChng, &aChng); |
| 167 | + if( nChng ){ |
| 168 | + for(i=0; i<nChng; i++){ |
| 169 | + db_multi_exec( |
| 170 | + "UPDATE fv SET fnp=(SELECT name FROM filename WHERE fnid=%d)" |
| 171 | + " WHERE fn=(SELECT name FROM filename WHERE fnid=%d)", |
| 172 | + aChng[i*2+1], aChng[i*2] |
| 173 | + ); |
| 174 | + } |
| 175 | + fossil_free(aChng); |
| 176 | + } |
| 177 | + |
| 178 | + /* Add files found in P |
| 179 | + */ |
| 180 | + db_multi_exec( |
| 181 | + "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)" |
| 182 | + " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 " |
| 183 | + " FROM vfile" |
| 184 | + " WHERE vid=%d AND pathname NOT IN (SELECT fnp FROM fv)", |
| 185 | + pid |
| 186 | + ); |
| 187 | + |
| 188 | + /* |
| 189 | + ** Compute name changes from P->M |
| 190 | + */ |
| 191 | + find_filename_changes(pid, mid, &nChng, &aChng); |
| 192 | + if( nChng ){ |
| 193 | + if( nChng>4 ) db_multi_exec("CREATE INDEX fv_fnp ON fv(fnp)"); |
| 194 | + for(i=0; i<nChng; i++){ |
| 195 | + db_multi_exec( |
| 196 | + "UPDATE fv SET fnm=(SELECT name FROM filename WHERE fnid=%d)" |
| 197 | + " WHERE fnp=(SELECT name FROM filename WHERE fnid=%d)", |
| 198 | + aChng[i*2+1], aChng[i*2] |
| 199 | + ); |
| 200 | + } |
| 201 | + fossil_free(aChng); |
| 202 | + } |
| 203 | + |
| 204 | + /* Add files found in M |
| 205 | + */ |
| 137 | 206 | db_multi_exec( |
| 138 | | - "INSERT OR IGNORE INTO fv" |
| 139 | | - " SELECT pathname, 0, 0, 0, 0, 0, 0, 0 FROM vfile WHERE vid IN (%d,%d,%d);", |
| 140 | | - pid, vid, mid |
| 207 | + "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)" |
| 208 | + " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 " |
| 209 | + " FROM vfile" |
| 210 | + " WHERE vid=%d" |
| 211 | + " AND pathname NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)", |
| 212 | + mid |
| 141 | 213 | ); |
| 214 | + |
| 215 | + /* |
| 216 | + ** Compute the file version ids for V, P, and M. |
| 217 | + */ |
| 142 | 218 | db_multi_exec( |
| 143 | 219 | "UPDATE fv SET" |
| 144 | | - " idp=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fn),0)," |
| 145 | | - " ridp=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fn),0)," |
| 146 | | - " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fn),0)," |
| 147 | | - " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fn),0)," |
| 220 | + " idp=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnp),0)," |
| 221 | + " ridp=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnp),0)," |
| 222 | + " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnm),0)," |
| 223 | + " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnm),0)," |
| 148 | 224 | " idv=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fn),0)," |
| 149 | 225 | " ridv=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fn),0)," |
| 150 | 226 | " chnged=coalesce((SELECT chnged FROM vfile" |
| 151 | 227 | " WHERE vid=%d AND pathname=fn),0)", |
| 152 | 228 | pid, pid, mid, mid, vid, vid, vid |
| 153 | 229 | ); |
| 230 | + |
| 231 | + if( debugFlag ){ |
| 232 | + db_prepare(&q, |
| 233 | + "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm FROM fv" |
| 234 | + ); |
| 235 | + while( db_step(&q)==SQLITE_ROW ){ |
| 236 | + printf("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d\n", |
| 237 | + db_column_int(&q, 0), |
| 238 | + db_column_int(&q, 5), |
| 239 | + db_column_int(&q, 6), |
| 240 | + db_column_int(&q, 7), |
| 241 | + db_column_int(&q, 4)); |
| 242 | + printf(" fn = [%s]\n", db_column_text(&q, 1)); |
| 243 | + printf(" fnp = [%s]\n", db_column_text(&q, 2)); |
| 244 | + printf(" fnm = [%s]\n", db_column_text(&q, 3)); |
| 245 | + } |
| 246 | + db_finalize(&q); |
| 247 | + } |
| 154 | 248 | |
| 155 | 249 | /* |
| 156 | | - ** Find files in mid and vid but not in pid and report conflicts. |
| 157 | | - ** The file in mid will be ignored. It will be treated as if it |
| 250 | + ** Find files in M and V but not in P and report conflicts. |
| 251 | + ** The file in M will be ignored. It will be treated as if it |
| 158 | 252 | ** does not exist. |
| 159 | 253 | */ |
| 160 | 254 | db_prepare(&q, |
| 161 | 255 | "SELECT idm FROM fv WHERE idp=0 AND idv>0 AND idm>0" |
| 162 | 256 | ); |
| 163 | 257 | while( db_step(&q)==SQLITE_ROW ){ |
| 164 | 258 | int idm = db_column_int(&q, 0); |
| 165 | 259 | char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idm); |
| 166 | | - printf("WARNING: conflict on %s\n", zName); |
| 260 | + printf("WARNING - no common ancestor: %s\n", zName); |
| 167 | 261 | free(zName); |
| 168 | 262 | db_multi_exec("UPDATE fv SET idm=0 WHERE idm=%d", idm); |
| 169 | 263 | } |
| 170 | 264 | db_finalize(&q); |
| 171 | 265 | |
| 172 | 266 | /* |
| 173 | | - ** Add to vid files that are not in pid but are in mid |
| 267 | + ** Add to V files that are not in V or P but are in M |
| 174 | 268 | */ |
| 175 | 269 | db_prepare(&q, |
| 176 | | - "SELECT idm, rowid, fn FROM fv WHERE idp=0 AND idv=0 AND idm>0" |
| 270 | + "SELECT idm, rowid, fnm FROM fv AS x" |
| 271 | + " WHERE idp=0 AND idv=0 AND idm>0" |
| 177 | 272 | ); |
| 178 | 273 | while( db_step(&q)==SQLITE_ROW ){ |
| 179 | 274 | int idm = db_column_int(&q, 0); |
| 180 | 275 | int rowid = db_column_int(&q, 1); |
| 181 | 276 | int idv; |
| | @@ -195,40 +290,39 @@ |
| 195 | 290 | } |
| 196 | 291 | } |
| 197 | 292 | db_finalize(&q); |
| 198 | 293 | |
| 199 | 294 | /* |
| 200 | | - ** Find files that have changed from pid->mid but not pid->vid. |
| 201 | | - ** Copy the mid content over into vid. |
| 295 | + ** Find files that have changed from P->M but not P->V. |
| 296 | + ** Copy the M content over into V. |
| 202 | 297 | */ |
| 203 | 298 | db_prepare(&q, |
| 204 | | - "SELECT idv, ridm FROM fv" |
| 299 | + "SELECT idv, ridm, fn FROM fv" |
| 205 | 300 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 206 | 301 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 207 | 302 | ); |
| 208 | 303 | while( db_step(&q)==SQLITE_ROW ){ |
| 209 | 304 | int idv = db_column_int(&q, 0); |
| 210 | 305 | int ridm = db_column_int(&q, 1); |
| 211 | | - char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); |
| 306 | + const char *zName = db_column_text(&q, 2); |
| 212 | 307 | /* Copy content from idm over into idv. Overwrite idv. */ |
| 213 | 308 | printf("UPDATE %s\n", zName); |
| 214 | 309 | if( !nochangeFlag ){ |
| 215 | 310 | undo_save(zName); |
| 216 | 311 | db_multi_exec( |
| 217 | 312 | "UPDATE vfile SET mrid=%d, chnged=2 WHERE id=%d", ridm, idv |
| 218 | 313 | ); |
| 219 | 314 | vfile_to_disk(0, idv, 0, 0); |
| 220 | 315 | } |
| 221 | | - free(zName); |
| 222 | 316 | } |
| 223 | 317 | db_finalize(&q); |
| 224 | 318 | |
| 225 | 319 | /* |
| 226 | | - ** Do a three-way merge on files that have changes pid->mid and pid->vid |
| 320 | + ** Do a three-way merge on files that have changes on both P->M and P->V. |
| 227 | 321 | */ |
| 228 | 322 | db_prepare(&q, |
| 229 | | - "SELECT ridm, idv, ridp, ridv, %s FROM fv" |
| 323 | + "SELECT ridm, idv, ridp, ridv, %s, fn FROM fv" |
| 230 | 324 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 231 | 325 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 232 | 326 | glob_expr("fv.fn", zBinGlob) |
| 233 | 327 | ); |
| 234 | 328 | while( db_step(&q)==SQLITE_ROW ){ |
| | @@ -235,12 +329,12 @@ |
| 235 | 329 | int ridm = db_column_int(&q, 0); |
| 236 | 330 | int idv = db_column_int(&q, 1); |
| 237 | 331 | int ridp = db_column_int(&q, 2); |
| 238 | 332 | int ridv = db_column_int(&q, 3); |
| 239 | 333 | int isBinary = db_column_int(&q, 4); |
| 334 | + const char *zName = db_column_text(&q, 5); |
| 240 | 335 | int rc; |
| 241 | | - char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); |
| 242 | 336 | char *zFullPath; |
| 243 | 337 | Blob m, p, v, r; |
| 244 | 338 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 245 | 339 | if( detailFlag ){ |
| 246 | 340 | printf("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv); |
| | @@ -261,15 +355,16 @@ |
| 261 | 355 | } |
| 262 | 356 | if( rc>=0 ){ |
| 263 | 357 | if( !nochangeFlag ) blob_write_to_file(&r, zFullPath); |
| 264 | 358 | if( rc>0 ){ |
| 265 | 359 | printf("***** %d merge conflicts in %s\n", rc, zName); |
| 360 | + nConflict++; |
| 266 | 361 | } |
| 267 | 362 | }else{ |
| 268 | 363 | printf("***** Cannot merge binary file %s\n", zName); |
| 364 | + nConflict++; |
| 269 | 365 | } |
| 270 | | - free(zName); |
| 271 | 366 | blob_reset(&p); |
| 272 | 367 | blob_reset(&m); |
| 273 | 368 | blob_reset(&v); |
| 274 | 369 | blob_reset(&r); |
| 275 | 370 | db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)", |
| | @@ -276,33 +371,77 @@ |
| 276 | 371 | idv,ridm); |
| 277 | 372 | } |
| 278 | 373 | db_finalize(&q); |
| 279 | 374 | |
| 280 | 375 | /* |
| 281 | | - ** Drop files from vid that are in pid but not in mid |
| 376 | + ** Drop files that are in P and V but not in M |
| 282 | 377 | */ |
| 283 | 378 | db_prepare(&q, |
| 284 | | - "SELECT idv FROM fv" |
| 379 | + "SELECT idv, fn, chnged FROM fv" |
| 285 | 380 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 286 | 381 | ); |
| 287 | 382 | while( db_step(&q)==SQLITE_ROW ){ |
| 288 | 383 | int idv = db_column_int(&q, 0); |
| 289 | | - char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); |
| 384 | + const char *zName = db_column_text(&q, 1); |
| 385 | + int chnged = db_column_int(&q, 2); |
| 290 | 386 | /* Delete the file idv */ |
| 291 | 387 | printf("DELETE %s\n", zName); |
| 388 | + if( chnged ){ |
| 389 | + printf("WARNING: local edits lost for %s\n", zName); |
| 390 | + nConflict++; |
| 391 | + } |
| 292 | 392 | undo_save(zName); |
| 293 | 393 | db_multi_exec( |
| 294 | 394 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 295 | 395 | ); |
| 296 | 396 | if( !nochangeFlag ){ |
| 297 | 397 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 298 | 398 | unlink(zFullPath); |
| 299 | 399 | free(zFullPath); |
| 300 | 400 | } |
| 301 | | - free(zName); |
| 401 | + } |
| 402 | + db_finalize(&q); |
| 403 | + |
| 404 | + /* |
| 405 | + ** Rename files that have taken a rename on P->M but which keep the same |
| 406 | + ** name o P->V. If a file is renamed on P->V only or on both P->V and |
| 407 | + ** P->M then we retain the V name of the file. |
| 408 | + */ |
| 409 | + db_prepare(&q, |
| 410 | + "SELECT idv, fnp, fnm FROM fv" |
| 411 | + " WHERE idv>0 AND idp>0 AND idm>0 AND fnp=fn AND fnm!=fnp" |
| 412 | + ); |
| 413 | + while( db_step(&q)==SQLITE_ROW ){ |
| 414 | + int idv = db_column_int(&q, 0); |
| 415 | + const char *zOldName = db_column_text(&q, 1); |
| 416 | + const char *zNewName = db_column_text(&q, 2); |
| 417 | + printf("RENAME %s -> %s\n", zOldName, zNewName); |
| 418 | + undo_save(zOldName); |
| 419 | + undo_save(zNewName); |
| 420 | + db_multi_exec( |
| 421 | + "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 422 | + " WHERE id=%d AND vid=%d", zNewName, idv, vid |
| 423 | + ); |
| 424 | + if( !nochangeFlag ){ |
| 425 | + char *zFullOldPath = mprintf("%s%s", g.zLocalRoot, zOldName); |
| 426 | + char *zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); |
| 427 | + file_copy(zFullOldPath, zFullNewPath); |
| 428 | + unlink(zFullOldPath); |
| 429 | + free(zFullNewPath); |
| 430 | + free(zFullOldPath); |
| 431 | + } |
| 302 | 432 | } |
| 303 | 433 | db_finalize(&q); |
| 434 | + |
| 435 | + |
| 436 | + /* Report on conflicts |
| 437 | + */ |
| 438 | + if( nConflict ){ |
| 439 | + printf("WARNING: %d merge conflicts.\n" |
| 440 | + " ... Use \"fossil undo\" to back out this merge\n", |
| 441 | + nConflict); |
| 442 | + } |
| 304 | 443 | |
| 305 | 444 | /* |
| 306 | 445 | ** Clean up the mid and pid VFILE entries. Then commit the changes. |
| 307 | 446 | */ |
| 308 | 447 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); |
| 309 | 448 | |