Fossil SCM
Record information about merge operations in the localdb.mergestat table. Make that information available using the merge-info command.
Commit
625ff9d574307c3eb31b05fe95df81b178a4ee34fb07f883e48f6c5f03f98e97
Parent
7b8be85206d9940…
1 file changed
+173
-6
+173
-6
| --- src/merge.c | ||
| +++ src/merge.c | ||
| @@ -19,10 +19,87 @@ | ||
| 19 | 19 | ** a single tree. |
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "merge.h" |
| 23 | 23 | #include <assert.h> |
| 24 | + | |
| 25 | +/* | |
| 26 | +** COMMAND: merge-info | |
| 27 | +** | |
| 28 | +** Display information about the most recent merge operation. | |
| 29 | +** | |
| 30 | +** Right now, this command basically just dumps the localdb.mergestat | |
| 31 | +** table. The plan moving forward is that it can generate data for | |
| 32 | +** a Tk-based GUI to show the details of the merge. This command is | |
| 33 | +** a work-in-progress. | |
| 34 | +*/ | |
| 35 | +void merge_info_cmd(void){ | |
| 36 | + Stmt q; | |
| 37 | + verify_all_options(); | |
| 38 | + db_must_be_within_tree(); | |
| 39 | + | |
| 40 | + if( !db_table_exists("localdb","mergestat") ){ | |
| 41 | + return; | |
| 42 | + } | |
| 43 | + db_prepare(&q, | |
| 44 | + /* 0 1 2 3 4 */ | |
| 45 | + "SELECT op, fn, fnr, nc, msg FROM mergestat ORDER BY coalesce(fnr,fn)" | |
| 46 | + ); | |
| 47 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 48 | + const char *zOp = db_column_text(&q, 0); | |
| 49 | + const char *zName = db_column_text(&q, 2); | |
| 50 | + const char *zErr = db_column_text(&q, 4); | |
| 51 | + if( zName==0 ) zName = db_column_text(&q, 1); | |
| 52 | + if( zErr ){ | |
| 53 | + fossil_print("%-7s %s ** %s **\n", zOp, zName, zErr); | |
| 54 | + }else{ | |
| 55 | + fossil_print("%-7s %s\n", zOp, zName); | |
| 56 | + } | |
| 57 | + } | |
| 58 | + db_finalize(&q); | |
| 59 | +} | |
| 60 | + | |
| 61 | +/* | |
| 62 | +** Erase all information about prior merges. Do this, for example, after | |
| 63 | +** a commit. | |
| 64 | +*/ | |
| 65 | +void merge_info_forget(void){ | |
| 66 | + db_multi_exec("DROP TABLE IF EXISTS localdb.mergestat"); | |
| 67 | +} | |
| 68 | + | |
| 69 | + | |
| 70 | +/* | |
| 71 | +** Initialize the MERGESTAT table. | |
| 72 | +** | |
| 73 | +** Notes about mergestat: | |
| 74 | +** | |
| 75 | +** * ridv is a positive integer and sz is NULL if the V file contained | |
| 76 | +** no local edits prior to the merge. If the V file was modified prior | |
| 77 | +** to the merge then ridv is NULL and sz is the size of the file prior | |
| 78 | +** to merge. | |
| 79 | +** | |
| 80 | +** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was | |
| 81 | +** added by merge. | |
| 82 | +*/ | |
| 83 | +void merge_info_init(void){ | |
| 84 | + db_multi_exec( | |
| 85 | + "DROP TABLE IF EXISTS localdb.mergestat;\n" | |
| 86 | + "CREATE TABLE localdb.mergestat(\n" | |
| 87 | + " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n" | |
| 88 | + " fnp TEXT, -- Name of the pivot file (P)\n" | |
| 89 | + " ridp INT, -- RID for the pivot file\n" | |
| 90 | + " fn TEXT, -- Name of origin file (V)\n" | |
| 91 | + " ridv INT, -- RID for origin file, or NULL if previously edited\n" | |
| 92 | + " sz INT, -- Size of origin file in bytes, NULL if unedited\n" | |
| 93 | + " fnm TEXT, -- Name of the file being merged in (M)\n" | |
| 94 | + " ridm INT, -- RID for the merge-in file\n" | |
| 95 | + " fnr TEXT, -- Name of the final output file, after all renaming\n" | |
| 96 | + " nc INT DEFAULT 0, -- Number of conflicts\n" | |
| 97 | + " msg TEXT -- Error message\n" | |
| 98 | + ");" | |
| 99 | + ); | |
| 100 | +} | |
| 24 | 101 | |
| 25 | 102 | /* |
| 26 | 103 | ** Print information about a particular check-in. |
| 27 | 104 | */ |
| 28 | 105 | void print_checkin_description(int rid, int indent, const char *zLabel){ |
| @@ -323,11 +400,11 @@ | ||
| 323 | 400 | ** --force-missing Force the merge even if there is missing content |
| 324 | 401 | ** --integrate Merged branch will be closed when committing |
| 325 | 402 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 326 | 403 | ** used for merging, named *-baseline, *-original, |
| 327 | 404 | ** and *-merge. |
| 328 | -** -n|--dry-run If given, display instead of run actions | |
| 405 | +** -n|--dry-run Do not actually change files on disk | |
| 329 | 406 | ** --nosync Do not auto-sync prior to merging |
| 330 | 407 | ** -v|--verbose Show additional details of the merge |
| 331 | 408 | */ |
| 332 | 409 | void merge_cmd(void){ |
| 333 | 410 | int vid; /* Current version "V" */ |
| @@ -800,15 +877,21 @@ | ||
| 800 | 877 | |
| 801 | 878 | /************************************************************************ |
| 802 | 879 | ** All of the information needed to do the merge is now contained in the |
| 803 | 880 | ** FV table. Starting here, we begin to actually carry out the merge. |
| 804 | 881 | ** |
| 805 | - ** First, find files that have changed from P->M but not P->V. | |
| 882 | + ** Begin by constructing the localdb.mergestat table. | |
| 883 | + */ | |
| 884 | + merge_info_init(); | |
| 885 | + | |
| 886 | + /* | |
| 887 | + ** Find files that have changed from P->M but not P->V. | |
| 806 | 888 | ** Copy the M content over into V. |
| 807 | 889 | */ |
| 808 | 890 | db_prepare(&q, |
| 809 | - "SELECT idv, ridm, fn, islinkm FROM fv" | |
| 891 | + /* 0 1 2 3 4 5 6 7 */ | |
| 892 | + "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv" | |
| 810 | 893 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 811 | 894 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 812 | 895 | ); |
| 813 | 896 | while( db_step(&q)==SQLITE_ROW ){ |
| 814 | 897 | int idv = db_column_int(&q, 0); |
| @@ -825,10 +908,21 @@ | ||
| 825 | 908 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 826 | 909 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| 827 | 910 | ); |
| 828 | 911 | vfile_to_disk(0, idv, 0, 0); |
| 829 | 912 | } |
| 913 | + db_multi_exec( | |
| 914 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)" | |
| 915 | + "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)", | |
| 916 | + /* fnp */ db_column_text(&q, 4), | |
| 917 | + /* ridp */ db_column_int(&q,5), | |
| 918 | + /* fn */ zName, | |
| 919 | + /* ridv */ db_column_int(&q,6), | |
| 920 | + /* fnm */ db_column_text(&q, 7), | |
| 921 | + /* ridm */ ridm, | |
| 922 | + /* fnr */ zName | |
| 923 | + ); | |
| 830 | 924 | } |
| 831 | 925 | db_finalize(&q); |
| 832 | 926 | |
| 833 | 927 | /* |
| 834 | 928 | ** Do a three-way merge on files that have changes on both P->M and P->V. |
| @@ -836,11 +930,15 @@ | ||
| 836 | 930 | ** Proceed even if the file doesn't exist on P, just like the common ancestor |
| 837 | 931 | ** of M and V is an empty file. In this case, merge conflict marks will be |
| 838 | 932 | ** added to the file and user will be forced to take a decision. |
| 839 | 933 | */ |
| 840 | 934 | db_prepare(&q, |
| 841 | - "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" | |
| 935 | + /* 0 1 2 3 4 5 6 7 8 */ | |
| 936 | + "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm," | |
| 937 | + /* 9 10 11 */ | |
| 938 | + " fnp, fnm, chnged" | |
| 939 | + " FROM fv" | |
| 842 | 940 | " WHERE idv>0 AND idm>0" |
| 843 | 941 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 844 | 942 | glob_expr("fv.fn", zBinGlob) |
| 845 | 943 | ); |
| 846 | 944 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -851,10 +949,11 @@ | ||
| 851 | 949 | int isBinary = db_column_int(&q, 4); |
| 852 | 950 | const char *zName = db_column_text(&q, 5); |
| 853 | 951 | int isExe = db_column_int(&q, 6); |
| 854 | 952 | int islinkv = db_column_int(&q, 7); |
| 855 | 953 | int islinkm = db_column_int(&q, 8); |
| 954 | + int chnged = db_column_int(&q, 11); | |
| 856 | 955 | int rc; |
| 857 | 956 | char *zFullPath; |
| 858 | 957 | Blob m, p, r; |
| 859 | 958 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 860 | 959 | if( verboseFlag ){ |
| @@ -864,13 +963,29 @@ | ||
| 864 | 963 | fossil_print("MERGE %s\n", zName); |
| 865 | 964 | } |
| 866 | 965 | if( islinkv || islinkm ){ |
| 867 | 966 | fossil_print("***** Cannot merge symlink %s\n", zName); |
| 868 | 967 | nConflict++; |
| 968 | + db_multi_exec( | |
| 969 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)" | |
| 970 | + "VALUES('MERGE',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')", | |
| 971 | + /* fnp */ db_column_text(&q, 9), | |
| 972 | + /* ridp */ ridp, | |
| 973 | + /* fn */ zName, | |
| 974 | + /* ridv */ ridv, | |
| 975 | + /* fnm */ db_column_text(&q, 10), | |
| 976 | + /* ridm */ ridm, | |
| 977 | + /* fnr */ zName | |
| 978 | + ); | |
| 869 | 979 | }else{ |
| 980 | + i64 sz; | |
| 981 | + const char *zErrMsg = 0; | |
| 982 | + int nc = 0; | |
| 983 | + | |
| 870 | 984 | if( !dryRunFlag ) undo_save(zName); |
| 871 | 985 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 986 | + sz = file_size(zFullPath, ExtFILE); | |
| 872 | 987 | content_get(ridp, &p); |
| 873 | 988 | content_get(ridm, &m); |
| 874 | 989 | if( isBinary ){ |
| 875 | 990 | rc = -1; |
| 876 | 991 | blob_zero(&r); |
| @@ -887,15 +1002,35 @@ | ||
| 887 | 1002 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
| 888 | 1003 | if( rc>0 ){ |
| 889 | 1004 | fossil_print("***** %d merge conflict%s in %s\n", |
| 890 | 1005 | rc, rc>1 ? "s" : "", zName); |
| 891 | 1006 | nConflict++; |
| 1007 | + nc = rc; | |
| 1008 | + zErrMsg = "merge conflicts"; | |
| 892 | 1009 | } |
| 893 | 1010 | }else{ |
| 894 | 1011 | fossil_print("***** Cannot merge binary file %s\n", zName); |
| 895 | 1012 | nConflict++; |
| 1013 | + nc = 1; | |
| 1014 | + zErrMsg = "cannot merge binary file"; | |
| 896 | 1015 | } |
| 1016 | + db_multi_exec( | |
| 1017 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" | |
| 1018 | + "VALUES('MERGE',%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),%Q,%d," | |
| 1019 | + "%Q,%d,%Q)", | |
| 1020 | + /* fnp */ db_column_text(&q, 9), | |
| 1021 | + /* ridp */ ridp, | |
| 1022 | + /* fn */ zName, | |
| 1023 | + /* ridv */ chnged==0, ridv, | |
| 1024 | + /* sz */ chnged!=0, sz, | |
| 1025 | + /* fnm */ db_column_text(&q, 10), | |
| 1026 | + /* ridm */ ridm, | |
| 1027 | + /* fnr */ zName, | |
| 1028 | + /* nc */ nc, | |
| 1029 | + /* msg */ zErrMsg | |
| 1030 | + ); | |
| 1031 | + fossil_free(zFullPath); | |
| 897 | 1032 | blob_reset(&p); |
| 898 | 1033 | blob_reset(&m); |
| 899 | 1034 | blob_reset(&r); |
| 900 | 1035 | } |
| 901 | 1036 | vmerge_insert(idv, ridm); |
| @@ -904,22 +1039,33 @@ | ||
| 904 | 1039 | |
| 905 | 1040 | /* |
| 906 | 1041 | ** Drop files that are in P and V but not in M |
| 907 | 1042 | */ |
| 908 | 1043 | db_prepare(&q, |
| 909 | - "SELECT idv, fn, chnged FROM fv" | |
| 1044 | + "SELECT idv, fn, chnged, ridv FROM fv" | |
| 910 | 1045 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 911 | 1046 | ); |
| 912 | 1047 | while( db_step(&q)==SQLITE_ROW ){ |
| 913 | 1048 | int idv = db_column_int(&q, 0); |
| 914 | 1049 | const char *zName = db_column_text(&q, 1); |
| 915 | 1050 | int chnged = db_column_int(&q, 2); |
| 1051 | + int ridv = db_column_int(&q, 3); | |
| 1052 | + int sz = -1; | |
| 1053 | + const char *zErrMsg = 0; | |
| 1054 | + int nc = 0; | |
| 916 | 1055 | /* Delete the file idv */ |
| 917 | 1056 | fossil_print("DELETE %s\n", zName); |
| 918 | 1057 | if( chnged ){ |
| 1058 | + char *zFullPath; | |
| 919 | 1059 | fossil_warning("WARNING: local edits lost for %s", zName); |
| 920 | 1060 | nConflict++; |
| 1061 | + ridv = 0; | |
| 1062 | + nc = 1; | |
| 1063 | + zErrMsg = "local edits lost"; | |
| 1064 | + zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); | |
| 1065 | + sz = file_size(zFullPath, ExtFILE); | |
| 1066 | + fossil_free(zFullPath); | |
| 921 | 1067 | } |
| 922 | 1068 | if( !dryRunFlag ) undo_save(zName); |
| 923 | 1069 | db_multi_exec( |
| 924 | 1070 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 925 | 1071 | ); |
| @@ -926,10 +1072,20 @@ | ||
| 926 | 1072 | if( !dryRunFlag ){ |
| 927 | 1073 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 928 | 1074 | file_delete(zFullPath); |
| 929 | 1075 | free(zFullPath); |
| 930 | 1076 | } |
| 1077 | + db_multi_exec( | |
| 1078 | + "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)" | |
| 1079 | + "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL)," | |
| 1080 | + "NULL,NULL,%d,%Q)", | |
| 1081 | + /* fn */ zName, | |
| 1082 | + /* ridv */ chnged==0, ridv, | |
| 1083 | + /* sz */ chnged!=0, sz, | |
| 1084 | + /* nc */ nc, | |
| 1085 | + /* msg */ zErrMsg | |
| 1086 | + ); | |
| 931 | 1087 | } |
| 932 | 1088 | db_finalize(&q); |
| 933 | 1089 | |
| 934 | 1090 | /* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
| 935 | 1091 | ** being renamed must first be moved to a temporary location to avoid |
| @@ -956,10 +1112,14 @@ | ||
| 956 | 1112 | const char *zNewName = db_column_text(&q, 2); |
| 957 | 1113 | int isExe = db_column_int(&q, 3); |
| 958 | 1114 | fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
| 959 | 1115 | if( !dryRunFlag ) undo_save(zOldName); |
| 960 | 1116 | if( !dryRunFlag ) undo_save(zNewName); |
| 1117 | + db_multi_exec( | |
| 1118 | + "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q", | |
| 1119 | + zOldName | |
| 1120 | + ); | |
| 961 | 1121 | db_multi_exec( |
| 962 | 1122 | "UPDATE vfile SET pathname=NULL, origname=pathname" |
| 963 | 1123 | " WHERE vid=%d AND pathname=%Q;" |
| 964 | 1124 | "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 965 | 1125 | " WHERE id=%d;", |
| @@ -1009,11 +1169,11 @@ | ||
| 1009 | 1169 | |
| 1010 | 1170 | /* |
| 1011 | 1171 | ** Insert into V any files that are not in V or P but are in M. |
| 1012 | 1172 | */ |
| 1013 | 1173 | db_prepare(&q, |
| 1014 | - "SELECT idm, fnm FROM fv" | |
| 1174 | + "SELECT idm, fnm, ridm FROM fv" | |
| 1015 | 1175 | " WHERE idp=0 AND idv=0 AND idm>0" |
| 1016 | 1176 | ); |
| 1017 | 1177 | while( db_step(&q)==SQLITE_ROW ){ |
| 1018 | 1178 | int idm = db_column_int(&q, 0); |
| 1019 | 1179 | const char *zName; |
| @@ -1042,10 +1202,17 @@ | ||
| 1042 | 1202 | nOverwrite++; |
| 1043 | 1203 | }else{ |
| 1044 | 1204 | fossil_print("ADDED %s\n", zName); |
| 1045 | 1205 | } |
| 1046 | 1206 | fossil_free(zFullName); |
| 1207 | + db_multi_exec( | |
| 1208 | + "INSERT INTO mergestat(op,fnm,ridm,fnr)" | |
| 1209 | + "VALUES('ADDED',%Q,%d,%Q)", | |
| 1210 | + /* fnm */ zName, | |
| 1211 | + /* ridm */ db_column_int(&q,2), | |
| 1212 | + /* fnr */ zName | |
| 1213 | + ); | |
| 1047 | 1214 | if( !dryRunFlag ){ |
| 1048 | 1215 | undo_save(zName); |
| 1049 | 1216 | vfile_to_disk(0, idm, 0, 0); |
| 1050 | 1217 | } |
| 1051 | 1218 | } |
| 1052 | 1219 |
| --- src/merge.c | |
| +++ src/merge.c | |
| @@ -19,10 +19,87 @@ | |
| 19 | ** a single tree. |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "merge.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | /* |
| 26 | ** Print information about a particular check-in. |
| 27 | */ |
| 28 | void print_checkin_description(int rid, int indent, const char *zLabel){ |
| @@ -323,11 +400,11 @@ | |
| 323 | ** --force-missing Force the merge even if there is missing content |
| 324 | ** --integrate Merged branch will be closed when committing |
| 325 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 326 | ** used for merging, named *-baseline, *-original, |
| 327 | ** and *-merge. |
| 328 | ** -n|--dry-run If given, display instead of run actions |
| 329 | ** --nosync Do not auto-sync prior to merging |
| 330 | ** -v|--verbose Show additional details of the merge |
| 331 | */ |
| 332 | void merge_cmd(void){ |
| 333 | int vid; /* Current version "V" */ |
| @@ -800,15 +877,21 @@ | |
| 800 | |
| 801 | /************************************************************************ |
| 802 | ** All of the information needed to do the merge is now contained in the |
| 803 | ** FV table. Starting here, we begin to actually carry out the merge. |
| 804 | ** |
| 805 | ** First, find files that have changed from P->M but not P->V. |
| 806 | ** Copy the M content over into V. |
| 807 | */ |
| 808 | db_prepare(&q, |
| 809 | "SELECT idv, ridm, fn, islinkm FROM fv" |
| 810 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 811 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 812 | ); |
| 813 | while( db_step(&q)==SQLITE_ROW ){ |
| 814 | int idv = db_column_int(&q, 0); |
| @@ -825,10 +908,21 @@ | |
| 825 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 826 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| 827 | ); |
| 828 | vfile_to_disk(0, idv, 0, 0); |
| 829 | } |
| 830 | } |
| 831 | db_finalize(&q); |
| 832 | |
| 833 | /* |
| 834 | ** Do a three-way merge on files that have changes on both P->M and P->V. |
| @@ -836,11 +930,15 @@ | |
| 836 | ** Proceed even if the file doesn't exist on P, just like the common ancestor |
| 837 | ** of M and V is an empty file. In this case, merge conflict marks will be |
| 838 | ** added to the file and user will be forced to take a decision. |
| 839 | */ |
| 840 | db_prepare(&q, |
| 841 | "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" |
| 842 | " WHERE idv>0 AND idm>0" |
| 843 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 844 | glob_expr("fv.fn", zBinGlob) |
| 845 | ); |
| 846 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -851,10 +949,11 @@ | |
| 851 | int isBinary = db_column_int(&q, 4); |
| 852 | const char *zName = db_column_text(&q, 5); |
| 853 | int isExe = db_column_int(&q, 6); |
| 854 | int islinkv = db_column_int(&q, 7); |
| 855 | int islinkm = db_column_int(&q, 8); |
| 856 | int rc; |
| 857 | char *zFullPath; |
| 858 | Blob m, p, r; |
| 859 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 860 | if( verboseFlag ){ |
| @@ -864,13 +963,29 @@ | |
| 864 | fossil_print("MERGE %s\n", zName); |
| 865 | } |
| 866 | if( islinkv || islinkm ){ |
| 867 | fossil_print("***** Cannot merge symlink %s\n", zName); |
| 868 | nConflict++; |
| 869 | }else{ |
| 870 | if( !dryRunFlag ) undo_save(zName); |
| 871 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 872 | content_get(ridp, &p); |
| 873 | content_get(ridm, &m); |
| 874 | if( isBinary ){ |
| 875 | rc = -1; |
| 876 | blob_zero(&r); |
| @@ -887,15 +1002,35 @@ | |
| 887 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
| 888 | if( rc>0 ){ |
| 889 | fossil_print("***** %d merge conflict%s in %s\n", |
| 890 | rc, rc>1 ? "s" : "", zName); |
| 891 | nConflict++; |
| 892 | } |
| 893 | }else{ |
| 894 | fossil_print("***** Cannot merge binary file %s\n", zName); |
| 895 | nConflict++; |
| 896 | } |
| 897 | blob_reset(&p); |
| 898 | blob_reset(&m); |
| 899 | blob_reset(&r); |
| 900 | } |
| 901 | vmerge_insert(idv, ridm); |
| @@ -904,22 +1039,33 @@ | |
| 904 | |
| 905 | /* |
| 906 | ** Drop files that are in P and V but not in M |
| 907 | */ |
| 908 | db_prepare(&q, |
| 909 | "SELECT idv, fn, chnged FROM fv" |
| 910 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 911 | ); |
| 912 | while( db_step(&q)==SQLITE_ROW ){ |
| 913 | int idv = db_column_int(&q, 0); |
| 914 | const char *zName = db_column_text(&q, 1); |
| 915 | int chnged = db_column_int(&q, 2); |
| 916 | /* Delete the file idv */ |
| 917 | fossil_print("DELETE %s\n", zName); |
| 918 | if( chnged ){ |
| 919 | fossil_warning("WARNING: local edits lost for %s", zName); |
| 920 | nConflict++; |
| 921 | } |
| 922 | if( !dryRunFlag ) undo_save(zName); |
| 923 | db_multi_exec( |
| 924 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 925 | ); |
| @@ -926,10 +1072,20 @@ | |
| 926 | if( !dryRunFlag ){ |
| 927 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 928 | file_delete(zFullPath); |
| 929 | free(zFullPath); |
| 930 | } |
| 931 | } |
| 932 | db_finalize(&q); |
| 933 | |
| 934 | /* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
| 935 | ** being renamed must first be moved to a temporary location to avoid |
| @@ -956,10 +1112,14 @@ | |
| 956 | const char *zNewName = db_column_text(&q, 2); |
| 957 | int isExe = db_column_int(&q, 3); |
| 958 | fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
| 959 | if( !dryRunFlag ) undo_save(zOldName); |
| 960 | if( !dryRunFlag ) undo_save(zNewName); |
| 961 | db_multi_exec( |
| 962 | "UPDATE vfile SET pathname=NULL, origname=pathname" |
| 963 | " WHERE vid=%d AND pathname=%Q;" |
| 964 | "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 965 | " WHERE id=%d;", |
| @@ -1009,11 +1169,11 @@ | |
| 1009 | |
| 1010 | /* |
| 1011 | ** Insert into V any files that are not in V or P but are in M. |
| 1012 | */ |
| 1013 | db_prepare(&q, |
| 1014 | "SELECT idm, fnm FROM fv" |
| 1015 | " WHERE idp=0 AND idv=0 AND idm>0" |
| 1016 | ); |
| 1017 | while( db_step(&q)==SQLITE_ROW ){ |
| 1018 | int idm = db_column_int(&q, 0); |
| 1019 | const char *zName; |
| @@ -1042,10 +1202,17 @@ | |
| 1042 | nOverwrite++; |
| 1043 | }else{ |
| 1044 | fossil_print("ADDED %s\n", zName); |
| 1045 | } |
| 1046 | fossil_free(zFullName); |
| 1047 | if( !dryRunFlag ){ |
| 1048 | undo_save(zName); |
| 1049 | vfile_to_disk(0, idm, 0, 0); |
| 1050 | } |
| 1051 | } |
| 1052 |
| --- src/merge.c | |
| +++ src/merge.c | |
| @@ -19,10 +19,87 @@ | |
| 19 | ** a single tree. |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "merge.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | /* |
| 26 | ** COMMAND: merge-info |
| 27 | ** |
| 28 | ** Display information about the most recent merge operation. |
| 29 | ** |
| 30 | ** Right now, this command basically just dumps the localdb.mergestat |
| 31 | ** table. The plan moving forward is that it can generate data for |
| 32 | ** a Tk-based GUI to show the details of the merge. This command is |
| 33 | ** a work-in-progress. |
| 34 | */ |
| 35 | void merge_info_cmd(void){ |
| 36 | Stmt q; |
| 37 | verify_all_options(); |
| 38 | db_must_be_within_tree(); |
| 39 | |
| 40 | if( !db_table_exists("localdb","mergestat") ){ |
| 41 | return; |
| 42 | } |
| 43 | db_prepare(&q, |
| 44 | /* 0 1 2 3 4 */ |
| 45 | "SELECT op, fn, fnr, nc, msg FROM mergestat ORDER BY coalesce(fnr,fn)" |
| 46 | ); |
| 47 | while( db_step(&q)==SQLITE_ROW ){ |
| 48 | const char *zOp = db_column_text(&q, 0); |
| 49 | const char *zName = db_column_text(&q, 2); |
| 50 | const char *zErr = db_column_text(&q, 4); |
| 51 | if( zName==0 ) zName = db_column_text(&q, 1); |
| 52 | if( zErr ){ |
| 53 | fossil_print("%-7s %s ** %s **\n", zOp, zName, zErr); |
| 54 | }else{ |
| 55 | fossil_print("%-7s %s\n", zOp, zName); |
| 56 | } |
| 57 | } |
| 58 | db_finalize(&q); |
| 59 | } |
| 60 | |
| 61 | /* |
| 62 | ** Erase all information about prior merges. Do this, for example, after |
| 63 | ** a commit. |
| 64 | */ |
| 65 | void merge_info_forget(void){ |
| 66 | db_multi_exec("DROP TABLE IF EXISTS localdb.mergestat"); |
| 67 | } |
| 68 | |
| 69 | |
| 70 | /* |
| 71 | ** Initialize the MERGESTAT table. |
| 72 | ** |
| 73 | ** Notes about mergestat: |
| 74 | ** |
| 75 | ** * ridv is a positive integer and sz is NULL if the V file contained |
| 76 | ** no local edits prior to the merge. If the V file was modified prior |
| 77 | ** to the merge then ridv is NULL and sz is the size of the file prior |
| 78 | ** to merge. |
| 79 | ** |
| 80 | ** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was |
| 81 | ** added by merge. |
| 82 | */ |
| 83 | void merge_info_init(void){ |
| 84 | db_multi_exec( |
| 85 | "DROP TABLE IF EXISTS localdb.mergestat;\n" |
| 86 | "CREATE TABLE localdb.mergestat(\n" |
| 87 | " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n" |
| 88 | " fnp TEXT, -- Name of the pivot file (P)\n" |
| 89 | " ridp INT, -- RID for the pivot file\n" |
| 90 | " fn TEXT, -- Name of origin file (V)\n" |
| 91 | " ridv INT, -- RID for origin file, or NULL if previously edited\n" |
| 92 | " sz INT, -- Size of origin file in bytes, NULL if unedited\n" |
| 93 | " fnm TEXT, -- Name of the file being merged in (M)\n" |
| 94 | " ridm INT, -- RID for the merge-in file\n" |
| 95 | " fnr TEXT, -- Name of the final output file, after all renaming\n" |
| 96 | " nc INT DEFAULT 0, -- Number of conflicts\n" |
| 97 | " msg TEXT -- Error message\n" |
| 98 | ");" |
| 99 | ); |
| 100 | } |
| 101 | |
| 102 | /* |
| 103 | ** Print information about a particular check-in. |
| 104 | */ |
| 105 | void print_checkin_description(int rid, int indent, const char *zLabel){ |
| @@ -323,11 +400,11 @@ | |
| 400 | ** --force-missing Force the merge even if there is missing content |
| 401 | ** --integrate Merged branch will be closed when committing |
| 402 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 403 | ** used for merging, named *-baseline, *-original, |
| 404 | ** and *-merge. |
| 405 | ** -n|--dry-run Do not actually change files on disk |
| 406 | ** --nosync Do not auto-sync prior to merging |
| 407 | ** -v|--verbose Show additional details of the merge |
| 408 | */ |
| 409 | void merge_cmd(void){ |
| 410 | int vid; /* Current version "V" */ |
| @@ -800,15 +877,21 @@ | |
| 877 | |
| 878 | /************************************************************************ |
| 879 | ** All of the information needed to do the merge is now contained in the |
| 880 | ** FV table. Starting here, we begin to actually carry out the merge. |
| 881 | ** |
| 882 | ** Begin by constructing the localdb.mergestat table. |
| 883 | */ |
| 884 | merge_info_init(); |
| 885 | |
| 886 | /* |
| 887 | ** Find files that have changed from P->M but not P->V. |
| 888 | ** Copy the M content over into V. |
| 889 | */ |
| 890 | db_prepare(&q, |
| 891 | /* 0 1 2 3 4 5 6 7 */ |
| 892 | "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv" |
| 893 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 894 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 895 | ); |
| 896 | while( db_step(&q)==SQLITE_ROW ){ |
| 897 | int idv = db_column_int(&q, 0); |
| @@ -825,10 +908,21 @@ | |
| 908 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 909 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| 910 | ); |
| 911 | vfile_to_disk(0, idv, 0, 0); |
| 912 | } |
| 913 | db_multi_exec( |
| 914 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)" |
| 915 | "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)", |
| 916 | /* fnp */ db_column_text(&q, 4), |
| 917 | /* ridp */ db_column_int(&q,5), |
| 918 | /* fn */ zName, |
| 919 | /* ridv */ db_column_int(&q,6), |
| 920 | /* fnm */ db_column_text(&q, 7), |
| 921 | /* ridm */ ridm, |
| 922 | /* fnr */ zName |
| 923 | ); |
| 924 | } |
| 925 | db_finalize(&q); |
| 926 | |
| 927 | /* |
| 928 | ** Do a three-way merge on files that have changes on both P->M and P->V. |
| @@ -836,11 +930,15 @@ | |
| 930 | ** Proceed even if the file doesn't exist on P, just like the common ancestor |
| 931 | ** of M and V is an empty file. In this case, merge conflict marks will be |
| 932 | ** added to the file and user will be forced to take a decision. |
| 933 | */ |
| 934 | db_prepare(&q, |
| 935 | /* 0 1 2 3 4 5 6 7 8 */ |
| 936 | "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm," |
| 937 | /* 9 10 11 */ |
| 938 | " fnp, fnm, chnged" |
| 939 | " FROM fv" |
| 940 | " WHERE idv>0 AND idm>0" |
| 941 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 942 | glob_expr("fv.fn", zBinGlob) |
| 943 | ); |
| 944 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -851,10 +949,11 @@ | |
| 949 | int isBinary = db_column_int(&q, 4); |
| 950 | const char *zName = db_column_text(&q, 5); |
| 951 | int isExe = db_column_int(&q, 6); |
| 952 | int islinkv = db_column_int(&q, 7); |
| 953 | int islinkm = db_column_int(&q, 8); |
| 954 | int chnged = db_column_int(&q, 11); |
| 955 | int rc; |
| 956 | char *zFullPath; |
| 957 | Blob m, p, r; |
| 958 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 959 | if( verboseFlag ){ |
| @@ -864,13 +963,29 @@ | |
| 963 | fossil_print("MERGE %s\n", zName); |
| 964 | } |
| 965 | if( islinkv || islinkm ){ |
| 966 | fossil_print("***** Cannot merge symlink %s\n", zName); |
| 967 | nConflict++; |
| 968 | db_multi_exec( |
| 969 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)" |
| 970 | "VALUES('MERGE',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')", |
| 971 | /* fnp */ db_column_text(&q, 9), |
| 972 | /* ridp */ ridp, |
| 973 | /* fn */ zName, |
| 974 | /* ridv */ ridv, |
| 975 | /* fnm */ db_column_text(&q, 10), |
| 976 | /* ridm */ ridm, |
| 977 | /* fnr */ zName |
| 978 | ); |
| 979 | }else{ |
| 980 | i64 sz; |
| 981 | const char *zErrMsg = 0; |
| 982 | int nc = 0; |
| 983 | |
| 984 | if( !dryRunFlag ) undo_save(zName); |
| 985 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 986 | sz = file_size(zFullPath, ExtFILE); |
| 987 | content_get(ridp, &p); |
| 988 | content_get(ridm, &m); |
| 989 | if( isBinary ){ |
| 990 | rc = -1; |
| 991 | blob_zero(&r); |
| @@ -887,15 +1002,35 @@ | |
| 1002 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
| 1003 | if( rc>0 ){ |
| 1004 | fossil_print("***** %d merge conflict%s in %s\n", |
| 1005 | rc, rc>1 ? "s" : "", zName); |
| 1006 | nConflict++; |
| 1007 | nc = rc; |
| 1008 | zErrMsg = "merge conflicts"; |
| 1009 | } |
| 1010 | }else{ |
| 1011 | fossil_print("***** Cannot merge binary file %s\n", zName); |
| 1012 | nConflict++; |
| 1013 | nc = 1; |
| 1014 | zErrMsg = "cannot merge binary file"; |
| 1015 | } |
| 1016 | db_multi_exec( |
| 1017 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" |
| 1018 | "VALUES('MERGE',%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),%Q,%d," |
| 1019 | "%Q,%d,%Q)", |
| 1020 | /* fnp */ db_column_text(&q, 9), |
| 1021 | /* ridp */ ridp, |
| 1022 | /* fn */ zName, |
| 1023 | /* ridv */ chnged==0, ridv, |
| 1024 | /* sz */ chnged!=0, sz, |
| 1025 | /* fnm */ db_column_text(&q, 10), |
| 1026 | /* ridm */ ridm, |
| 1027 | /* fnr */ zName, |
| 1028 | /* nc */ nc, |
| 1029 | /* msg */ zErrMsg |
| 1030 | ); |
| 1031 | fossil_free(zFullPath); |
| 1032 | blob_reset(&p); |
| 1033 | blob_reset(&m); |
| 1034 | blob_reset(&r); |
| 1035 | } |
| 1036 | vmerge_insert(idv, ridm); |
| @@ -904,22 +1039,33 @@ | |
| 1039 | |
| 1040 | /* |
| 1041 | ** Drop files that are in P and V but not in M |
| 1042 | */ |
| 1043 | db_prepare(&q, |
| 1044 | "SELECT idv, fn, chnged, ridv FROM fv" |
| 1045 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 1046 | ); |
| 1047 | while( db_step(&q)==SQLITE_ROW ){ |
| 1048 | int idv = db_column_int(&q, 0); |
| 1049 | const char *zName = db_column_text(&q, 1); |
| 1050 | int chnged = db_column_int(&q, 2); |
| 1051 | int ridv = db_column_int(&q, 3); |
| 1052 | int sz = -1; |
| 1053 | const char *zErrMsg = 0; |
| 1054 | int nc = 0; |
| 1055 | /* Delete the file idv */ |
| 1056 | fossil_print("DELETE %s\n", zName); |
| 1057 | if( chnged ){ |
| 1058 | char *zFullPath; |
| 1059 | fossil_warning("WARNING: local edits lost for %s", zName); |
| 1060 | nConflict++; |
| 1061 | ridv = 0; |
| 1062 | nc = 1; |
| 1063 | zErrMsg = "local edits lost"; |
| 1064 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1065 | sz = file_size(zFullPath, ExtFILE); |
| 1066 | fossil_free(zFullPath); |
| 1067 | } |
| 1068 | if( !dryRunFlag ) undo_save(zName); |
| 1069 | db_multi_exec( |
| 1070 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 1071 | ); |
| @@ -926,10 +1072,20 @@ | |
| 1072 | if( !dryRunFlag ){ |
| 1073 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 1074 | file_delete(zFullPath); |
| 1075 | free(zFullPath); |
| 1076 | } |
| 1077 | db_multi_exec( |
| 1078 | "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)" |
| 1079 | "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL)," |
| 1080 | "NULL,NULL,%d,%Q)", |
| 1081 | /* fn */ zName, |
| 1082 | /* ridv */ chnged==0, ridv, |
| 1083 | /* sz */ chnged!=0, sz, |
| 1084 | /* nc */ nc, |
| 1085 | /* msg */ zErrMsg |
| 1086 | ); |
| 1087 | } |
| 1088 | db_finalize(&q); |
| 1089 | |
| 1090 | /* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
| 1091 | ** being renamed must first be moved to a temporary location to avoid |
| @@ -956,10 +1112,14 @@ | |
| 1112 | const char *zNewName = db_column_text(&q, 2); |
| 1113 | int isExe = db_column_int(&q, 3); |
| 1114 | fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
| 1115 | if( !dryRunFlag ) undo_save(zOldName); |
| 1116 | if( !dryRunFlag ) undo_save(zNewName); |
| 1117 | db_multi_exec( |
| 1118 | "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q", |
| 1119 | zOldName |
| 1120 | ); |
| 1121 | db_multi_exec( |
| 1122 | "UPDATE vfile SET pathname=NULL, origname=pathname" |
| 1123 | " WHERE vid=%d AND pathname=%Q;" |
| 1124 | "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 1125 | " WHERE id=%d;", |
| @@ -1009,11 +1169,11 @@ | |
| 1169 | |
| 1170 | /* |
| 1171 | ** Insert into V any files that are not in V or P but are in M. |
| 1172 | */ |
| 1173 | db_prepare(&q, |
| 1174 | "SELECT idm, fnm, ridm FROM fv" |
| 1175 | " WHERE idp=0 AND idv=0 AND idm>0" |
| 1176 | ); |
| 1177 | while( db_step(&q)==SQLITE_ROW ){ |
| 1178 | int idm = db_column_int(&q, 0); |
| 1179 | const char *zName; |
| @@ -1042,10 +1202,17 @@ | |
| 1202 | nOverwrite++; |
| 1203 | }else{ |
| 1204 | fossil_print("ADDED %s\n", zName); |
| 1205 | } |
| 1206 | fossil_free(zFullName); |
| 1207 | db_multi_exec( |
| 1208 | "INSERT INTO mergestat(op,fnm,ridm,fnr)" |
| 1209 | "VALUES('ADDED',%Q,%d,%Q)", |
| 1210 | /* fnm */ zName, |
| 1211 | /* ridm */ db_column_int(&q,2), |
| 1212 | /* fnr */ zName |
| 1213 | ); |
| 1214 | if( !dryRunFlag ){ |
| 1215 | undo_save(zName); |
| 1216 | vfile_to_disk(0, idm, 0, 0); |
| 1217 | } |
| 1218 | } |
| 1219 |