dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Copyright (c) 2007 D. Richard Hipp dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ** This program is free software; you can redistribute it and/or dbda8d6ce9 2007-07-21 drh: ** modify it under the terms of the GNU General Public dbda8d6ce9 2007-07-21 drh: ** License version 2 as published by the Free Software Foundation. dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ** This program is distributed in the hope that it will be useful, dbda8d6ce9 2007-07-21 drh: ** but WITHOUT ANY WARRANTY; without even the implied warranty of dbda8d6ce9 2007-07-21 drh: ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dbda8d6ce9 2007-07-21 drh: ** General Public License for more details. dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ** You should have received a copy of the GNU General Public dbda8d6ce9 2007-07-21 drh: ** License along with this library; if not, write to the dbda8d6ce9 2007-07-21 drh: ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, dbda8d6ce9 2007-07-21 drh: ** Boston, MA 02111-1307, USA. dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ** Author contact information: dbda8d6ce9 2007-07-21 drh: ** drh@hwaci.com dbda8d6ce9 2007-07-21 drh: ** http://www.hwaci.com/drh/ dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ******************************************************************************* dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ** This file contains code used to merge two or more branches into dbda8d6ce9 2007-07-21 drh: ** a single tree. dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: #include "config.h" dbda8d6ce9 2007-07-21 drh: #include "merge.h" dbda8d6ce9 2007-07-21 drh: #include <assert.h> dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** COMMAND: merge dbda8d6ce9 2007-07-21 drh: ** 6607844a01 2007-08-18 drh: ** Usage: %fossil merge VERSION 6607844a01 2007-08-18 drh: ** dbda8d6ce9 2007-07-21 drh: ** The argument is a version that should be merged into the current dbda8d6ce9 2007-07-21 drh: ** checkout. dbda8d6ce9 2007-07-21 drh: ** dbda8d6ce9 2007-07-21 drh: ** Only file content is merged. The result continues to use the dbda8d6ce9 2007-07-21 drh: ** file and directory names from the current check-out even if those dbda8d6ce9 2007-07-21 drh: ** names might have been changed in the branch being merged in. dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: void merge_cmd(void){ dbda8d6ce9 2007-07-21 drh: int vid; /* Current version */ dbda8d6ce9 2007-07-21 drh: int mid; /* Version we are merging against */ dbda8d6ce9 2007-07-21 drh: int pid; /* The pivot version - most recent common ancestor */ dbda8d6ce9 2007-07-21 drh: Stmt q; 50ff86afd0 2007-11-08 drh: int detailFlag; dbda8d6ce9 2007-07-21 drh: 50ff86afd0 2007-11-08 drh: detailFlag = find_option("detail",0,0)!=0; dbda8d6ce9 2007-07-21 drh: if( g.argc!=3 ){ dbda8d6ce9 2007-07-21 drh: usage("VERSION"); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_must_be_within_tree(); dbda8d6ce9 2007-07-21 drh: vid = db_lget_int("checkout", 0); dbda8d6ce9 2007-07-21 drh: if( vid==0 ){ dbda8d6ce9 2007-07-21 drh: fossil_panic("nothing is checked out"); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: mid = name_to_rid(g.argv[2]); dbda8d6ce9 2007-07-21 drh: if( mid==0 ){ dbda8d6ce9 2007-07-21 drh: fossil_panic("not a version: %s", g.argv[2]); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){ dbda8d6ce9 2007-07-21 drh: fossil_panic("not a version: %s", g.argv[2]); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: pivot_set_primary(mid); dbda8d6ce9 2007-07-21 drh: pivot_set_secondary(vid); dbda8d6ce9 2007-07-21 drh: db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: pivot_set_secondary(db_column_int(&q,0)); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: pid = pivot_find(); dbda8d6ce9 2007-07-21 drh: if( pid<=0 ){ dbda8d6ce9 2007-07-21 drh: fossil_panic("cannot find a common ancestor between the current" dbda8d6ce9 2007-07-21 drh: "checkout and %s", g.argv[2]); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: if( pid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", pid) ){ dbda8d6ce9 2007-07-21 drh: fossil_panic("not a version: record #%d", mid); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: vfile_check_signature(vid); dbda8d6ce9 2007-07-21 drh: db_begin_transaction(); a36177bcce 2007-09-11 drh: undo_begin(); dbda8d6ce9 2007-07-21 drh: load_vfile_from_rid(mid); dbda8d6ce9 2007-07-21 drh: load_vfile_from_rid(pid); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** The vfile.pathname field is used to match files against each other. The dbda8d6ce9 2007-07-21 drh: ** FV table contains one row for each each unique filename in dbda8d6ce9 2007-07-21 drh: ** in the current checkout, the pivot, and the version being merged. dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "DROP TABLE IF EXISTS fv;" dbda8d6ce9 2007-07-21 drh: "CREATE TEMP TABLE fv(" dbda8d6ce9 2007-07-21 drh: " fn TEXT PRIMARY KEY," /* The filename */ dbda8d6ce9 2007-07-21 drh: " idv INTEGER," /* VFILE entry for current version */ dbda8d6ce9 2007-07-21 drh: " idp INTEGER," /* VFILE entry for the pivot */ dbda8d6ce9 2007-07-21 drh: " idm INTEGER," /* VFILE entry for version merging in */ dbda8d6ce9 2007-07-21 drh: " chnged BOOLEAN," /* True if current version has been edited */ dbda8d6ce9 2007-07-21 drh: " ridv INTEGER," /* Record ID for current version */ dbda8d6ce9 2007-07-21 drh: " ridp INTEGER," /* Record ID for pivot */ dbda8d6ce9 2007-07-21 drh: " ridm INTEGER" /* Record ID for merge */ dbda8d6ce9 2007-07-21 drh: ");" dbda8d6ce9 2007-07-21 drh: "INSERT OR IGNORE INTO fv" dbda8d6ce9 2007-07-21 drh: " SELECT pathname, 0, 0, 0, 0, 0, 0, 0 FROM vfile" dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT id, pathname, rid FROM vfile" dbda8d6ce9 2007-07-21 drh: " WHERE vid=%d", pid dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int id = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: const char *fn = db_column_text(&q, 1); dbda8d6ce9 2007-07-21 drh: int rid = db_column_int(&q, 2); dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "UPDATE fv SET idp=%d, ridp=%d WHERE fn=%Q", dbda8d6ce9 2007-07-21 drh: id, rid, fn dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT id, pathname, rid FROM vfile" dbda8d6ce9 2007-07-21 drh: " WHERE vid=%d", mid dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int id = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: const char *fn = db_column_text(&q, 1); dbda8d6ce9 2007-07-21 drh: int rid = db_column_int(&q, 2); dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "UPDATE fv SET idm=%d, ridm=%d WHERE fn=%Q", dbda8d6ce9 2007-07-21 drh: id, rid, fn dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT id, pathname, rid, chnged FROM vfile" dbda8d6ce9 2007-07-21 drh: " WHERE vid=%d", vid dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int id = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: const char *fn = db_column_text(&q, 1); dbda8d6ce9 2007-07-21 drh: int rid = db_column_int(&q, 2); dbda8d6ce9 2007-07-21 drh: int chnged = db_column_int(&q, 3); dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "UPDATE fv SET idv=%d, ridv=%d, chnged=%d WHERE fn=%Q", dbda8d6ce9 2007-07-21 drh: id, rid, chnged, fn dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Find files in mid and vid but not in pid and report conflicts. dbda8d6ce9 2007-07-21 drh: ** The file in mid will be ignored. It will be treated as if it dbda8d6ce9 2007-07-21 drh: ** does not exist. dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT idm FROM fv WHERE idp=0 AND idv>0 AND idm>0" dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int idm = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idm); dbda8d6ce9 2007-07-21 drh: printf("WARNING: conflict on %s\n", zName); dbda8d6ce9 2007-07-21 drh: free(zName); dbda8d6ce9 2007-07-21 drh: db_multi_exec("UPDATE fv SET idm=0 WHERE idm=%d", idm); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Add to vid files that are not in pid but are in mid dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT idm, rowid, fn FROM fv WHERE idp=0 AND idv=0 AND idm>0" dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int idm = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: int rowid = db_column_int(&q, 1); dbda8d6ce9 2007-07-21 drh: int idv; a36177bcce 2007-09-11 drh: const char *zName; dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,pathname)" dbda8d6ce9 2007-07-21 drh: " SELECT %d,3,0,rid,mrid,pathname FROM vfile WHERE id=%d", dbda8d6ce9 2007-07-21 drh: vid, idm dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: idv = db_last_insert_rowid(); dbda8d6ce9 2007-07-21 drh: db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid); a36177bcce 2007-09-11 drh: zName = db_column_text(&q, 2); a36177bcce 2007-09-11 drh: printf("ADDED %s\n", zName); a36177bcce 2007-09-11 drh: undo_save(zName); dbda8d6ce9 2007-07-21 drh: vfile_to_disk(0, idm, 0); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Find files that have changed from pid->mid but not pid->vid. dbda8d6ce9 2007-07-21 drh: ** Copy the mid content over into vid. dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT idv, ridm FROM fv" dbda8d6ce9 2007-07-21 drh: " WHERE idp>0 AND idv>0 AND idm>0" dbda8d6ce9 2007-07-21 drh: " AND ridm!=ridp AND ridv=ridp AND NOT chnged" dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int idv = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: int ridm = db_column_int(&q, 1); dbda8d6ce9 2007-07-21 drh: char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); dbda8d6ce9 2007-07-21 drh: /* Copy content from idm over into idv. Overwrite idv. */ dbda8d6ce9 2007-07-21 drh: printf("UPDATE %s\n", zName); a36177bcce 2007-09-11 drh: undo_save(zName); dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "UPDATE vfile SET mrid=%d, chnged=2 WHERE id=%d", ridm, idv dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: vfile_to_disk(0, idv, 0); dbda8d6ce9 2007-07-21 drh: free(zName); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Do a three-way merge on files that have changes pid->mid and pid->vid dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_prepare(&q, 50ff86afd0 2007-11-08 drh: "SELECT ridm, idv, ridp, ridv FROM fv" dbda8d6ce9 2007-07-21 drh: " WHERE idp>0 AND idv>0 AND idm>0" dbda8d6ce9 2007-07-21 drh: " AND ridm!=ridp AND (ridv!=ridp OR chnged)" dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int ridm = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: int idv = db_column_int(&q, 1); dbda8d6ce9 2007-07-21 drh: int ridp = db_column_int(&q, 2); 50ff86afd0 2007-11-08 drh: int ridv = db_column_int(&q, 3); 36b96b8616 2007-11-16 drh: int rc; dbda8d6ce9 2007-07-21 drh: char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); dbda8d6ce9 2007-07-21 drh: char *zFullPath; dbda8d6ce9 2007-07-21 drh: Blob m, p, v, r; dbda8d6ce9 2007-07-21 drh: /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ 50ff86afd0 2007-11-08 drh: if( detailFlag ){ 50ff86afd0 2007-11-08 drh: printf("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv); 50ff86afd0 2007-11-08 drh: }else{ 50ff86afd0 2007-11-08 drh: printf("MERGE %s\n", zName); 50ff86afd0 2007-11-08 drh: } a36177bcce 2007-09-11 drh: undo_save(zName); a36177bcce 2007-09-11 drh: zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); dbda8d6ce9 2007-07-21 drh: content_get(ridp, &p); dbda8d6ce9 2007-07-21 drh: content_get(ridm, &m); dbda8d6ce9 2007-07-21 drh: blob_zero(&v); dbda8d6ce9 2007-07-21 drh: blob_read_from_file(&v, zFullPath); 36b96b8616 2007-11-16 drh: rc = blob_merge(&p, &m, &v, &r); 36b96b8616 2007-11-16 drh: if( rc>=0 ){ 36b96b8616 2007-11-16 drh: blob_write_to_file(&r, zFullPath); 36b96b8616 2007-11-16 drh: if( rc>0 ){ 36b96b8616 2007-11-16 drh: printf("***** %d merge conflicts in %s\n", rc, zName); 36b96b8616 2007-11-16 drh: } 36b96b8616 2007-11-16 drh: }else{ 36b96b8616 2007-11-16 drh: printf("***** Cannot merge binary file %s\n", zName); 36b96b8616 2007-11-16 drh: } 36b96b8616 2007-11-16 drh: free(zName); dbda8d6ce9 2007-07-21 drh: blob_reset(&p); dbda8d6ce9 2007-07-21 drh: blob_reset(&m); dbda8d6ce9 2007-07-21 drh: blob_reset(&v); dbda8d6ce9 2007-07-21 drh: blob_reset(&r); 4c82c7773f 2007-08-30 drh: db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)", 4c82c7773f 2007-08-30 drh: idv,ridm); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Drop files from vid that are in pid but not in mid dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_prepare(&q, dbda8d6ce9 2007-07-21 drh: "SELECT idv FROM fv" dbda8d6ce9 2007-07-21 drh: " WHERE idp>0 AND idv>0 AND idm=0" dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: while( db_step(&q)==SQLITE_ROW ){ dbda8d6ce9 2007-07-21 drh: int idv = db_column_int(&q, 0); dbda8d6ce9 2007-07-21 drh: char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); dbda8d6ce9 2007-07-21 drh: /* Delete the file idv */ dbda8d6ce9 2007-07-21 drh: printf("DELETE %s\n", zName); a36177bcce 2007-09-11 drh: undo_save(zName); dbda8d6ce9 2007-07-21 drh: db_multi_exec( dbda8d6ce9 2007-07-21 drh: "UPDATE vfile SET deleted=1 WHERE id=%d", idv dbda8d6ce9 2007-07-21 drh: ); dbda8d6ce9 2007-07-21 drh: free(zName); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: db_finalize(&q); dbda8d6ce9 2007-07-21 drh: dbda8d6ce9 2007-07-21 drh: /* dbda8d6ce9 2007-07-21 drh: ** Clean up the mid and pid VFILE entries. Then commit the changes. dbda8d6ce9 2007-07-21 drh: */ dbda8d6ce9 2007-07-21 drh: db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); 4c82c7773f 2007-08-30 drh: db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid); dbda8d6ce9 2007-07-21 drh: db_end_transaction(0); dbda8d6ce9 2007-07-21 drh: }